带有可选参数(即函数)的Python装饰器
注意:我知道带有可选参数的装饰器包含三个嵌套函数。但这里的可选参数是函数本身。请仔细阅读完整的帖子,然后再将其标记为副本。我已经尝试了使用可选参数的装饰器的所有技巧,但找不到任何将函数作为参数的装饰器 我有一个包装错误的装饰师:带有可选参数(即函数)的Python装饰器,python,python-2.7,decorator,python-decorators,Python,Python 2.7,Decorator,Python Decorators,注意:我知道带有可选参数的装饰器包含三个嵌套函数。但这里的可选参数是函数本身。请仔细阅读完整的帖子,然后再将其标记为副本。我已经尝试了使用可选参数的装饰器的所有技巧,但找不到任何将函数作为参数的装饰器 我有一个包装错误的装饰师: def wrap_error(func): from functools import wraps @wraps(func) def wrapper(*args, **kwargs): try: retu
def wrap_error(func):
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
import sys
exc_msg = traceback.format_exception(*sys.exc_info())
raise MyCustomError(exc_msg)
return wrapper
如果某个函数引发任何异常,它将封装错误。此包装器的使用方式如下:
@wrap_error
def foo():
...
现在我想用附加的回调函数修改这个包装器,这个回调函数是可选的。我希望这个包装被用作:
@wrap_error
def foo():
...
@wrap_error(callback)
def foo():
...
基于包装器中的isfunctionfunc检查,我知道如何在传递的参数不是函数的情况下使用可选参数编写装饰器。但我不知道如何处理这个案子
注意:我不能用@wrap\u error代替@wrap\u error。此包装器用于多个包,并且不可能在所有包中更新更改
以下是拦截器:
考虑包装为:
@wrap_error(callback) ---> foo = wrap_error(callback)(foo)
def foo():
...
因此,在执行wrap_errorfoo时,我们不知道在执行之后是否会有任何回调函数,以防我们只使用@wrap_error而不是@wrap_errorcallback
如果没有回调,wrap_error中的wrapping函数将返回func*args**kwargs以便我可以提出异常。否则我们必须返回func,以便在下一步调用它,如果func引发异常,我们将在exception block中调用callback。要在尝试回答问题之前总结问题,您需要一个在以下两种上下文中都能正常工作的装饰器:
@decorator # case 1
def some_func(...):
...
@decorator(some_callback) # case 2
def some_func(...):
...
或者,展开@语法以澄清问题:
some_func = decorator(some_func) # case 1
some_func = decorator(some_callback)(some_func) # case 2
在我看来,这里的棘手问题是,装饰者很难区分某些函数和回调函数之间的区别,因此也很难区分案例1和案例2;两者都可能只是可调用的对象
一种可能的解决方案是提供命名参数:
# imports at top of file, not in function definitions
from functools import wraps
import sys
def decorator(func=None, callback=None):
# Case 1
if func is not None:
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs) # or whatever
return wrapper
# Case 2
elif callback is not None:
def deco(f):
@wraps(f)
def wrapper(*args, **kwargs):
return callback(f(*args, **kwargs)) # or whatever
return wrapper
return deco
这使得案例2看起来略有不同:
@decorator(callback=some_callback)
def some_func(...):
...
但除此之外,你想做什么就做什么。请注意,您说您不能使用的选项
@decorator()
def some_func(...):
...
无法使用此选项,因为装饰程序希望提供func或callback,否则它将返回None,这是不可调用的,因此您将得到一个TypeError。由于很难区分装饰程序func和装饰程序callback,请创建两个装饰程序:
from functools import wraps
class MyCustomError(Exception):
def __init__(self):
print('in MyCustomError')
# Common implementation
def wrap(func,cb=None):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
if cb is not None:
cb()
raise MyCustomError()
return wrapper
# No parameters version
def wrap_error(func):
return wrap(func)
# callback parameter version
def wrap_error_cb(cb):
def deco(func):
return wrap(func,cb)
return deco
@wrap_error
def foo(a,b):
print('in foo',a,b)
raise Exception('foo exception')
def callback():
print('in callback')
@wrap_error_cb(callback)
def bar(a):
print('in bar',a)
raise Exception('bar exception')
检查foo和bar是否正确使用functools.wrap:
接受参数的装饰器是不同的;它们有三层def,而不是两层。您可以尝试编写返回装饰器或装饰函数的内容,具体取决于调用方式,但这将很难实现;如何区分使用回调调用与使用要包装的函数调用的区别?两者都是可调用的对象。您是否考虑过传递要包装的函数或回调的字符串名?您必须有一些有效回调的映射,可能有一些注册方法,但这将允许您根据需要使用它。@jornsharpe,不过这是可能的;Django是在它的.Ah中实现的,不过我明白你关于包装的func和回调之间混淆的观点。别理我,问题更新了。我知道带有可选参数的装饰器包含三个嵌套函数。但这里的可选参数是函数本身。请仔细阅读完整的帖子,然后再将其标记为副本。我已经用可选参数尝试了装饰器的所有技巧,但是我找不到任何将函数作为参数的方法。有没有办法将两者合并为一个呢?这不太实用。您有两种不同的行为,因此使用两个函数。为什么要把它复杂化@jonrsharpe提供了一种使用命名参数的方法,但没有指定语法。我确实想出了一种使用语法的方法。它涉及到使用另一个decorator标记回调函数,以便将其与包装函数区分开来。请参阅更新的答案。我尝试了这个。但他没能做到这一点。你能粘贴关于你的建议的完整代码吗?@Moinuddinqadri我已经更新了,但是更多的信息会对你有所帮助。@JonShape:我的意思是你写的代码没有按预期工作。它没有包装异常。不过,谢谢你的回复。@moinuddinkadri你能说得更具体一点吗?错误?意外行为?我的演示只是为了展示一个装饰师是如何工作的,你必须根据你的具体需要来调整它。@johnsharpe:更新你的问题答案。
>>> foo
<function foo at 0x0000000003F00400>
>>> bar
<function bar at 0x0000000003F00598>
>>> foo(1,2)
in foo 1 2
in MyCustomError
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "C:\test.py", line 16, in wrapper
raise MyCustomError()
MyCustomError
>>> bar(3)
in bar 3
in callback
in MyCustomError
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "C:\test.py", line 16, in wrapper
raise MyCustomError()
MyCustomError
from functools import wraps
class MyCustomError(Exception):
def __init__(self):
print('in MyCustomError')
# Common implementation
def wrap(func,cb=None):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except:
if cb is not None:
cb()
raise MyCustomError()
return wrapper
def wrap_error(func_or_cb):
# If the function is tagged as a wrap_error_callback
# return a decorator that returns the wrapped function
# with a callback.
if hasattr(func_or_cb,'cb'):
def deco(func):
return wrap(func,func_or_cb)
return deco
# Otherwise, return a wrapped function without a callback.
return wrap(func_or_cb)
# decorator to tag callbacks so wrap_error can distinguish them
# from *regular* functions.
def wrap_error_callback(func):
func.cb = True
return func
### Examples of use
@wrap_error
def foo(a,b):
print('in foo',a,b)
raise Exception('foo exception')
@wrap_error_callback
def callback():
print('in callback')
@wrap_error(callback)
def bar(a):
print('in bar',a)
raise Exception('bar exception')