自我修改的Python?如何在不修改sys.stdout的情况下重定向函数内的所有打印语句?
我遇到一个情况,想把一些复杂的Python程序移植到多线程环境中。
我希望能在每次调用时,把函数里的print
输出重定向到别的地方,具体来说是一个logging.Logger
。
我真的不想改动我正在编译的代码,因为我需要保持与其他调用这些模块的软件的兼容性(那些软件是单线程的,输出是通过简单地抓取sys.stdout
里的内容来获取的)。
我知道最好的办法是重写一些东西,但我在这里真的没有选择。
编辑 -
另外,有没有办法可以覆盖本地的print定义,让它指向一个不同的函数呢?
这样我就可以定义本地的print = 系统的print,除非通过一个关键字参数被覆盖,这样只需要在每个程序的开头修改几行代码。
3 个回答
在六十年代,有人想出了一个解决这个问题的办法,但需要一些外星科技。可惜的是,Python 没有“当前环境”这个概念,这意味着你不能提供上下文,除非在调用时把它作为参数指定。
为了处理这个特定的问题,为什么不把标准输出(stdout)替换成一个根据线程特定上下文来工作的类似文件的对象呢?这样,源代码保持不变,但比如说,你可以为每个线程获得一个单独的日志。其实在每次调用时做到这一点也很简单……例如:
class MyFakeStdout:
def write(self, s):
try:
separate_logs[current_thread()].write(s)
except KeyError:
old_stdout.write(s)
然后可以有一个函数,用于在调用时本地设置一个日志记录器(with
)。
顺便说一下,我看到标题里有“没有触碰 stdout”,但我觉得这是因为你只想让某个线程受到影响。触碰它的同时让其他线程不受影响,在我看来是和问题相符的。
修改源代码并不一定意味着会破坏向后兼容性。
首先,你需要把每个打印语句替换成一个调用函数的方式,这个函数的作用和打印语句是一样的:
import sys
def _print(*args, **kw):
sep = kw.get('sep', ' ')
end = kw.get('end', '\n')
file = kw.get('file', sys.stdout)
file.write(sep.join(args))
file.write(end)
def foo():
# print "whatever","you","want"
_print("whatever","you","want")
接下来,第二步是停止直接使用这个_print函数,而是把它变成一个关键字参数:
def foo(_print=_print):
...
同时要确保把所有内部函数调用都改成传递这个_print函数。
这样,现有的代码依然可以正常工作,并且会使用打印功能,但你可以传入任何你想要的_print函数。
需要注意的是,_print函数的参数和最近版本的Python中的print函数是完全一样的,所以一旦你升级,就可以直接改成使用print()
。另外,你也可以使用2to3工具来迁移现有代码中的打印语句,这样可以减少需要编辑的内容。
在Python2.6(和2.7)中,你可以使用
from __future__ import print_function
然后你可以把代码改成用print()
这个函数,就像在Python3中那样使用
这样你就可以创建一个模块级别的全局或局部函数,叫做print,这个函数会优先于内置的print函数被使用
比如:
from __future__ import print_function
def f(x, print=print):
print(x*x)
f(5)
L=[]
f(6, print=L.append)
print(L)