Python 2 和 3 兼容的字符串文件编写器

Python 2 and 3 compatible string file writer

我创建了一个 python 上下文管理器,它捕获所有输出 sys.stdout,例如使用 print(),并将其写入文件。

问题是我无法让它同时适用于 python 2.7 和 3.6。

上下文管理器内部使用

self.file_writer = open(self.log_file, 'w', encoding='utf8')

但是当我 运行 它在 Python 2.7 中时,那么

print(u"a test string")

导致错误消息:

write() argument 1 must be unicode, not str

即使字符串显然是 unicode。

如果我将文件更改为

self.file_writer = open(self.log_file, 'wb')

那么它在 Python 2.7 中有效,但在 3.6 中无效。

我需要做什么才能使其适用于任何 python 版本?

以下为经理摘录:

PATH_PREFIX = "some/path/"
class manager:
    def __init__(self):
        self.log_file = os.path.join(PATH_PREFIX, 'log.txt')
    def __enter__(self):
        # create a file for logging
        self.log_file_stream = open(self.log_file, 'w', encoding='utf8')
        self.log_file_stream.__enter__()
        # redirect stdout to this file
        self.previous_stdout = sys.stdout
        sys.stdout = self.log_file_stream
        return self
    def __exit__(self, etype, value, exception_traceback):
        # stop redirecting stdout to the log file
        sys.stdout = self.previous_stdout
        # close the log file
        self.log_file_stream.__exit__()

sys.stdout 在 Python 2 中应该是一个字节流,但是在 Python 3 中是一个 Unicode 流。 print for Python 2 编码在写入 stdout 之前将 Unicode 字符串转换为字节字符串,但您已将 sys.stdout 覆盖为 Python 2 和 Python 3 中的 Unicode 流。

覆盖sys.stdout时,您需要为Python 2提供字节流,但为Python 3提供Unicode流。您可以使用sys.version_info.major来决定支持哪一个。

阅读其他答案并了解到 python 的现有库无法做到这一点后,我编写了一个上下文管理器来替换 open()。这有点 hacky,但它完成了工作。

它可能可以通过显式检查 python 版本而不是使用 hasattr() 来改进,但我在写这篇文章时并不知道这一点:

class open_properly(object):
    """
    This is a file writer that doesn't complain about unicode/string mismatches.
    It works for both python 2.7 and 3+.
    """
    def __init__(self, file_path):
        super(open_properly, self).__init__()
        self.file_path = file_path
    def __enter__(self):
        self.file_writer = open(self.file_path, 'w', encoding='utf8')
        self.file_writer.__enter__()
        return self
    def write(self, s):
        if not isinstance(s, string_types):
            raise ValueError("the text to print must be a valid string type")
        # make sure it's unicode (important for python 2)
        if isinstance(s, str):
            if hasattr(s, 'decode'):
                s = s.decode('utf-8')
        # write unicode to file
        self.file_writer.write(s)
    def __exit__(self, exc_type, exc_val, exc_tb):
        return self.file_writer.__exit__(exc_type, exc_val, exc_tb)