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

时间:2010-09-24 18:34:51

标签: python windows unicode filesystems

  

可能重复:
  Python, Unicode, and the Windows console

我有一个文件名为“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 个答案:

答案 0 :(得分:7)

到目前为止,似乎所有答案都来自Unix人,他们认为Windows控制台就像Unix终端,但事实并非如此。

问题是您无法使用常规基础文件I / O功能将Unicode输出写入Windows控制台。需要使用Windows API WriteConsole。 Python可能应该透明地执行此操作,但事实并非如此。

如果将输出重定向到文件,则会出现另一个问题:Windows文本文件历来是ANSI代码页,而不是Unicode。你可以相当安全地将UTF-8写入Windows中的文本文件,但默认情况下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&gt;文件),那就不行了,我们需要将文件的编码改为UTF-8。

在任何一种情况下相反都行不通。您无法使用WriteConsole输出到常规文件(它实际上不是字节API,而是UTF-16; PyWin隐藏此详细信息),并且您无法将UTF-8写入Windows控制台。

此外,它应该使用_get_osfhandle来获取stdout和stderr的句柄,而不是假设它们被分配给标准句柄,但该API似乎没有任何PyWin绑定。

答案 1 :(得分:-2)

对于硬编码字符串,您需要specify the encoding at the top of source files。对于从其他来源输入的字节串 - 例如os.walk - ,您需要指定字节字符串的编码(请参阅unutbu的答案)。