在Python中的其他函数中添加函数调用

时间:2012-07-08 13:44:10

标签: python metaprogramming

这有点棘手,但我想要的是一个简单的方法:

我有一个函数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

我想在运行时更改为process-decorator提供的foo函数,以包含对另一个函数的调用,例如一个get_locals()函数,可能会在foo的最开始覆盖局部变量:

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

我尝试使用inspect执行某些操作,获取函数的源代码,然后将其编译为代码对象,但这会导致'code' object is not callable异常或类似的事情。< / p>

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

1 个答案:

答案 0 :(得分:1)

您想要做的事情有几种不同的方法, 不同的“清洁度”(与临时的hackish相反), 容易做(并在以后理解和维护), 依赖于Python实现,等等。

我看到使用哪种方法的限制因素取决于 你如何/想要干涉装饰的声明 功能本身。

1。所以,如果你处于某个位置 更改将使用装饰器的所有代码 (或将其记录给将使用它的其他人),最直接的, 更简单,我认为我会推荐,方式去,重新分配 运行时函数的默认参数tuple(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对象相同,但是对于“constants”参数 - 它将替换原始的co_consts属性。

我不会举出这样做事的例子,因为它属于 “很好的实验性黑客,但对于实际的生产代码来说是可怕的” - 因为它太依赖于实现了。

4. 您可以在装饰器上设置代码跟踪 - 通常由debuger实用程序完成,这样您就可以回调一个为每个要计算的表达式指定的函数在目标代码上 - 你可以使用它们,启用“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。