这似乎是不可能的,但是我正在尝试实现以下内容:
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语句是首选,但我认为这两种方法都可以。
您可以创建一个将函数存储在列表中的装饰器,该装饰器将由另一个装饰器附加到另一个函数。您似乎认为这是问题的难点,但这很简单:
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
如果您只想修改传递给该函数的参数,则不需要对globals进行所有这些处理。只需让before
函数修改参数,并让装饰器-应用程序before
先将参数传递给每个函数,然后再将其传递给实函数。
或者,或者,如果您想修改函数使用的全局变量,请让函数实际使用这些全局变量而不是获取参数。
或者,或者,如果您想就地改变值,则使它变得可变,如列表,并使before
函数使值发生改变,而不仅仅是将全局变量重新绑定为其他值。
但是,您需要的是一个可以到达调用框架的装饰器,弄清楚对哪些表达式进行了求值以获取参数,并强制对其进行重新求值。那真是愚蠢。
如果您真的很想这样做,那么唯一的方法就是捕获并解释中的字节码sys._getframe(1).f_code
。
至少在CPython 2.7中,您将获得一些代码序列,这些代码序列将装饰后的函数推入堆栈(一个简单的LOAD_NAME
或LOAD_NAME
在典型情况下,但不一定),然后是一系列代码,用于计算表达式,然后是一个CALL_FUNCTION
/ CALL_FUNCTION_VAR
/ etc 。因此,您可以向后走,模拟操作,直到找到将函数推入堆栈的函数为止。(我不确定如何以一种万无一失的方式执行此操作,但它应该可行。然后,构建一个新code
对象,该对象只将函数推入aLOAD_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
将在新的全局变量中对其进行重新评估。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句