自我修改的Python?如何在不修改sys.stdout的情况下重定向函数内的所有打印语句?

0 投票
3 回答
1343 浏览
提问于 2025-04-16 09:16

我遇到一个情况,想把一些复杂的Python程序移植到多线程环境中。

我希望能在每次调用时,把函数里的print输出重定向到别的地方,具体来说是一个logging.Logger

我真的不想改动我正在编译的代码,因为我需要保持与其他调用这些模块的软件的兼容性(那些软件是单线程的,输出是通过简单地抓取sys.stdout里的内容来获取的)。

我知道最好的办法是重写一些东西,但我在这里真的没有选择。

编辑 -

另外,有没有办法可以覆盖本地的print定义,让它指向一个不同的函数呢?

这样我就可以定义本地的print = 系统的print,除非通过一个关键字参数被覆盖,这样只需要在每个程序的开头修改几行代码。

3 个回答

2

在六十年代,有人想出了一个解决这个问题的办法,但需要一些外星科技。可惜的是,Python 没有“当前环境”这个概念,这意味着你不能提供上下文,除非在调用时把它作为参数指定。

为了处理这个特定的问题,为什么不把标准输出(stdout)替换成一个根据线程特定上下文来工作的类似文件的对象呢?这样,源代码保持不变,但比如说,你可以为每个线程获得一个单独的日志。其实在每次调用时做到这一点也很简单……例如:

class MyFakeStdout:
    def write(self, s):
        try:
            separate_logs[current_thread()].write(s)
        except KeyError:
            old_stdout.write(s)

然后可以有一个函数,用于在调用时本地设置一个日志记录器(with)。

顺便说一下,我看到标题里有“没有触碰 stdout”,但我觉得这是因为你只想让某个线程受到影响。触碰它的同时让其他线程不受影响,在我看来是和问题相符的。

3

修改源代码并不一定意味着会破坏向后兼容性。

首先,你需要把每个打印语句替换成一个调用函数的方式,这个函数的作用和打印语句是一样的:

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工具来迁移现有代码中的打印语句,这样可以减少需要编辑的内容。

6

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

撰写回答