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

问题描述 投票:0回答:3

我想打印

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
3个回答
20
投票

在 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)

用途:

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

这将打开任何级别的装饰器包装。或者使用 Python 3
中的

inspect.unwrap()

 实现的副本,其中包括检查意外的循环引用。


3
投票
@functool.wraps()

装饰器没有定义

__wrapped__
属性,这将使做你想做的事情变得非常容易。根据我读到的
文档
,尽管它是在 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



0
投票

def extract_wrapped(decorated): closure = decorated.__closure__ if closure: for cell in closure: if isinstance(cell.cell_contents, FunctionType) and cell.cell_contents.__closure__ is None: return cell.cell_contents elif isinstance(cell.cell_contents, FunctionType): return extract_wrapped(cell.cell_contents) return decorated

可以使用extract_wrapped函数来获取被修饰后的原始函数。例如:

import inspect from types import FunctionType def decorator(func): def wrapper(): print("before") func() print("after") return wrapper @decorator @decorator def my_function(): print("func") print("Decorated:") my_function() # before # before # func # after # after def extract_wrapped(decorated): closure = decorated.__closure__ if closure: for cell in closure: if isinstance(cell.cell_contents, FunctionType) and cell.cell_contents.__closure__ is None: return cell.cell_contents elif isinstance(cell.cell_contents, FunctionType): return extract_wrapped(cell.cell_contents) return decorated original = extract_wrapped(my_function) print("\nOriginal source:") print(inspect.getsource(original)) # @decorator # @decorator # def my_function(): # print("func") print("\nOriginal:") original() # func

© www.soinside.com 2019 - 2024. All rights reserved.