如何使用 Python 装饰器检查函数参数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15299878/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use Python decorators to check function arguments?
提问by AsTeR
I would like to define some generic decorators to check arguments before calling some functions.
我想定义一些通用装饰器来在调用一些函数之前检查参数。
Something like:
就像是:
@checkArguments(types = ['int', 'float'])
def myFunction(thisVarIsAnInt, thisVarIsAFloat)
''' Here my code '''
pass
Side notes:
旁注:
- Type checking is just here to show an example
- I'm using Python 2.7 but Python 3.0 whould be interesting too
- 类型检查只是为了展示一个例子
- 我正在使用 Python 2.7 但 Python 3.0 也应该很有趣
采纳答案by jfs
From the Decorators for Functions and Methods:
Python 2
蟒蛇 2
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
Python 3
蟒蛇 3
In Python 3 func_codehas changed to __code__and func_namehas changed to __name__.
在 Python 3 中func_code已更改为__code__并且func_name已更改为__name__.
def accepts(*types):
def check_accepts(f):
assert len(types) == f.__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.__name__ = f.__name__
return new_f
return check_accepts
Usage:
用法:
@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'>
arg2can be either intor float
arg2可以是int或float
回答by dawg
On Python 3.3, you can use function annotations and inspect:
在 Python 3.3 上,您可以使用函数注释并检查:
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)
If there is a validation error, prints:
如果存在验证错误,则打印:
AssertionError: call to xXy(x=12, y=3): y failed <lambda>)
If there is not a validation error, prints:
如果没有验证错误,则打印:
wrapped call to xXy({'y': 3.0, 'x': 12})
returning 36.0 with annotation: "('x times y', 'in X and Y units')"
You can use a function rather than a lambda to get a name in the assertion failure.
您可以使用函数而不是 lambda 来获取断言失败中的名称。
回答by jbouwman
To enforce string arguments to a parser that would throw cryptic errors when provided with non-string input, I wrote the following, which tries to avoid allocation and function calls:
为了强制解析器的字符串参数在提供非字符串输入时会抛出神秘错误,我写了以下内容,试图避免分配和函数调用:
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
回答by MaxFragg
I have a slightly improved version of @jbouwmans sollution, using python decorator module, which makes the decorator fully transparent and keeps not only signature but also docstrings in place and might be the most elegant way of using decorators
我有一个稍微改进的@jbouwmans 解决方案,使用 python 装饰器模块,这使得装饰器完全透明,不仅保持签名而且保持文档字符串到位,这可能是使用装饰器的最优雅的方式
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
回答by Madlozoz
As you certainly know, it's not pythonic to reject an argument only based on its type.
Pythonic approach is rather "try to deal with it first"
That's why I would rather do a decorator to convert the arguments
您当然知道,仅根据参数类型拒绝参数并不是 Pythonic。
Pythonic 的方法是“尝试先处理它”
这就是为什么我宁愿做一个装饰器来转换参数
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
This way, your function is fed with the type you expect But if the parameter can quack like a float, it is accepted
这样,您的函数就会使用您期望的类型但是如果参数可以像浮点数一样嘎嘎作响,它就会被接受
@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'
I use this trick (with proper conversion method) to deal with vectors.
Many methods I write expect MyVector class as it has plenty of functionalities; but sometime you just want to write
我使用这个技巧(使用适当的转换方法)来处理向量。
我编写的许多方法都期望 MyVector 类,因为它具有很多功能;但有时你只想写
transpose ((2,4))
回答by Iwan LD
I think the Python 3.5 answer to this question is beartype. As explained in this postit comes with handy features. Your code would then look like this
我认为这个问题的 Python 3.5 答案是beartype。正如这篇文章中所解释的,它具有方便的功能。您的代码将如下所示
from beartype import beartype
@beartype
def sprint(s: str) -> None:
print(s)
and results in
并导致
>>> 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'>
回答by Ethan Keller
All of these posts seem out of date - pint now provides this functionality built in. See here. Copied here for posterity:
所有这些帖子似乎都过时了 - 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.
>>> mypp = ureg.check('[length]')(pendulum_period)In the decorator format:
>>> @ureg.check('[length]') ... def pendulum_period(length): ... return 2*math.pi*math.sqrt(length/G)
检查维度 当您希望将品脱数量用作函数的输入时,品脱提供了一个包装器以确保单位的类型正确 - 或者更准确地说,它们与物理量的预期维度相匹配。
与 wraps() 类似,您可以通过 None 跳过某些参数的检查,但不检查返回参数类型。
>>> mypp = ureg.check('[length]')(pendulum_period)在装饰器格式中:
>>> @ureg.check('[length]') ... def pendulum_period(length): ... return 2*math.pi*math.sqrt(length/G)
回答by Facundo
The package typeguardprovides a decorator for this, it reads the type information from type annotations, it requires Python >=3.5.2 though. I think the resulting code is quite nice.
该包typeguard为此提供了一个装饰器,它从类型注释中读取类型信息,但它需要 Python >=3.5.2。我认为生成的代码非常好。
@typeguard.typechecked
def my_function(this_var_is_an_int: int, this_var_is_a_float: float)
''' Here my code '''
pass

