为什么 sys.stdout.write 被调用了两次?

Why is sys.stdout.write called twice?

我试图将我所有的打印记录到日志文件中(并在实际消息之前添加时间戳,仅在日志文件中),但我无法弄清楚为什么 sys.stdout.write 被调用两次。

import sys
from datetime import datetime

class Logger(object):
    def __init__(self, filename="Default.log"):
        self.stdout = sys.stdout
        self.log = open(filename, "w")

    def write(self, message):
        self.stdout.write(message)
        time_stamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
        self.log.write(f"{time_stamp} - {message}")
        self.log.flush()
    
    def flush(self):
        pass

sys.stdout = Logger("log_file.txt")
print("Hello world !")

终端输出:

Hello World !

log_file.txt中的输出:

2021-04-19 18:43:14.800691 - Hello world !2021-04-19 18:43:14.800735 - 

我在这里错过了什么? write方法是在self.log.flush()调用过后再次调用的,但是这里是message=''.

如果我忽略 time_stamp 变量,它就像一个魅力,例如调用 self.log.write(message).

我当然可以只检查消息是否为空,但我真的很想在这里了解我的问题!

我的解决方案

@iBug 给了我答案!我不知道 print 确实以这种方式工作:一次写调用数据,一次写调用 end 关键字,默认为 \n

我的解决方案是在Logger class 中添加一个变量self._hidden_end,初始化为0,然后在每次调用write 时切换以检查它我应该是否在写入日志之前添加时间戳。

import sys
from datetime import datetime

class Logger(object):
    def __init__(self, filename="Default.log"):
        self.stdout = sys.stdout
        self.log = open(filename, "w")
        self._hidden_end = 0

    def write(self, message):
        self.stdout.write(message)
        if not self._hidden_end:
            time_stamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
            message = f"{time_stamp} - {message}"
        self._hidden_end ^= 1
        self.log.write(message)
        self.log.flush()
    
    def flush(self):
        pass

sys.stdout = Logger("log_file.txt")
print("Hello world !")

欢迎对我的上述解决方案提出任何想法! :)

您的感知结果表明 print() calls file.write twice: Once for the data and once for the "end content", which is a newline by default and can be overridden with the end keyword argument. Relevant source code lines 证实了这一推测。

但是,这不是您应该依赖的东西。它完全是实现细节,对于另一个实现(例如 PyPy)可能有所不同,并且可能随时更改,恕不另行通知,更不用说覆盖内置函数是另一种不好的做法。如果没有令人信服的理由,您应该避免这种做法不要修改代码的其他部分以使用您的自定义日志记录工具。

如果您真的需要进行猴子修补,那么覆盖 print() 函数会更安全,因为它具有已定义的可靠接口。您可以导入 builtins module 并将新的打印函数分配给 builtins.print,从而保持对对日志记录处理程序的调用的控制。