我想创建一个Python装饰器,它可以使用参数:
@redirect_output("somewhere.log")
def foo():
....
或者不使用它们(例如,默认情况下将输出重定向到stderr ):
@redirect_output
def foo():
....
这有可能吗?
请注意,我并不是在寻找不同的解决方案来解决重定向输出的问题,这只是我想要实现的语法的一个示例。
发布于 2013-01-19 17:24:02
我知道这个问题很老,但有些评论是新的,虽然所有可行的解决方案本质上都是相同的,但大多数都不是非常干净或易于阅读。
正如thobe的答案所说,处理这两种情况的唯一方法是检查两种情况。最简单的方法是简单地检查是否有一个参数,它是callabe (注意:如果你的装饰器只有一个参数,并且它恰好是一个可调用的对象,那么就需要额外的检查):
def decorator(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# called as @decorator
else:
# called as @decorator(*args, **kwargs)
在第一种情况下,您可以像任何普通的装饰器一样,返回传入函数的修改或包装版本。
在第二种情况下,您返回一个'new‘装饰器,该装饰器以某种方式使用通过*args,**kwargs传入的信息。
这很好,但是必须为你制作的每个装饰器都写出来,这可能是相当恼人的,也不是很干净。相反,如果能够自动修改我们的装饰器,而不必重写它们,那就更好了……但这就是装饰者的用处!
使用下面的装饰器装饰器,我们可以取消装饰器的位置,以便它们可以带参数或不带参数使用:
def doublewrap(f):
'''
a decorator decorator, allowing the decorator to be used as:
@decorator(with, arguments, and=kwargs)
or
@decorator
'''
@wraps(f)
def new_dec(*args, **kwargs):
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
# actual decorated function
return f(args[0])
else:
# decorator arguments
return lambda realf: f(realf, *args, **kwargs)
return new_dec
现在,我们可以用@doublewrap来装饰我们的装饰器,它们可以在有参数和没有参数的情况下工作,但有一个警告:
我在上面提到过,但这里应该重复一遍,这个修饰器中的检查对装饰器可以接收的参数进行了假设(即它不能接收单个可调用的参数)。由于我们现在将其应用于任何生成器,因此需要记住它,或者如果它将被矛盾,则修改它。
下面演示了它的用法:
def test_doublewrap():
from util import doublewrap
from functools import wraps
@doublewrap
def mult(f, factor=2):
'''multiply a function's return value'''
@wraps(f)
def wrap(*args, **kwargs):
return factor*f(*args,**kwargs)
return wrap
# try normal
@mult
def f(x, y):
return x + y
# try args
@mult(3)
def f2(x, y):
return x*y
# try kwargs
@mult(factor=5)
def f3(x, y):
return x - y
assert f(2,3) == 10
assert f2(2,5) == 30
assert f3(8,1) == 5*7
发布于 2009-03-17 09:13:19
使用带有默认值的关键字参数(如kquinn所建议的)是一个好主意,但需要包括括号:
@redirect_output()
def foo():
...
如果你想要一个在装饰器上没有括号的版本,你必须在你的装饰器代码中考虑到这两种情况。
如果您使用的是Python 3.0,则可以对以下内容使用仅关键字参数:
def redirect_output(fn=None,*,destination=None):
destination = sys.stderr if destination is None else destination
def wrapper(*args, **kwargs):
... # your code here
if fn is None:
def decorator(fn):
return functools.update_wrapper(wrapper, fn)
return decorator
else:
return functools.update_wrapper(wrapper, fn)
在Python 2.x中,这可以用varargs技巧来模拟:
def redirected_output(*fn,**options):
destination = options.pop('destination', sys.stderr)
if options:
raise TypeError("unsupported keyword arguments: %s" %
",".join(options.keys()))
def wrapper(*args, **kwargs):
... # your code here
if fn:
return functools.update_wrapper(wrapper, fn[0])
else:
def decorator(fn):
return functools.update_wrapper(wrapper, fn)
return decorator
这些版本中的任何一个都允许您编写如下代码:
@redirected_output
def foo():
...
@redirected_output(destination="somewhere.log")
def bar():
...
发布于 2009-03-17 09:25:42
您需要检测这两种情况,例如使用第一个参数的类型,并相应地返回包装器(不带参数时)或装饰器(与参数一起使用时)。
from functools import wraps
import inspect
def redirect_output(fn_or_output):
def decorator(fn):
@wraps(fn)
def wrapper(*args, **args):
# Redirect output
try:
return fn(*args, **args)
finally:
# Restore output
return wrapper
if inspect.isfunction(fn_or_output):
# Called with no parameter
return decorator(fn_or_output)
else:
# Called with a parameter
return decorator
在使用@redirect_output("output.log")
语法时,使用单个参数"output.log"
调用redirect_output
,并且它必须返回一个装饰器,该装饰器接受要作为参数装饰器的函数。当用作@redirect_output
时,它直接与要修饰为参数的函数一起调用。
或者换句话说:@
语法后面必须跟一个表达式,该表达式的结果是一个函数,该函数接受要修饰的函数作为其唯一参数,并返回修饰后的函数。表达式本身可以是函数调用,@redirect_output("output.log")
就是这种情况。错综复杂,但却是真的:-)
https://stackoverflow.com/questions/653368
复制相似问题