Python os.walk 和日文文件名崩溃
可能是重复的问题:
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 个回答
对于那些写死在代码里的字符串,你需要在源文件的顶部 指定编码方式。如果是从其他地方输入的字节字符串,比如用 os.walk
获取的,你也需要说明这个字节字符串的编码方式(可以参考unutbu的回答)。
看起来到目前为止的所有回答都是来自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的绑定。