如何在Python中去除函数的装饰器

97 投票
10 回答
42522 浏览
提问于 2025-04-15 13:04

假设我有以下内容:

def with_connection(f):
    def decorated(*args, **kwargs):
        f(get_connection(...), *args, **kwargs)
    return decorated

@with_connection
def spam(connection):
    # Do something

我想测试一下 spam 这个函数,但不想麻烦地去设置连接(或者说这个装饰器在做什么)。

那么,给定 spam,我该怎么去掉它的装饰器,得到一个没有装饰的“原始”函数呢?

10 个回答

34

balpha的解决方案可以通过这个元装饰器变得更通用:

def include_original(dec):
    def meta_decorator(f):
        decorated = dec(f)
        decorated._original = f
        return decorated
    return meta_decorator

然后你可以用@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
104

这个问题有了一些更新。如果你在使用Python 3,并且用了@functools.wraps,那么你可以使用__wrapped__这个属性来处理标准库中的装饰器。

这里有一个例子,来自《Python Cookbook》第三版,第9.3节 装饰器的解包

>>> @somedecorator
>>> def add(x, y):
...     return x + y
...
>>> orig_add = add.__wrapped__
>>> orig_add(3, 4)
7
>>>

如果你想从自定义装饰器中解包一个函数,那么这个装饰器函数需要使用functools中的wraps函数。具体讨论可以参考《Python Cookbook》第三版,第9.2节 编写装饰器时保留函数元数据

>>> from functools import wraps
>>> def somedecorator(func):
...    @wraps(func)
...    def wrapper(*args, **kwargs):
...       # decorator implementation here
...       # ......
...       return func(*args, **kwargs)
...
...    return wrapper
57

一般情况下,你是做不到的,因为

@with_connection
def spam(connection):
    # Do something

这相当于

def spam(connection):
    # Do something

spam = with_connection(spam)

这意味着“原始”的垃圾邮件可能已经不存在了。一个(不是很优雅的)解决办法是:

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

撰写回答