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 116 117 118 119 120 121 122 123 124 125 126 127 128
| ''' One of three degrees of enforcement may be specified by passing the 'debug' keyword argument to the decorator: 0 -- NONE: No type-checking. Decorators disabled. #!python -- MEDIUM: Print warning message to stderr. (Default) 2 -- STRONG: Raise TypeError with message. If 'debug' is not passed to the decorator, the default level is used. Example usage: >>> NONE, MEDIUM, STRONG = 0, 1, 2 >>> >>> @accepts(int, int, int) ... @returns(float) ... def average(x, y, z): ... return (x + y + z) / 2 ... >>> average(5.5, 10, 15.0) TypeWarning: 'average' method accepts (int, int, int), but was given (float, int, float) 15.25 >>> average(5, 10, 15) TypeWarning: 'average' method returns (float), but result is (int) 15 Needed to cast params as floats in function def (or simply divide by 2.0). >>> TYPE_CHECK = STRONG >>> @accepts(int, debug=TYPE_CHECK) ... @returns(int, debug=TYPE_CHECK) ... def fib(n): ... if n in (0, 1): return n ... return fib(n-1) + fib(n-2) ... >>> fib(5.3) Traceback (most recent call last): ... TypeError: 'fib' method accepts (int), but was given (float) ''' import sys def accepts(*types, **kw): '''Function decorator. Checks decorated function's arguments are of the expected types. Parameters: types -- The expected types of the inputs to the decorated function. Must specify type for each parameter. kw -- Optional specification of 'debug' level (this is the only valid keyword argument, no other should be given). debug = ( 0 | 1 | 2 ) ''' if not kw: debug = 1 else: debug = kw['debug'] try: def decorator(f): def newf(*args): if debug is 0: return f(*args) assert len(args) == len(types) argtypes = tuple(map(type, args)) if argtypes != types: msg = info(f.__name__, types, argtypes, 0) if debug is 1: print >> sys.stderr, 'TypeWarning: ', msg elif debug is 2: raise TypeError, msg return f(*args) newf.__name__ = f.__name__ return newf return decorator except KeyError, key: raise KeyError, key + "is not a valid keyword argument" except TypeError, msg: raise TypeError, msg def returns(ret_type, **kw): '''Function decorator. Checks decorated function's return value is of the expected type. Parameters: ret_type -- The expected type of the decorated function's return value. Must specify type for each parameter. kw -- Optional specification of 'debug' level (this is the only valid keyword argument, no other should be given). debug=(0 | 1 | 2) ''' try: if not kw: debug = 1 else: debug = kw['debug'] def decorator(f): def newf(*args): result = f(*args) if debug is 0: return result res_type = type(result) if res_type != ret_type: msg = info(f.__name__, (ret_type,), (res_type,), 1) if debug is 1: print >> sys.stderr, 'TypeWarning: ', msg elif debug is 2: raise TypeError, msg return result newf.__name__ = f.__name__ return newf return decorator except KeyError, key: raise KeyError, key + "is not a valid keyword argument" except TypeError, msg: raise TypeError, msg def info(fname, expected, actual, flag): '''Convenience function returns nicely formatted error/warning msg.''' format = lambda types: ', '.join([str(t).split("'")[1] for t in types]) expected, actual = format(expected), format(actual) msg = "'{}' method ".format( fname )\ + ("accepts", "returns")[flag] + " ({}), but ".format(expected)\ + ("was given", "result is")[flag] + " ({})".format(actual) return msg
|