在 运行 中更改对函数的引用-在 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
我需要在 运行 时间内更改对另一个函数内函数的调用。
考虑以下代码:
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