4

这有点棘手,但我想要的,以一种简单的方式,是这样的:

我有一个函数foo和一个装饰器process

@process
def foo(x, y):
    print x + y

装饰器正在创建一个Process对象,该对象被传递给foo函数及其参数

# The process decorator
def process(func=None, **options):
    if func != None:
        def _call(*args, **kwargs):
            if len(options) > 0 and 'fail' in options:
                return Process(func, fail=options['fail'], *args, **kwargs)
            else:
                return Process(func, fail=None, *args, **kwargs)
        return _call
    else:
        def _func(func):
            return process(func, **options)
        return _func

我想foo在运行时更改给进程装饰器的函数,以包括对另一个函数的调用,例如一个get_locals()函数,它可能会覆盖局部变量,在开头foo

def foo(x, y):
    get_locals()
    print x + y

我试图用 做一些事情inspect,获取函数的源代码,然后将其编译为代码对象,但是这导致了'code' object is not callable异常或类似的事情。

这甚至可能吗?有没有更简单/更好的解决方案?

4

1 回答 1

1

您想要做的事情有几种不同的方法,在“清洁度”(而不是临时的hackish)、易于做(以及稍后理解和维护)、对Python实现的依赖等方面有所不同。

我看到使用哪种方法的限制因素取决于您可以/想要如何干扰装饰函数本身的声明。

1.因此,如果您能够更改将使用您的装饰器的所有代码(或将其记录给将使用它的其他人),那么我认为我会推荐最简单、最简单的方法go,是在运行时重新分配函数的默认参数元组(foo.func_defaults)。

这种方法要求将要在运行代码中覆盖的变量声明为具有默认值的函数参数:

(出于记录目的,我将使用比您的更简单的装饰器)

new_args = (10,20)

def process(func):
    def new_func(*args, **kw):
        func_defaults = func.func_defaults
        func.func_defaults = new_args
        result = func(*args, **kw)
        func.func_defaults = func_defaults
        return result
    return new_func

@process
def foo(x=1, y =2):
    print x + y

并且“有效”(tm)

>>> foo()
30

2.另一种方法,仍然要求修饰函数遵循特定模式,是将所有要覆盖的变量声明为全局变量,并在调用时创建一个新的函数对象,一切都与原始函数相同,但是改变它的全局字典。由于您为每个调用创建一个新的函数对象,这将是线程安全的 -0 但是,您不能更改声明为函数参数的变量(因为 Python 将它们标记为“本地”,并且它们不能随后被标记作为“全球”)

from types import FunctionType

new_args = {"x":10, "y":20} 

def process(func):
    def new_func(*args, **kw):
        temp_func = FunctionType(func.func_code,
                                 new_args,
                                 func.func_name, 
                                 func.func_defaults,
                                 func.func_closure)
        return temp_func(*args, **kw)
    return new_func


@process
def foo():
    global x, y
    print x + y

3.按照相同的方法,对于您想要覆盖的每个局部变量,您会将其列在函数顶部作为接收默认值 - 就像在

def foo(x=1,y=2):
   z= 3
   w = 4

在运行时,您将在每次调用时重新构建动态,不仅是函数对象,还有它的代码对象 - Yu 可以调用 types.CodeType 与原始 func.func_code 对象相同的参数,但对于“常量" 参数 - 将替换原始的 co_consts 属性。

我不会举例说明这种做事的方式,因为它属于“很好的实验性hack,但对于实际生产代码来说是可怕的”——因为它太依赖于实现了。

4.您可以在您的装饰器上设置代码跟踪-这通常由调试器实用程序完成,以便您可以回调您为要在目标代码上计算的每个表达式指定的函数-您可以启用“settrace”,并在您的回调函数中摆弄目标函数上的局部变量。
这非常简单,一旦你看到 sys.gettrace 的文档 - http://docs.python.org/library/sys.html#sys.settrace - 但是它确实有一点不方便,根本不工作 :-) 发生这种情况是因为尽管您确实有权在框架本地字典上进行读写,但语言优化在使用它们时并不总是从该字典中获取值(如果有的话)。所以这个直截了当的例子表明它不起作用:

>>> import sys
>>> def a():
...    b = 1
...    f = sys._getframe()
...    print f.f_locals
...    f.f_locals["b"] = 2
...    print b
... 
>>> a()
{'b': 1, 'f': <frame object at 0x1038cd0>}
1

5.最后,您可以完全按照您说的做,并使用 Python 的“dis”模块和编译器函数在您的函数代码中注入变量属性 - 直接在字节码上。您必须:反编译函数的代码对象,成功处理字节码以使用所需值正确加载所需的变量,重新编译字节码,重新创建代码对象(使用前面提到的 types.CodeType 调用),传递一个适当的“常量”值以正确加载您的变量,从中重新创建函数对象并进行函数调用。

我不会列举我发现这种方法不如其他方法有用的原因,也不会尝试参考实现。但是,如果做得好,它会起作用,至少对于 cpython 2.x。

于 2012-07-08T19:18:51.847 回答