在 运行 中更改对函数的引用-在 Python 中

Change reference to function in run-time in Python

我需要在 运行 时间内更改对另一个函数内函数的调用。

考虑以下代码:

def now():
    print "Hello World!"

class Sim:
    def __init__(self, arg, msg):
        self.msg = msg
        self.func = arg
        self.patch(self.func)

    def now(self):
        print self.msg

    def run(self):
        self.func()

    def patch(self, func):
        # Any references to the global now() in func
        # are replaced with the self.now() method.

def myfunc():
    now()

然后...

>>> a = Sim(myfunc, "Hello Locals #1")
>>> b = Sim(myfunc, "Hello Locals #2")
>>> b.run()
Hello Locals #2
>>> a.run()
Hello Locals #1

一位用户编写了代码 myfunc(),它调用了全局定义的函数 now(),但我无法对其进行编辑。但我希望它改为调用 Sim 实例的方法。所以我需要在 运行 时间内 "patch" myfunc() 函数。

我该怎么做?

一种可能的解决方案是编辑字节码,如下所示: http://web.archive.org/web/20140306210310/http://www.jonathon-vogel.com/posts/patching_function_bytecode_with_python 但我想知道是否有更简单的方法。

函数有一个 func_globals 属性。在这种情况下,您可以像这样实现 patch

def now():
    print "Hello World!"


class Sim:
    def __init__(self, arg):
        self.func = arg
        self.patch(self.func)
        self.func()

    def now(self):
        print "Hello Locals!"

    def patch(self, func):
        # Any references to the global now() in func
        # are replaced with the self.now() method.
        func.func_globals['now'] = self.now


def myfunc():
    now()


Sim(myfunc)

打印:

Hello World!
Hello Locals!

不是很优雅,但是您可以用另一个函数包装您的 func,该函数仅在 func 为 运行.

时才修改全局环境
def now():
    print "Hello World!"

class Sim:
    def __init__(self, arg, msg):
        self.msg = msg
    self.func = arg
    self.patch(self.func)

  def now(self):
    print self.msg

  def run(self):
    self.func()

  def patch(self, func):
    # Any references to the global now() in func
    # are replaced with the self.now() method.

    def newfunc():
        global now
        tmp = now
        now = self.now
        func()
        now = tmp

    self.func = newfunc


def myfunc():
    now()

这比看起来要棘手。为此,您需要:

  • 使用 __missing__ 方法创建一个 dict subclass 来保存 now 的新值。 __missing__ 方法然后从通常的 globals() 字典中获取不在字典中的任何项目。

  • 从现有的 myfunc() 函数创建一个新的函数对象,保留其代码对象,但使用您为全局变量创建的新字典。

  • 使用函数名称将新函数重新分配给全局变量。

方法如下:

def now():
    print "Hello World!"

class NowGlobals(dict):
    def __init__(self, now, globals):
        self["now"] = now
        self.globals = globals
    def __missing__(self, key):
        return self.globals[key]

class Sim(object):
    def __init__(self, func):
        func = self.patch(func)
        self.func = func
    def now(self):
        print "Hello locals!"
    def patch(self, func):
        funcname   = func.__name__
        nowglobals = NowGlobals(self.now, func.func_globals)
        func = type(func)(func.func_code, nowglobals)
        globals()[funcname] = func
        return func

def myfunc():
    now()

sim = Sim(myfunc)
myfunc()

确实没有必要将它放在 class 中,但我一直保持这种方式,因为那是您最初编写它的方式。

如果 myfunc 在另一个模块中,您需要重写 Sim.patch() 以修补回该命名空间,例如module.myfunc = sim.patch(module.myfunc)

本回答仅供娱乐。请永远不要这样做。

可以通过将 myfunc 替换为包装函数来完成,该函数制作全局变量字典的副本,替换有问题的 'now' 值,使用原始 myfunc 和新的全局字典,然后调用新函数而不是原始 myfunc。像这样:

import types
def now():
    print("Hello World!")

class Sim:
    def __init__(self, arg):
        self.func = arg
        self.patch(self.func)
        self.func()

    def now(self):
        print("Hello Locals!")

    def patch(self, func):

        def wrapper(*args, **kw):
            globalcopy = globals().copy()
            globalcopy['now'] = self.now
            newfunc = types.FunctionType(func.__code__, globalcopy, func.__name__)
            return newfunc(*args, **kw)
        globals()[func.__name__] = wrapper

def myfunc():
    now()

def otherfunc():
    now()

然后:

>>> myfunc()
Hello World!
>>> otherfunc()
Hello World!
>>> Sim(myfunc)
Hello World!
<__main__.Sim instance at 0x0000000002AD96C8>
>>> myfunc()
Hello Locals!
>>> otherfunc()
Hello World!

有很多理由不这样做。如果 myfunc 访问的 now 函数与 Sim 不在同一个模块中,它将不起作用。它可能会破坏其他东西。此外,像 Sim(myfunc) 这样的普通 class 实例化以这种方式改变全局状态真的很邪恶。

您可以使用 mock 包,它已经解决了修补函数调用的大部分困难工作。它是 Python 2(和 3 的早期版本?)中的第三方安装,但作为 unittest.mock 的 Python 3 标准库的一部分。它主要用于测试,但您可以在这里尝试使用它。

import mock

def now():
    print "Hello World!"

class Sim:
    # Your code from above here

def myfunc():
    now()

myfunc()   # Outputs Hello World!

s = Sim(myfunc, "Hello Locals #1")
mock.patch('__main__.now', wraps=s.now).start()

myfunc()   # Now outputs Hello Locals #1