How to use Python decorators to check function arguments?
我想在调用一些函数之前定义一些通用的修饰符来检查参数。
类似:
1 2 3 4 | @checkArguments(types = ['int', 'float']) def myFunction(thisVarIsAnInt, thisVarIsAFloat) ''' Here my code ''' pass |
侧记:
从函数和方法的decorator:
1 2 3 4 5 6 7 8 9 10 11 | def accepts(*types): def check_accepts(f): assert len(types) == f.func_code.co_argcount def new_f(*args, **kwds): for (a, t) in zip(args, types): assert isinstance(a, t), \ "arg %r does not match %s" % (a,t) return f(*args, **kwds) new_f.func_name = f.func_name return new_f return check_accepts |
用途:
1 2 3 4 5 6 | @accepts(int, (int,float)) def func(arg1, arg2): return arg1 * arg2 func(3, 2) # -> 6 func('3', 2) # -> AssertionError: arg '3' does not match <type 'int'> |
在Python3.3上,可以使用函数注释并检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import inspect def validate(f): def wrapper(*args): fname = f.__name__ fsig = inspect.signature(f) vars = ', '.join('{}={}'.format(*pair) for pair in zip(fsig.parameters, args)) params={k:v for k,v in zip(fsig.parameters, args)} print('wrapped call to {}({})'.format(fname, params)) for k, v in fsig.parameters.items(): p=params[k] msg='call to {}({}): {} failed {})'.format(fname, vars, k, v.annotation.__name__) assert v.annotation(params[k]), msg ret = f(*args) print(' returning {} with annotation:"{}"'.format(ret, fsig.return_annotation)) return ret return wrapper @validate def xXy(x: lambda _x: 10<_x<100, y: lambda _y: isinstance(_y,float)) -> ('x times y','in X and Y units'): return x*y xy = xXy(10,3) print(xy) |
如果存在验证错误,则打印:
1 | AssertionError: call to xXy(x=12, y=3): y failed <lambda>) |
如果没有验证错误,则打印:
1 2 | wrapped call to xXy({'y': 3.0, 'x': 12}) returning 36.0 with annotation:"('x times y', 'in X and Y units')" |
在断言失败时,可以使用函数而不是lambda来获取名称。
正如你所知道的,仅仅根据一个参数的类型来拒绝它并不是Python式的。Python疗法相当于"先处理它"这就是为什么我宁愿做一个修饰器来转换参数
1 2 3 4 5 6 7 8 9 10 | def enforce(*types): def decorator(f): def new_f(*args, **kwds): #we need to convert args into something mutable newargs = [] for (a, t) in zip(args, types): newargs.append( t(a)) #feel free to have more elaborated convertion return f(*newargs, **kwds) return new_f return decorator |
这样,您的函数就得到了您期望的类型但是,如果参数可以像浮点数一样抖动,则可以接受
1 2 3 4 5 6 7 | @enforce(int, float) def func(arg1, arg2): return arg1 * arg2 print (func(3, 2)) # -> 6.0 print (func('3', 2)) # -> 6.0 print (func('three', 2)) # -> ValueError: invalid literal for int() with base 10: 'three' |
我使用这个技巧(使用适当的转换方法)来处理向量。我编写的许多方法都期望MyVector类具有许多功能;但有时您只想编写
1 | transpose ((2,4)) |
为了对解析器强制使用字符串参数,当提供非字符串输入时,这些参数会引发神秘的错误,我编写了以下内容,试图避免分配和函数调用:
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 | from functools import wraps def argtype(**decls): """Decorator to check argument types. Usage: @argtype(name=str, text=str) def parse_rule(name, text): ... """ def decorator(func): code = func.func_code fname = func.func_name names = code.co_varnames[:code.co_argcount] @wraps(func) def decorated(*args,**kwargs): for argname, argtype in decls.iteritems(): try: argval = args[names.index(argname)] except ValueError: argval = kwargs.get(argname) if argval is None: raise TypeError("%s(...): arg '%s' is null" % (fname, argname)) if not isinstance(argval, argtype): raise TypeError("%s(...): arg '%s': type is %s, must be %s" % (fname, argname, type(argval), argtype)) return func(*args,**kwargs) return decorated return decorator |
所有这些帖子似乎都过时了-Pint现在提供了内置的功能。请看这里。为子孙后代复制到这里:
Checking dimensionality When you want pint quantities to be used as
inputs to your functions, pint provides a wrapper to ensure units are
of correct type - or more precisely, they match the expected
dimensionality of the physical quantity.Similar to wraps(), you can pass None to skip checking of some
parameters, but the return parameter type is not checked.
1 >>> mypp = ureg.check('[length]')(pendulum_period)In the decorator format:
1
2
3 >>> @ureg.check('[length]')
... def pendulum_period(length):
... return 2*math.pi*math.sqrt(length/G)
1 2 3 4 5 6 7 8 | def decorator(function): def validation(*args): if type(args[0]) == int and \ type(args[1]) == float: return function(*args) else: print('Not valid !') return validation |
我认为python 3.5对这个问题的答案是beartype。正如本文中所解释的,它有一些方便的特性。然后您的代码将如下所示
1 2 3 4 | from beartype import beartype @beartype def sprint(s: str) -> None: print(s) |
结果
1 2 3 4 5 6 7 | >>> sprint("s") s >>> sprint(3) Traceback (most recent call last): File"<stdin>", line 1, in <module> File"<string>", line 13, in func_beartyped TypeError: sprint() parameter s=3 not of <class 'str'> |
我有一个稍微改进的@jbouwmans解决方案版本,它使用python decorator模块,使decorator完全透明,不仅保留签名,而且保留文档字符串,这可能是使用decorator的最优雅的方式。
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 | from decorator import decorator def check_args(**decls): """Decorator to check argument types. Usage: @check_args(name=str, text=str) def parse_rule(name, text): ... """ @decorator def wrapper(func, *args, **kwargs): code = func.func_code fname = func.func_name names = code.co_varnames[:code.co_argcount] for argname, argtype in decls.iteritems(): try: argval = args[names.index(argname)] except IndexError: argval = kwargs.get(argname) if argval is None: raise TypeError("%s(...): arg '%s' is null" % (fname, argname)) if not isinstance(argval, argtype): raise TypeError("%s(...): arg '%s': type is %s, must be %s" % (fname, argname, type(argval), argtype)) return func(*args, **kwargs) return wrapper |