Python os.walk 和日文文件名崩溃

3 投票
2 回答
2293 浏览
提问于 2025-04-16 04:34

可能是重复的问题:
Python、Unicode和Windows控制台

我有一个文件夹,里面有一个文件,名字叫“01 - ナナナン塊.txt”。

我在和这个文件同一个文件夹里打开了Python的交互式命令行,想要查看文件夹的结构:

Python 3.1.2 (r312:79149, Mar 21 2010, 00:41:52) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> for x in os.walk('.'):
...     print(x)
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "C:\dev\Python31\lib\encodings\cp850.py", line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_map)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 17-21: character maps to <undefined>

显然,我现在使用的编码方式无法处理日文字符。没关系。但我了解到Python 3.1应该是完全支持Unicode的,所以我不知道该怎么解决这个问题。有没有人有好的建议?

2 个回答

-2

对于那些写死在代码里的字符串,你需要在源文件的顶部 指定编码方式。如果是从其他地方输入的字节字符串,比如用 os.walk 获取的,你也需要说明这个字节字符串的编码方式(可以参考unutbu的回答)。

7

看起来到目前为止的所有回答都是来自Unix系统的人,他们认为Windows的控制台和Unix的终端是一样的,其实并不是。

问题在于,使用普通的文件输入输出功能无法在Windows控制台上写入Unicode输出。我们需要使用Windows的API函数WriteConsole。理论上,Python应该能够自动处理这些事情,但实际上并没有做到。

如果你把输出重定向到一个文件,那又是另一个问题:Windows的文本文件历史上是用ANSI编码,而不是Unicode。现在在Windows上写UTF-8格式的文本文件是相对安全的,但Python默认并不会这样做。

我认为Python应该处理这些问题,不过这里有一些代码可以帮你实现。如果你不想担心细节,只需调用ConsoleFile.wrap_standard_handles()就可以了。你需要安装PyWin才能访问必要的API。

import os, sys, io, win32api, win32console, pywintypes

def change_file_encoding(f, encoding):
    """
    TextIOWrapper is missing a way to change the file encoding, so we have to
    do it by creating a new one.
    """

    errors = f.errors
    line_buffering = f.line_buffering
    # f.newlines is not the same as the newline parameter to TextIOWrapper.
    # newlines = f.newlines

    buf = f.detach()

    # TextIOWrapper defaults newline to \r\n on Windows, even though the underlying
    # file object is already doing that for us.  We need to explicitly say "\n" to
    # make sure we don't output \r\r\n; this is the same as the internal function
    # create_stdio.
    return io.TextIOWrapper(buf, encoding, errors, "\n", line_buffering)


class ConsoleFile:
    class FileNotConsole(Exception): pass

    def __init__(self, handle):
        handle = win32api.GetStdHandle(handle)
        self.screen = win32console.PyConsoleScreenBufferType(handle)
        try:
            self.screen.GetConsoleMode()
        except pywintypes.error as e:
            raise ConsoleFile.FileNotConsole

    def write(self, s):
        self.screen.WriteConsole(s)

    def close(self): pass
    def flush(self): pass
    def isatty(self): return True

    @staticmethod
    def wrap_standard_handles():
        sys.stdout.flush()
        try:
            # There seems to be no binding for _get_osfhandle.
            sys.stdout = ConsoleFile(win32api.STD_OUTPUT_HANDLE)
        except ConsoleFile.FileNotConsole:
            sys.stdout = change_file_encoding(sys.stdout, "utf-8")

        sys.stderr.flush()
        try:
            sys.stderr = ConsoleFile(win32api.STD_ERROR_HANDLE)
        except ConsoleFile.FileNotConsole:
            sys.stderr = change_file_encoding(sys.stderr, "utf-8")

ConsoleFile.wrap_standard_handles()

print("English 漢字 Кири́ллица")

这有点复杂:如果标准输出(stdout)或标准错误(stderr)是控制台,我们需要用WriteConsole来输出;但如果不是(比如说foo.py > file),那就不行了,我们需要把文件的编码改成UTF-8。

在这两种情况下,反过来都是不行的。你不能用WriteConsole输出到普通文件(它实际上不是一个字节API,而是一个UTF-16的API;PyWin隐藏了这个细节),而且你也不能在Windows控制台上写UTF-8。

另外,实际上应该使用_get_osfhandle来获取标准输出和标准错误的句柄,而不是假设它们被分配给标准句柄,但这个API似乎没有PyWin的绑定。

撰写回答