【问题标题】:How to get source code of function that is wrapped by a decorator?如何获取由装饰器包装的函数的源代码?
【发布时间】:2017-09-16 07:28:56
【问题描述】:

我想打印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

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

【问题讨论】:

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


    【解决方案1】:

    在 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 的副本,其中包括检查意外循环引用。

    【讨论】:

    • 对于 Python 2,编写自己的 wraps() 装饰器来定义“方便”属性不是也相对容易吗?即使没有必要,该代码似乎也可以在 Python 3 中工作(即它是可移植的)?
    • @martineau:如果你控制了装饰器的源代码,你可以很容易地换掉wraps() 实现,当然。你也可以猴子补丁functools.update_wrapper
    【解决方案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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-25
      • 2018-04-04
      • 1970-01-01
      • 2017-11-05
      • 1970-01-01
      • 2019-07-09
      • 2021-04-06
      • 2017-09-13
      相关资源
      最近更新 更多