Watchdog((Param_Type1, Param_Type2, ...), Result_Type) returns a function decorator which can easily be applied to functions in oder to check parameter / result integrity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | from sets import Set, ImmutableSet
from types import TypeType, ClassType, NoneType, InstanceType
class Error(ValueError):
"""Error class for Watchdog"""
def __init__(self, func, value, exp_type, name, number = None):
try:
prefix = "value '%s' of " % value
except:
prefix = ''
if number != None:
number_str = ' #%s' % number
else:
number_str = ''
msg = "mismatch in file '%s', line %s: %s%s%s should be %s, got %s" % (
func.func_code.co_filename, func.func_code.co_firstlineno,
prefix, name, number_str, exp_type, type(value))
ValueError.__init__(self, msg)
class Watchdog(object):
# cache for parameters / result types:
signatures = Set()
def __new__(cls, para_types, res_type, strict = False):
if not strict and not __debug__:
# decorator: identity, does nothing
return lambda func: func
# create watchdog instance:
watchdog = object.__new__(cls)
# assign function signature to instance & add to cache if needed:
signature = para_types, res_type
if signature not in Watchdog.signatures:
# unknown signature: check and insert
if not isinstance(para_types, tuple):
raise ValueError('Parameter #1 must be a tuple')
allow_types = [TypeType, ClassType]
for entry in para_types:
if type(entry) not in allow_types:
msg = 'Parameter #1 must contain only Types and/or Classes'
raise ValueError(msg)
if type(res_type) not in allow_types:
raise ValueError('Parameter #2 must be a Type or Class')
Watchdog.signatures |= ImmutableSet([signature])
watchdog.signature = signature
# returns the decorator(not the watchdog class-instance):
return watchdog.check
def check(self, func):
def caller(*args):
# load function signature:
para_types, res_type = self.signature
normal_len = func.func_code.co_argcount
submit_len = len(args)
if normal_len != len(para_types):
raise ValueError('wrong number of parameter types')
# check if there are conflicts with user arguments:
pairs = zip(args, para_types, range(1, submit_len + 1))
for pair in pairs:
if not isinstance(pair[0], pair[1]):
raise Error(func, pair[0], pair[1], 'parameter', pair[2])
try:
# check default parameter integrity:
default_args = func.func_defaults
default_len = len(default_args)
default_types = para_types[0: default_len]
count_range = range(normal_len - default_len + 1, normal_len + 1)
pairs = zip(default_args, default_types, count_range)
for pair in pairs:
if not isinstance(pair[0], pair[1]):
raise Error(func, pair[0], pair[1],
'default parameter', pair[2])
except TypeError:
# no default arguments: ignore
pass
# we actually call the function here:
res = func(*args)
# check result integrity:
if not isinstance(res, res_type):
raise Error(func, res, res_type, 'result')
return res
return caller
if __name__ == "__main__":
# a small test:
# try to run "python watchdog.py" and "python -O watchdog.py"
@Watchdog((int, int), int, strict = True)
def add(a, b):
return a + b
@Watchdog((int, int), int) # strict is implicitly set to False
def sub(a, b):
return a - b
# strict checking(not affected by __debug__):
try:
add(1, '3')
except Error, err:
print 'strict check failed: %s' % err
# non-strict checking(affected by __debug__):
try:
sub([1,2,3], 7)
except Error, err:
print 'non-strict check failed: %s' % err
|
The idea behind Watchdog is the following: Normally, the __new__ constructor would return a class instance(which cannot be used for function decoration). So the solution is: We first create an instance with a method "check", which takes an arbitrary function as a parameter. Then, we do NOT return the instance, but the "check" method reference, which fits our needs as a decorator.
You can imagine that the instance serves the parameter type / result type configuration to that returned method. To speed things up there is a signatures cache at class-level.
Hope you'll have fun with it Tobi
Documentation? Some docstrings and comments would be nice.
debug/production flag. It will be nice to have debug/production flag:
debug: create decorators as specified production: no checks/empty decorators for speed optimisations
strict flag. I added a 'strict' flag(default = False) for the constructor, which means:
If 'strict' is set, there is no way to to suppress the evaluation - but if it is not set, the evaluation depends from the __debug__ state of the interpreter. When __debug__ is set, there will be an evaluation. Otherwise, the decorator is replaced by "lambda func: func" - should be fast enough.
Best Regards, Tobi