我想创建一个可以与参数一起使用的Python装饰器:
@redirect_output("somewhere.log")
def foo():
....
Run Code Online (Sandbox Code Playgroud)
或没有它们(例如,默认情况下将输出重定向到stderr):
@redirect_output
def foo():
....
Run Code Online (Sandbox Code Playgroud)
那可能吗?
请注意,我不是在寻找重定向输出问题的不同解决方案,它只是我想要实现的语法的一个示例.
bj0*_*bj0 56
我知道这个问题很老,但有些评论是新的,虽然所有可行的解决方案基本相同,但大多数都不是很干净或易于阅读.
就像thobe的回答所说,处理这两种情况的唯一方法是检查这两种情况.最简单的方法就是检查是否有一个参数并且它是callabe(注意:如果你的装饰器只接受1个参数并且碰巧是一个可调用的对象,则需要额外的检查):
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)
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,您执行任何普通装饰器所做的操作,返回传入函数的修改或包装版本.
在第二种情况下,你返回一个'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
Run Code Online (Sandbox Code Playgroud)
现在,我们可以使用@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
Run Code Online (Sandbox Code Playgroud)
tho*_*obe 29
使用具有默认值的关键字参数(如kquinn所建议)是个好主意,但需要包含括号:
@redirect_output()
def foo():
...
Run Code Online (Sandbox Code Playgroud)
如果您想要一个在装饰器上没有括号的情况下工作的版本,则必须在装饰器代码中考虑这两个场景.
如果您使用的是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)
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
任何这些版本都允许您编写如下代码:
@redirected_output
def foo():
...
@redirected_output(destination="somewhere.log")
def bar():
...
Run Code Online (Sandbox Code Playgroud)
hen*_*ace 14
这里的几个答案已经很好地解决了您的问题。然而,在风格方面,我更喜欢使用 来解决这个装饰器困境functools.partial,正如 David Beazley 的Python Cookbook 3 中所建议的:
from functools import partial, wraps
def decorator(func=None, foo='spam'):
if func is None:
return partial(decorator, foo=foo)
@wraps(func)
def wrapper(*args, **kwargs):
# do something with `func` and `foo`, if you're so inclined
pass
return wrapper
Run Code Online (Sandbox Code Playgroud)
虽然是的,但你可以这样做
@decorator()
def f(*args, **kwargs):
pass
Run Code Online (Sandbox Code Playgroud)
没有时髦的解决方法,我觉得它看起来很奇怪,我喜欢选择简单地用@decorator.
至于次要任务目标,重定向函数的输出在这篇Stack Overflow 帖子中得到解决。
如果您想深入了解,请查看Python Cookbook 3 中的第 9 章(元编程),该书可免费在线阅读。
其中一些材料在 Beazley 的精彩 YouTube 视频Python 3 元编程 中进行了现场演示(还有更多!)。
快乐编码:)
Rem*_*ank 12
您需要检测这两种情况,例如使用第一个参数的类型,并相应地返回包装器(在没有参数的情况下使用时)或装饰器(与参数一起使用时).
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
Run Code Online (Sandbox Code Playgroud)
使用@redirect_output("output.log")语法时,redirect_output使用单个参数调用"output.log"它,并且它必须返回一个装饰器,接受要作为参数装饰的函数.当用作时@redirect_output,它将直接调用函数作为参数进行修饰.
或者换句话说:@语法后面必须跟一个表达式,其结果是一个函数,接受一个要作为其唯一参数进行修饰的函数,并返回修饰函数.表达式本身可以是一个函数调用,就是这种情况@redirect_output("output.log").令人费解,但却是真的:-)
我知道这是一个老问题,但我真的不喜欢任何提出的技术,所以我想添加另一种方法.我看到django在他们的login_required装饰器中django.contrib.auth.decorators使用了一个非常干净的方法.正如您在装饰器的文档中看到的那样,它可以单独用作@login_required参数,也可以用于参数@login_required(redirect_field_name='my_redirect_field').
他们这样做的方式非常简单.他们在装饰器参数之前添加了一个kwarg(function=None).如果单独使用装饰器,function将是它正在装饰的实际功能,而如果使用参数调用它,function则将是None.
例:
from functools import wraps
def custom_decorator(function=None, some_arg=None, some_other_arg=None):
def actual_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
# Do stuff with args here...
if some_arg:
print(some_arg)
if some_other_arg:
print(some_other_arg)
return f(*args, **kwargs)
return wrapper
if function:
return actual_decorator(function)
return actual_decorator
Run Code Online (Sandbox Code Playgroud)
@custom_decorator
def test1():
print('test1')
>>> test1()
test1
Run Code Online (Sandbox Code Playgroud)
@custom_decorator(some_arg='hello')
def test2():
print('test2')
>>> test2()
hello
test2
Run Code Online (Sandbox Code Playgroud)
@custom_decorator(some_arg='hello', some_other_arg='world')
def test3():
print('test3')
>>> test3()
hello
world
test3
Run Code Online (Sandbox Code Playgroud)
我发现django使用的这种方法比这里提出的任何其他技术更优雅,更容易理解.
一个python装饰器以一种根本不同的方式被调用,具体取决于你是否给它参数.装饰实际上只是一个(语法限制)表达.
在你的第一个例子中:
@redirect_output("somewhere.log")
def foo():
....
Run Code Online (Sandbox Code Playgroud)
该函数redirect_output被调用,给定的参数,预计将返回一个装饰作用,它本身被调用,foo作为参数,这(最后!)预计将返回最终的装饰功能.
等效代码如下所示:
def foo():
....
d = redirect_output("somewhere.log")
foo = d(foo)
Run Code Online (Sandbox Code Playgroud)
第二个示例的等效代码如下所示:
def foo():
....
d = redirect_output
foo = d(foo)
Run Code Online (Sandbox Code Playgroud)
所以你可以做你想做的事情,但不能完全无缝地做到:
import types
def redirect_output(arg):
def decorator(file, f):
def df(*args, **kwargs):
print 'redirecting to ', file
return f(*args, **kwargs)
return df
if type(arg) is types.FunctionType:
return decorator(sys.stderr, arg)
return lambda f: decorator(arg, f)
Run Code Online (Sandbox Code Playgroud)
这应该没问题,除非您希望使用函数作为装饰器的参数,在这种情况下,装饰器会错误地认为它没有参数.如果此装饰应用于另一个不返回功能类型的装饰,它也将失败.
另一种方法就是要求总是调用装饰器函数,即使它没有参数.在这种情况下,您的第二个示例如下所示:
@redirect_output()
def foo():
....
Run Code Online (Sandbox Code Playgroud)
装饰器功能代码如下所示:
def redirect_output(file = sys.stderr):
def decorator(file, f):
def df(*args, **kwargs):
print 'redirecting to ', file
return f(*args, **kwargs)
return df
return lambda f: decorator(file, f)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
14329 次 |
| 最近记录: |