Python 2.7与3.6兼容的捕获stdout上下文管理器报错问题
这个问题的核心在于Python 2和3对字符串类型、文件IO模式的处理差异——你用了Python 3风格的open(..., encoding='utf8')在Python 2中完全不生效:Python 2的内置open()根本不支持encoding参数,它默认以字节模式打开文件,这就导致你写入Unicode字符串时触发类型错误。
下面是我整理的兼容两个版本的完整实现,一步步拆解问题:
1. 版本适配的文件打开方式
- Python 2:必须用
codecs.open()来指定编码,打开文本模式的文件,直接支持Unicode写入 - Python 3:内置
open()支持encoding参数,直接用即可
2. 替换sys.stdout的正确姿势
Python 2的sys.stdout是字节流对象,我们需要替换成一个能处理Unicode的包装类;Python 3的sys.stdout本身是文本流,直接替换成我们的文件对象即可(或者做一层简单包装保证一致性)。
完整兼容代码
import sys import codecs from contextlib import contextmanager @contextmanager def capture_stdout_to_file(log_file): # 保存原始stdout original_stdout = sys.stdout file_writer = None try: # 根据Python版本选择文件打开方式 if sys.version_info.major == 2: # Python 2用codecs.open打开带编码的文本文件 file_writer = codecs.open(log_file, 'w', encoding='utf-8') # 替换sys.stdout为能处理Unicode的对象 sys.stdout = file_writer else: # Python 3直接用内置open,指定编码 file_writer = open(log_file, 'w', encoding='utf-8') sys.stdout = file_writer yield finally: # 恢复原始stdout并关闭文件 sys.stdout = original_stdout if file_writer: file_writer.close() # 测试用例 if __name__ == "__main__": with capture_stdout_to_file("test.log"): # Python 2中用u"",Python 3中可以直接写字符串 print(u"测试Unicode字符串") print("普通字符串")
为什么你的原始代码报错?
在Python 2中,你调用open(self.log_file, 'w', encoding='utf8')时,encoding参数会被直接忽略(因为Python 2的open不支持这个参数),文件实际上是以字节模式打开的。当你执行print(u"a test string")时,print会把Unicode字符串传递给stdout的write()方法,但字节模式的文件只接受字节串(Python 2中的str是字节),所以触发write() argument 1 must be unicode, not str的错误——这里的提示有点误导人,实际是字节模式文件需要字节,你传了Unicode。
额外优化:统一处理字符串转换
如果你想让代码更健壮,可以在包装类中统一处理字符串类型转换,避免因输入字符串类型不一致报错:
# 针对Python 2的stdout包装类 class UnicodeStdoutWrapper(object): def __init__(self, file_obj): self.file_obj = file_obj def write(self, data): # 如果是字节串,先解码成Unicode;如果已经是Unicode直接写入 if isinstance(data, str): data = data.decode('utf-8') self.file_obj.write(data) def flush(self): self.file_obj.flush() # 在上下文管理器中替换为这个包装类(Python 2部分) if sys.version_info.major == 2: file_writer = codecs.open(log_file, 'w', encoding='utf-8') sys.stdout = UnicodeStdoutWrapper(file_writer)
这样不管传入的是字节串还是Unicode,都能正确写入文件。
内容的提问来源于stack exchange,提问作者Florian Dietz

