假设我有以下内容:
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
Run Code Online (Sandbox Code Playgroud)
我想测试该spam
函数而不必经历设置连接的麻烦(或者装饰器正在做的任何事情).
鉴于spam
,如何从中删除装饰器并获得底层的"未修饰"功能?
bal*_*pha 38
在一般情况下,你不能,因为
@with_connection
def spam(connection):
# Do something
Run Code Online (Sandbox Code Playgroud)
相当于
def spam(connection):
# Do something
spam = with_connection(spam)
Run Code Online (Sandbox Code Playgroud)
这意味着"原始"垃圾邮件可能甚至不再存在.一个(不太漂亮)黑客将是这样的:
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
decorated._original = f
return decorated
@with_connection
def spam(connection):
# Do something
spam._original(testcon) # calls the undecorated function
Run Code Online (Sandbox Code Playgroud)
Ale*_*kov 38
这个问题已经有了一些更新.如果您使用的是Python 3,则可以使用__wrapped__
stdlib中的装饰器属性.
以下是Python Cookbook第3版的示例,第9.3节展开装饰器
>>> @somedecorator
>>> def add(x, y):
... return x + y
...
>>> orig_add = add.__wrapped__
>>> orig_add(3, 4)
7
>>>
Run Code Online (Sandbox Code Playgroud)
如果您尝试从自定义装饰器中解包函数,装饰器函数需要使用wraps
函数来functools
参见Python Cookbook中的讨论,第3版,第9.2节编写装饰器时保留函数元数据
>>> from functools import wraps
>>> def somedecoarator(func):
... @wraps(func)
... def wrapper(*args, **kwargs):
... # decorator implementation here
... # ...
... return func(*args, kwargs)
...
... return wrapper
Run Code Online (Sandbox Code Playgroud)
jcd*_*yer 31
使用这个元装饰器可以使balpha的解决方案更具通用性:
def include_original(dec):
def meta_decorator(f):
decorated = dec(f)
decorated._original = f
return decorated
return meta_decorator
Run Code Online (Sandbox Code Playgroud)
然后你可以使用@include_original装饰你的装饰器,每个人都会有一个可测试的(未修饰的)版本隐藏在里面.
@include_original
def shout(f):
def _():
string = f()
return string.upper()
return _
@shout
def function():
return "hello world"
>>> print function()
HELLO_WORLD
>>> print function._original()
hello world
Run Code Online (Sandbox Code Playgroud)
Woj*_*ski 18
看哪,FuglyHackThatWillWorkForYourExampleButICantPromiseAnythingElse:
orig_spam = spam.func_closure[0].cell_contents
Run Code Online (Sandbox Code Playgroud)
编辑:对于多次装饰的函数/方法以及更复杂的装饰器,您可以尝试使用以下代码.它依赖于这样的事实,即装饰函数与原始函数的__name__d不同.
def search_for_orig(decorated, orig_name):
for obj in (c.cell_contents for c in decorated.__closure__):
if hasattr(obj, "__name__") and obj.__name__ == orig_name:
return obj
if hasattr(obj, "__closure__") and obj.__closure__:
found = search_for_orig(obj, orig_name)
if found:
return found
return None
>>> search_for_orig(spam, "spam")
<function spam at 0x027ACD70>
Run Code Online (Sandbox Code Playgroud)
但这不是万无一失的.如果从装饰器返回的函数的名称与装饰的名称相同,则它将失败.hasattr()检查的顺序也是一种启发式,在任何情况下都有装饰链返回错误的结果.
您现在可以使用未修饰的包:
>>> from undecorated import undecorated
>>> undecorated(spam)
Run Code Online (Sandbox Code Playgroud)
它经历了挖掘不同装饰器的所有层的麻烦,直到它到达底部函数并且不需要更改原始装饰器.它适用于Python 2和Python 3.
functools.wraps
像这样装饰装饰器是一种很好的做法:
import functools
def with_connection(f):
@functools.wraps(f)
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
Run Code Online (Sandbox Code Playgroud)
从 Python 3.2 开始,这将自动添加一个__wrapped__
属性,让您检索原始的、未修饰的函数:
>>> spam.__wrapped__
<function spam at 0x7fe4e6dfc048>
Run Code Online (Sandbox Code Playgroud)
但是,__wrapped__
与其手动访问属性,不如使用inspect.unwrap
:
>>> inspect.unwrap(spam)
<function spam at 0x7fe4e6dfc048>
Run Code Online (Sandbox Code Playgroud)
而不是......
def with_connection(f):
def decorated(*args, **kwargs):
f(get_connection(...), *args, **kwargs)
return decorated
@with_connection
def spam(connection):
# Do something
orig_spam = magic_hack_of_a_function(spam)
Run Code Online (Sandbox Code Playgroud)
你可以做......
def with_connection(f):
...
def spam_f(connection):
...
spam = with_connection(spam_f)
Run Code Online (Sandbox Code Playgroud)
...这就是所有@decorator
语法的作用 - 然后你可以明显地spam_f
正常访问原文.