如何获取由装饰器包装的函数的源代码?

时间:2017-04-19 21:28:16

标签: python decorator python-2.x python-decorators

我想打印由my_func包裹的my_decorator的源代码:

import inspect
from functools import wraps

def my_decorator(some_function):
    @wraps(some_function)
    def wrapper():
        some_function()

    return wrapper

@my_decorator
def my_func():
    print "supposed to return this instead!"
    return

print inspect.getsource(my_func)

但是,它会返回包装器的源代码:

@wraps(some_function)
def wrapper():
    some_function()

是否有办法打印以下内容?

def my_func():
    print "supposed to return this instead!"
    return

请注意,上述内容是从较大的程序中抽象出来的。当然,我们可以在这个例子中摆脱装饰器,但那不是我想要的。

2 个答案:

答案 0 :(得分:11)

在Python 2中,@functools.wraps()装饰器没有设置Python 3 version添加的便捷__wrapped__属性(Python 3.2中的新功能)。

这意味着您将不得不求助于从闭包中提取原始函数。究竟在哪个位置将取决于确切的装饰器实现,但选择第一个函数对象应该是一个很好的概括:

from types import FunctionType

def extract_wrapped(decorated):
    closure = (c.cell_contents for c in decorated.__closure__)
    return next((c for c in closure if isinstance(c, FunctionType)), None)

用法:

print inspect.getsource(extract_wrapped(my_func))

使用您的样本进行演示:

>>> print inspect.getsource(extract_wrapped(my_func))
@my_decorator
def my_func():
    print "supposed to return this instead!"
    return

另一种选择是更新functools库以为您添加__wrapped__属性,与Python 3的作用相同:

import functools

def add_wrapped(uw):
    @functools.wraps(uw)
    def update_wrapper(wrapper, wrapped, **kwargs):
        wrapper = uw(wrapper, wrapped, **kwargs)
        wrapper.__wrapped__ = wrapped
        return wrapper

functools.update_wrapper = add_wrapped(functools.update_wrapper)

在导入要查看受影响的装饰器之前运行该代码(因此他们最终使用新版本的functools.update_wrapper())。 你必须手动解包(Python 2 inspect模块不会寻找属性);这是一个简单的辅助函数:

def unwrap(func):
    while hasattr(func, '__wrapped__'):
        func = func.__wrapped__
    return func

这将解开任何级别的装饰包装。或者使用inspect.unwrap() implementation from Python 3的副本,其中包括检查意外的循环引用。

答案 1 :(得分:2)

正如Martijn Pieters在他的回答中指出的那样,Python 2 @functool.wraps()装饰器没有定义__wrapped__属性,这将使您想要做的事情变得非常简单。根据我读到的documentation,即使它是在Python 3.2中添加的,在版本3.4发布之前有时会处理bug - 所以下面的代码使用v3.4作为用于定义自定义wraps()装饰器的截止点。

因为从它的名字听起来你可以控制my_decorator(),你可以通过定义你自己的wraps之类的函数解决问题,而不是从闭包中提取原始函数,如他的回答所示。这是如何做到的(在Python 2和3中有效):

(正如Martijn所指出的那样,您可以通过覆盖functools.wraps模块属性来修改更改,这会使更改也影响使用functools的其他模块而不是只有它定义的那个。)

import functools
import inspect
import sys

if sys.version_info[0:2] >= (3, 4):  # Python v3.4+?
    wraps = functools.wraps  # built-in has __wrapped__ attribute
else:
    def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
              updated=functools.WRAPPER_UPDATES):
        def wrapper(f):
            f = functools.wraps(wrapped, assigned, updated)(f)
            f.__wrapped__ = wrapped  # set attribute missing in earlier versions
            return f
        return wrapper

def my_decorator(some_function):
    @wraps(some_function)
    def wrapper():
        some_function()

    return wrapper

@my_decorator
def my_func():
    print("supposed to return this instead!")
    return

print(inspect.getsource(my_func.__wrapped__))

输出:

@my_decorator
def my_func():
    print("supposed to return this instead!")
    return