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

Mar*_*ska 9 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)
Run Code Online (Sandbox Code Playgroud)

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

@wraps(some_function)
def wrapper():
    some_function()
Run Code Online (Sandbox Code Playgroud)

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

def my_func():
    print "supposed to return this instead!"
    return
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*ers 12

在Python 2中,@functools.wraps()装饰器不设置Python 3版本添加的便利__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)
Run Code Online (Sandbox Code Playgroud)

用法:

print inspect.getsource(extract_wrapped(my_func))
Run Code Online (Sandbox Code Playgroud)

使用您的样本进行演示:

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

另一种选择是更新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)
Run Code Online (Sandbox Code Playgroud)

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

def unwrap(func):
    while hasattr(func, '__wrapped__'):
        func = func.__wrapped__
    return func
Run Code Online (Sandbox Code Playgroud)

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