这似乎是可能无法实现的,但我正在尝试实现这样的事情:
a = 0
with before():
a += 1
do_thing(a) # does thing with a, whose value is now 1
do_thing(a) # does thing with a, whose value is now 2
所以我想要一些可以在with语句中使用块的东西,在某个地方保存该块并在每个do_thing
函数调用之前调用它,同时使用该范围。
另一种选择是这样的:
@before
def callback():
a += 1
而不是with声明。我认为,这两个选项对我都没问题,尽管首选的是with语句。
我认为有些东西应该做我想要的,here,但是当我真正尝试它时会出现错误。
答案 0 :(得分:1)
您可以创建一个装饰器,将函数存储在列表中,由另一个装饰器附加到另一个函数。这似乎是你认为问题的难点,但它是微不足道的:
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
对您没有好处。当然,它仍会改变传递给 next 调用的参数。所以,输出将是:
0
2
6
14
如果你只是想修改传递给函数的参数,那么你不需要对全局变量进行所有这些操作。只需让before
函数修改参数,让装饰器应用程序通过每个before
函数传递参数,然后再将它们传递给实函数。
或者,或者,如果你想修改函数使用的全局变量,让函数实际使用那些全局变量而不是参数。
或者,或者,如果你想改变它的位置,使它成为可变的,就像列表一样,并使before
函数改变值,而不是仅仅将全局重新绑定到不同的值。 / p>
但是你所要求的是一个可以达到调用框架的装饰器,找出为获取参数而评估的表达式,并强制它们被重新评估。那太傻了。
如果你真的真的想要这样做,那么唯一的方法就是捕获并解释sys._getframe(1).f_code
中的字节码。
至少在CPython 2.7中,你将得到一些代码序列,将你的修饰函数推送到堆栈上(在典型的情况下是一个简单的LOAD_NAME
或LOAD_NAME
,但不一定),然后是用于评估表达式的代码序列,然后是CALL_FUNCTION
/ CALL_FUNCTION_VAR
/等。因此,您可以向后走,模拟操作,直到找到将函数推入堆栈的操作。 (我不确定如何以万无一失的方式执行此操作,但它应该是可行的。然后,构建一个新的code
对象,只需用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
将被重新评估在新的全局中。