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

时间:2015-05-05 19:16:59

标签: python bytecode introspection

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

请考虑以下代码:

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实例的方法。所以我需要在运行时“修补”myfunc()函数。

我怎么能这样做?

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

5 个答案:

答案 0 :(得分:2)

这比看起来更棘手。要做到这一点,你需要:

  • 使用dict方法创建__missing__子类,以保存新值now__missing__方法然后从通常的globals()字典中获取字典中没有的任何项目。

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

  • 使用函数名将新函数重新分配给globals。

以下是:

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()

实际上没有必要在课堂上使用它,但我保持这种方式,因为这是你最初编写它的方式。

如果myfunc位于其他模块中,则您需要重写Sim.patch()以修补该命名空间,例如module.myfunc = sim.patch(module.myfunc)

答案 1 :(得分:1)

此答案仅供娱乐。请不要这样做。

可以通过将myfunc替换为包装函数来完成,该函数生成全局变量字典的副本,替换有问题的'now'值,使用原始代码对象生成新函数{ {1}}和新的全局字典,然后调用该新函数而不是原始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! 访问的now函数与myfunc不在同一模块中,它将无法工作。它可能会打破其他事情。此外,像Sim这样的普通类实例化以这种方式改变全局状态真的是邪恶的。

答案 2 :(得分:0)

不是很优雅,但你可以使用另一个仅在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()

答案 3 :(得分:0)

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

unittest.mock

答案 4 :(得分:-1)

函数具有@Test public void when_two_watchers_run_together_they_end_up_with_same_number_of_evaluation() throws InterruptedException, IOException { //setup Path input = environment.loadResourceAt("input.txt").asPath(); Path output = environment.loadResourceAt("output.txt").asPath(); if (Files.exists(input)) { Files.delete(input); } if (Files.exists(output)) { Files.delete(output); } Thread thread1 = makeThread(input, output, "watching input"); Thread thread2 = makeThread(output, input, "watching output"); //act thread1.start(); thread2.start(); Thread.sleep(50); BufferedWriter out = new BufferedWriter(new FileWriter(input.toFile())); out.write(0 + ""); out.close(); thread1.join(); thread2.join(); int inputResult = Integer.parseInt(Files.readAllLines(input).get(0)); int outputResult = Integer.parseInt(Files.readAllLines(output).get(0)); //assert assertThat(inputResult).describedAs("Expected is output file, Actual is input file").isEqualTo(outputResult); } public Thread makeThread(Path input, Path output, String threadName) { return new Thread(() -> { try { new Watcher(input, output).watchAndRespond(); } catch (IOException | InterruptedException e) { fail(); } }, threadName); } 属性。在这种情况下,您可以像这样实现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)