在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