如何将带有无效UTF-8字符的文件名转换回字节?

时间:2015-03-14 13:24:07

标签: python python-3.x unicode utf-8 filesystems

如何将os.listdir的输出转换为bytes列表(来自Unicode str列表)?即使文件名是无效的UTF-8,它也必须工作,例如:

$ locale
LANG=
LANGUAGE=
LC_CTYPE=en_US.UTF-8
LC_NUMERIC="POSIX"
LC_TIME="POSIX"
LC_COLLATE="POSIX"
LC_MONETARY="POSIX"
LC_MESSAGES="POSIX"
LC_PAPER="POSIX"
LC_NAME="POSIX"
LC_ADDRESS="POSIX"
LC_TELEPHONE="POSIX"
LC_MEASUREMENT="POSIX"
LC_IDENTIFICATION="POSIX"
LC_ALL=
$ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:11) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> open(b'\x80', 'w')
<_io.TextIOWrapper name=b'\x80' mode='w' encoding='UTF-8'>
>>> os.listdir('.')
['\udc80']
>>> import sys
>>> [fn.encode(sys.getfilesystemencoding()) for fn in os.listdir('.')]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
UnicodeEncodeError: 'utf-8' codec can't encode character '\udc80' in position 0: surrogates not allowed
>>> [... for fn in os.listdir('.')]
[b'\x80']

那么我需要写上面的...以使其有效?

请注意,在这种情况下,不能重命名文件,使用Python 2.x或使用纯ASCII文件名。我不是在寻找解决方法,我正在寻找代码来代替...

3 个答案:

答案 0 :(得分:4)

使用错误处理程序;在这种情况下,surrogateescape错误处理程序看起来合适:

  

价值: 'surrogateescape'
  含义: On decoding, replace byte with individual surrogate code ranging from U + DC80 to U + DCFF . This code will then be turned back into the same byte when the&#39; surrogateescape&#39;`编码数据时使用错误处理程序。 (有关详情,请参阅PEP 383。)

os.fsencode() utility function使用后一种选择;当适用于您的操作系统时,它使用代理转义错误处理程序编码为sys.getfilesystemencoding()

  

使用'surrogateescape'错误处理程序将文件名编码为文件系统编码,或在Windows上编译'strict';返回bytes不变。

实际上,只有当文件系统编码为'strict'时,它才会使用mbcs,请参阅os module source,这是一种仅适用于Windows的编解码器。

演示:

>>> import sys
>>> ld = ['\udc80']
>>> [fn.encode(sys.getfilesystemencoding()) for fn in ld]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
UnicodeEncodeError: 'utf-8' codec can't encode character '\udc80' in position 0: surrogates not allowed
>>> [fn.encode(sys.getfilesystemencoding(), 'surrogateescape') for fn in ld]
[b'\x80']
>>> import os
>>> [os.fsencode(fn) for fn in ld]
[b'\x80']

答案 1 :(得分:3)

>>> [os.fsencode(fn) for fn in os.listdir('.')]
[b'\x80']

另一方面也有相应的os.fsdecode转换。

文档:https://docs.python.org/3/library/os.html#os.fsencode

答案 2 :(得分:3)

如果您只想要来自os.listdir的文件名(以字节为单位),则它具有该选项。来自docs

  

路径可以是str类型,也可以是bytes类型。如果 path 的类型为bytes,则返回的文件名也将为bytes;在所有其他情况下,它们将是str类型。