0

这似乎是不可能的事情,但我正在尝试实现这样的事情:

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 语句是首选。

我想,这里有一些东西应该做我想做的事,但是当我真正尝试它时,我得到了一个错误。

4

1 回答 1

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所以,在参数已经被评估之后重新绑定对你没有好处。当然,它仍然会更改传递给下一个调用的参数。因此,输出将是:

0
2
6
14

如果您只想修改传递给函数的参数,则不需要所有这些与全局变量有关的东西。只需让before函数修改参数,并让装饰器应用程序将参数传递给每个before函数,然后再将它们传递给真正的函数。

或者,或者,如果您想修改函数使用的全局变量,请让函数实际使用这些全局变量而不是获取参数。

或者,或者,如果您想就地改变值,请将其设置为可变的,例如列表,并让before函数改变值,而不是仅仅将全局重新绑定到不同的值。

但是您需要的是一个装饰器,它可以到达调用框架,找出评估了哪些表达式以获取参数,并强制重新评估它们。这很愚蠢。


如果您真的非常想这样做,那么唯一的方法就是捕获和解释sys._getframe(1).f_code.

至少在 CPython 2.7 中,您将获得一些将您的装饰函数推入堆栈的代码序列(简单LOAD_NAMELOAD_NAME典型情况下,但不一定),然后是评估表达式的代码序列,然后是CALL_FUNCTION/ CALL_FUNCTION_VAR/etc . 因此,您可以向后走,模拟操作,直到找到将函数压入堆栈的操作。(我不确定如何以一种万无一失的方式做到这一点,但它应该是可行的。然后,构建一个新code对象,它只是用 a 推送你的函数LOAD_CONST并在它之后重复所有操作(然后返回值)。然后将其包装codefunction与调用者完全相同的环境中,然后调用该新函数并返回其值,而不是直接调用包装函数。

这是一个例子:

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在新的全局变量中重新评估。

于 2013-10-16T18:32:00.053 回答