【问题标题】:Prepend function call in other function in Python在 Python 中的其他函数中添加函数调用
【发布时间】:2012-07-08 13:44:09
【问题描述】:

这有点棘手,但我想要的,简单来说,是这样的:

我有一个函数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 异常或类似情况。

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

【问题讨论】:

  • func 如果用作装饰器,总是不会是 None 吗? is not None 也是正确的写法,因为 None 是单例。
  • 这是因为@process 装饰器可以有可选参数,这也是我检查'fail' 的选项数组的原因。

标签: python metaprogramming


【解决方案1】:

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

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

1. 所以,如果你处于一个位置 更改将使用您的装饰器的所有代码 (或将其记录给将使用它的其他人),最直接的, 更简单,我想我会推荐,要走的路是重新分配 函数在运行时的默认参数元组 (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 可以使用与原始 func.func_code 对象相同的参数调用 types.CodeType,但使用“constants”参数——这将替换原始的 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。

【讨论】:

    猜你喜欢
    • 2018-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 2016-05-27
    • 1970-01-01
    相关资源
    最近更新 更多