您可以创建一个将函数存储在列表中的装饰器,以由另一个装饰器附加到另一个函数。这似乎是您认为问题中最困难的部分,但这很简单:
before_funcs = []
def before(func):
before_funcs.append(func)
return func
def attach_befores(func):
@functools.wraps(func)
def newfunc(*args, **kwargs):
for before_func in before_funcs:
before_func()
return func(*args, **kwargs)
return newfunc
所以,现在你可以这样做:
a = 0
@before
def callback():
global a
a += 1
@before
def another():
global a
a *= 2
@attach_befores
def do_thing(i):
print(i)
请注意,您需要global a
那里,因为该功能在其他情况下无效。
现在,您可以调用它:
do_thing(a)
do_thing(a)
do_thing(a)
do_thing(a)
然而,它不会给你想要的结果——特别是,改变全局a
不会改变传递给真实do_thing
函数的参数。为什么?因为函数参数是在调用函数之前评估的。a
所以,在参数已经被评估之后重新绑定对你没有好处。当然,它仍然会更改传递给下一个调用的参数。因此,输出将是:
0
2
6
14
如果您只想修改传递给函数的参数,则不需要所有这些与全局变量有关的东西。只需让before
函数修改参数,并让装饰器应用程序将参数传递给每个before
函数,然后再将它们传递给真正的函数。
或者,或者,如果您想修改函数使用的全局变量,请让函数实际使用这些全局变量而不是获取参数。
或者,或者,如果您想就地改变值,请将其设置为可变的,例如列表,并让before
函数改变值,而不是仅仅将全局重新绑定到不同的值。
但是您需要的是一个装饰器,它可以到达调用框架,找出评估了哪些表达式以获取参数,并强制重新评估它们。这很愚蠢。
如果您真的非常想这样做,那么唯一的方法就是捕获和解释sys._getframe(1).f_code
.
至少在 CPython 2.7 中,您将获得一些将您的装饰函数推入堆栈的代码序列(简单LOAD_NAME
或LOAD_NAME
典型情况下,但不一定),然后是评估表达式的代码序列,然后是CALL_FUNCTION
/ CALL_FUNCTION_VAR
/etc . 因此,您可以向后走,模拟操作,直到找到将函数压入堆栈的操作。(我不确定如何以一种万无一失的方式做到这一点,但它应该是可行的。然后,构建一个新code
对象,它只是用 a 推送你的函数LOAD_CONST
并在它之后重复所有操作(然后返回值)。然后将其包装code
在function
与调用者完全相同的环境中,然后调用该新函数并返回其值,而不是直接调用包装函数。
这是一个例子:
def call_do_thing(b):
global a
b += a
return do_thing(a * b)
伪字节码是:
LOAD_FAST b
LOAD_GLOBAL a
INPLACE_ADD
STORE_FAST b
LOAD_GLOBAL do_thing
LOAD_GLOBAL a
LOAD_FAST b
BINARY_MULTIPLY
CALL_FUNCTION 1
RETURN_VALUE
在这种情况下,查找函数调用很容易,因为它使用了LOAD_GLOBAL
. 因此,我们只需要将所有操作从那里带到RETURN_VALUE
并包装在一个新函数中调用,而不是我们已经给出的那个,并将a
在新的全局变量中重新评估。