我需要在运行时更改对另一个函数内部函数的调用。
请考虑以下代码:
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但我想知道是否有更简单的方法。
答案 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)