Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/295.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Python中,在运行时将引用更改为函数_Python_Bytecode_Introspection - Fatal编程技术网

在Python中,在运行时将引用更改为函数

在Python中,在运行时将引用更改为函数,python,bytecode,introspection,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

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

考虑以下代码:

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

我该怎么做呢

一种可能的解决方案是按如下所述编辑字节码:
但是我想知道是否有更简单的方法。

函数有一个
func\u globals
属性。在这种情况下,您可以实现
补丁
,如下所示:

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

这比看起来要复杂得多。要做到这一点,您需要:

  • 使用
    \uuuu missing\uuuu
    方法创建一个
    dict
    子类,以便现在保存
    的新值。
    \uuuu missing\uuuu
    方法然后从通常的
    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()
确实没有必要在课堂上使用它,但我一直保持这种方式,因为这是您最初编写它的方式


如果
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()
你好,世界!
>>>otherfunc()
你好,世界!
>>>Sim(myfunc)
你好,世界!
>>>myfunc()
你好,当地人!
>>>otherfunc()
你好,世界!

有很多理由不这样做。如果
myfunc
访问的
now
功能与
Sim
不在同一模块中,则该功能将不起作用。它可能会破坏其他东西。另外,以这种方式使用一个简单的类实例化,比如
Sim(myfunc)
更改全局状态,这是非常有害的。

您可以使用
mock
包,该包已经完成了修补函数调用的许多艰苦工作。它是Python2(以及早期版本的Python3?)中的第三方安装,但它是Python3标准库的一部分,名为
unittest.mock
。它主要用于测试,但您可以在这里尝试使用它

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

这将导致对
now
的所有调用被替换,而不仅仅是从
myfunc
中对
now
的调用。错过了。编辑我的答案。这混淆了你想要修补的功能和你想要其他功能使用的功能。而且,它现在仍然替换模块中其他函数对
的调用。是的,你说得对。太匆忙了。当前编辑应能解决OP的问题。您的编辑不能解决问题
myfunc.func_globals
是对全局环境的引用,而不是它的副本。修改
myfunc.func\u globals
与直接修改
globals()
的结果具有相同的效果。在您的实际用例中,
现在和
myfunc
是否定义在同一个模块中?您是否关心现在
的其他用法是否受到影响?如果它们在不同的模块中,
myfunc
如何访问
now
功能?如果现在从somemodule导入
,所需的方法可能会有所不同。。。现在()。。。somemodule.now()
。是的,这很重要。我更新了示例以说明这一点。最终用户将从其代码中的库中导入全局
now
Sim
。我可以修改全局
now
,但我负担不起在全局
now
中查找本地版本的昂贵费用。这是否适用于更新代码中的两个不同的
Sim()
实例?看起来它正在修改原始的
myfunc
,并将修改以前的补丁。也许先深度复制
myfunc
,然后再进行修补就可以做到这一点?应该可以处理多个实例。显然,一次只能在全局中调用一个版本的
myfunc
,但是如果您通过
Sim
实例调用它(或者通过添加
\u call\u
方法使
Sim
可调用,或者只需调用
Sim.func()
),每个版本都使用自己的
myfunc
。您还可以推迟修补,直到调用实例。这非常有效。我只想指出,
globals()[funcname]=name
将更改原始的
myfunc
,因此它应该是可选的,具体取决于所需的结果。我个人刚刚把它注释掉,现在做我想做的事。
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