如何从Python中的函数中剥离装饰器

Her*_*rge 58 python decorator

假设我有以下内容:

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)

  • @eduffy:这就是问题的关键所在. (3认同)
  • 如果您要修改代码以调用`_original`,您不妨注释掉装饰器。 (2认同)
  • 这对于 Python 3 来说已经过时了。请参阅下面的答案:/sf/answers/2311731761/ (2认同)

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)

  • Python3获胜! (3认同)
  • 这对我有用。附加信息:如果您的函数有多个装饰器,您可以链接多个“.__wrapped__”来访问原始函数。 (3认同)
  • 这是不真实的。如果使用 [`functools.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps) 装饰,则装饰函数只有一个 `__wrapped__` 属性。另外,链接失效了。 (2认同)

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()检查的顺序也是一种启发式,在任何情况下都有装饰链返回错误的结果.

  • `func_closure`在3.x中被`__closure__`取代,它已经在2.6中了 (4认同)

Oin*_*Oin 8

您现在可以使用未修饰的包:

>>> from undecorated import undecorated
>>> undecorated(spam)
Run Code Online (Sandbox Code Playgroud)

它经历了挖掘不同装饰器的所有层的麻烦,直到它到达底部函数并且不需要更改原始装饰器.它适用于Python 2和Python 3.


Ara*_*Fey 8

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)


dbr*_*dbr 6

而不是......

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正常访问原文.