如何获取装饰器包装的函数的源代码?
How to get source code of function that is wrapped by a decorator?
我想打印 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)
但是,它 returns 包装器的来源:
@wraps(some_function)
def wrapper():
some_function()
有没有办法让它打印以下内容?
def my_func():
print "supposed to return this instead!"
return
请注意,以上内容是从一个更大的程序中抽象出来的。当然我们可以在这个例子中去掉装饰器,但这不是我想要的。
在 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)
运行 代码 before 导入你想看到受影响的装饰器(所以他们最终使用新版本的 functools.update_wrapper()
)。
您仍然必须手动解包(Python 2 inspect
模块不会去寻找属性);这是一个简单的辅助函数:
def unwrap(func):
while hasattr(func, '__wrapped__'):
func = func.__wrapped__
return func
这将解开任何级别的装饰器包装。或者使用 inspect.unwrap()
implementation from Python 3 的副本,其中包括检查意外循环引用。
正如 Martijn Pieters 在他的回答中指出的那样,Python 2 @functool.wraps()
装饰器没有定义 __wrapped__
属性,这将使做您想做的事情变得非常容易.根据 documentation I read, even though it was added in Python 3.2, there was a bug 在版本 3.4 发布之前有时处理它的方式——因此下面的代码使用 v3.4 作为定义自定义 wraps()
装饰器的截止点。
从它的名字来看,你似乎可以控制 my_decorator()
,你可以通过定义你自己的 wraps
类函数来解决这个问题,而不是从关闭,如他的回答所示。方法如下(适用于 Python 2 和 3):
(正如 Martijn 还指出的那样,您可以通过覆盖 functools.wraps
模块属性来猴子修补更改,这将使更改也会影响使用 [=18 的其他模块=] 而不是仅定义它的那个。)
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
我想打印 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)
但是,它 returns 包装器的来源:
@wraps(some_function)
def wrapper():
some_function()
有没有办法让它打印以下内容?
def my_func():
print "supposed to return this instead!"
return
请注意,以上内容是从一个更大的程序中抽象出来的。当然我们可以在这个例子中去掉装饰器,但这不是我想要的。
在 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)
运行 代码 before 导入你想看到受影响的装饰器(所以他们最终使用新版本的 functools.update_wrapper()
)。
您仍然必须手动解包(Python 2 inspect
模块不会去寻找属性);这是一个简单的辅助函数:
def unwrap(func):
while hasattr(func, '__wrapped__'):
func = func.__wrapped__
return func
这将解开任何级别的装饰器包装。或者使用 inspect.unwrap()
implementation from Python 3 的副本,其中包括检查意外循环引用。
正如 Martijn Pieters 在他的回答中指出的那样,Python 2 @functool.wraps()
装饰器没有定义 __wrapped__
属性,这将使做您想做的事情变得非常容易.根据 documentation I read, even though it was added in Python 3.2, there was a bug 在版本 3.4 发布之前有时处理它的方式——因此下面的代码使用 v3.4 作为定义自定义 wraps()
装饰器的截止点。
从它的名字来看,你似乎可以控制 my_decorator()
,你可以通过定义你自己的 wraps
类函数来解决这个问题,而不是从关闭,如他的回答所示。方法如下(适用于 Python 2 和 3):
(正如 Martijn 还指出的那样,您可以通过覆盖 functools.wraps
模块属性来猴子修补更改,这将使更改也会影响使用 [=18 的其他模块=] 而不是仅定义它的那个。)
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