2

我们遇到了一个问题(描述为http://wiki.python.org/moin/UnicodeDecodeError)——阅读第二段“......自相矛盾......”。

具体来说,我们正在尝试将字符串上转换为 unicode,并且我们收到了 UnicodeDecodeError。

例子:

   >>> unicode('\xab')
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   UnicodeDecodeError: 'ascii' codec can't decode byte 0xab in position 0: ordinal not in range(128)

但是,当然,这没有任何问题

   >>> unicode(u'\xab')
   u'\xab'

当然,这段代码是为了演示转换问题。在我们的实际代码中,我们没有使用字符串字面量,我们不能只在 unicode 'u' 前缀前面加上,而是处理从 os.walk() 返回的字符串,文件名包含上述值. 由于我们无法在不调用 unicode() 构造函数的情况下将值强制为 unicode,因此我们不确定如何继续。

发生的一个非常可怕的黑客攻击是编写我们自己的 str2uni() 方法,例如:

def str2uni(val):
    r"""brute force coersion of str -> unicode"""
    try:
        return unicode(src)
    except UnicodeDecodeError:
        pass
    res = u''
    for ch in val:
       res += unichr(ord(ch))
    return res

但在我们这样做之前——想看看其他人是否有任何见解?

更新

我看到每个人都在关注我如何获得我发布的示例,而不是结果。叹息——好的,这是导致我花费数小时将问题简化为我上面分享的最简单形式的代码。

for _,_,files in os.walk('/path/to/folder'):
    for fname in files:
        filename = unicode(fname)

当文件名具有以下值 '3\xab Floppy (A).link' 时,该代码会抛出 UnicodeDecodeError 异常

要亲自查看错误,请执行以下操作:

   >>> unicode('3\xab Floppy (A).link')
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   UnicodeDecodeError: 'ascii' codec can't decode byte 0xab in position 1: ordinal not in range(128)

更新

我真的很感谢每个试图提供帮助的人。我也很欣赏大多数人犯了一些与字符串/unicode处理相关的非常简单的错误。但我想强调对UnicodeDecodeError异常的引用。我们在调用 unicode() 构造函数时得到了这个!!!

我相信前面提到的 Wiki 文章http://wiki.python.org/moin/UnicodeDecodeError中描述了根本原因。从第二段开始阅读“自相矛盾的是,编码时可能会发生 UnicodeDecodeError ...”。Wiki 文章非常准确地描述了我们正在经历的事情——但是虽然它详细说明了原因,但它没有对解决方案提出任何建议。

事实上,第三段开始于以下令人震惊的承认“与 UnicodeEncodeError 的类似情况不同,这种失败不能总是避免......”

由于作为开发人员,我不习惯“无法从这里到达那里”的信息,因此我认为有兴趣在 Stack Overflow 上寻找其他人的经验。

4

4 回答 4

4

我认为您混淆了 Unicode 字符串和 Unicode 编码(如 UTF-8)。

os.walk(".")将文件名(和目录名等)作为在当前代码页中编码的字符串返回。它将默默地删除 当前代码页中不存在的字符(请参阅此问题以获取引人注目的示例)。

因此,如果您的文件/目录名称包含编码范围之外的字符,那么您肯定需要使用 Unicode 字符串来指定起始目录,例如通过调用os.walk(u"."). 然后你不需要(也不应该)unicode()再调用结果,因为它们已经Unicode 字符串。

如果你不这样做,你首先需要解码文件名(如mystring.decode("cp850")),它会给你一个 Unicode 字符串:

>>> "\xab".decode("cp850")
u'\xbd'

然后您可以将其编码为 UTF-8 或任何其他编码。

>>> _.encode("utf-8")
'\xc2\xbd'

如果您仍然对为什么unicode("\xab")会引发解码错误感到困惑,也许以下解释会有所帮助:

"\xab"是一个编码字符串。Python 无法知道是哪种编码,但在将其转换为 Unicode 之前,需要先对其进行解码。没有您的任何规范,unicode()假设它是用 ASCII 编码的,并且当它在这个假设下尝试对其进行解码时,它会失败,因为\xab它不是 ASCII 的一部分。因此,要么您需要找出文件系统正在使用哪种编码并调用unicode("\xab", encoding="cp850")或其他什么,要么首先从 Unicode 字符串开始。

于 2013-06-04T12:49:34.500 回答
3
for fname in files:
    filename = unicode(fname)

fname如果不是 ASCII ,第二行会报错。如果您想将字符串转换为 Unicode,而不是unicode(fname)您应该执行fname.decode('<the encoding here>').

我会建议编码,但你没有告诉我们\xab你的.link文件中有什么。无论如何,您都可以在 google 中搜索编码,所以它会保持这样的状态:

for fname in files:
    filename = fname.decode('<encoding>')

更新:例如,如果您的文件系统名称的编码是ISO-8859-1,那么 \xab char 将是“«”。要将其读入python,您应该这样做:

for fname in files:
    filename = fname.decode('latin1') #which is synonym to #ISO-8859-1

希望这可以帮助!

于 2013-06-04T13:26:08.200 回答
2

据我了解,您的问题是os.walk(unicode_path)无法将某些文件名解码为 Unicode。此问题已在 Python 3.1+ 中修复(请参阅PEP 383:系统字符接口中的不可解码字节):

文件名、环境变量和命令行参数在 POSIX 中被定义为字符数据;然而,C API 允许传递任意字节——无论这些字节是否符合某种编码。该 PEP 提出了一种处理此类不规则性的方法,方法是将字节嵌入字符串中,从而允许重新创建原始字节字符串。

Windows 提供了 Unicode API 来访问文件系统,所以不应该有这个问题。

Python 2.7(Linux 上的 utf-8 文件系统):

>>> import os
>>> list(os.walk("."))
[('.', [], ['\xc3('])]
>>> list(os.walk(u"."))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/os.py", line 284, in walk
    if isdir(join(top, name)):
  File "/usr/lib/python2.7/posixpath.py", line 71, in join
    path += '/' + b
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: \
    ordinal not in range(128)

蟒蛇 3.3:

>>> import os
>>> list(os.walk(b'.'))
[(b'.', [], [b'\xc3('])]
>>> list(os.walk(u'.'))
[('.', [], ['\udcc3('])]

您的str2uni()函数尝试(它引入了模棱两可的名称)来解决与 Python 3 上的“surrogateescape”错误处理程序相同的问题。如果您期望文件名无法使用sys.getfilesystemencoding().

于 2013-06-04T20:59:34.057 回答
1
'\xab'

是一个字节,编号 171。

u'\xab'

是一个字符,U+00AB 指向左的双角引号(«)。

u'\xab'是一种简写的说法u'\u00ab'。它与 byte 不同(甚至不同的数据类型)'\xab';始终使用 Unicode 字符串文字 IMO 中的语法可能会更清楚\u,但现在修复它为时已晚。

从字节到字符被称为解码操作。从字符到字节的转换称为编码操作。对于任何一个方向,您都需要知道使用哪种编码在两者之间进行映射。

>>> unicode('\xab')
UnicodeDecodeError

unicode是一个字符串,所以当你将字节传递给unicode()构造函数时会有一个隐式的解码操作。如果你不告诉它你想要哪种编码,你会得到默认编码,通常是ascii. ASCII 对字节 171 没有意义,因此您会收到错误消息。

>>> unicode(u'\xab')
u'\xab'

由于u'\xab'(or u'\u00ab') 已经是一个字符串,因此在将它传递给unicode()构造函数时没有隐式转换 - 你得到一个未更改的副本。

res = u''
for ch in val:
   res += unichr(ord(ch))
return res

将每个输入字节映射到具有相同序数值的 Unicode 字符的编码是 ISO-8859-1。因此,您可以将这个循环替换为:

return unicode(val, 'iso-8859-1')

(但请注意,如果 Windows 混合使用,那么您想要的编码可能不是那种编码,而是有点相似的编码windows-1252。)

发生的一个非常可怕的黑客攻击是编写我们自己的 str2uni() 方法

这通常不是一个好主意。UnicodeErrors 是 Python 告诉你你对字符串类型有误解;忽略该错误而不是从源头修复它意味着您更有可能隐藏稍后会咬您的细微故障。

filename = unicode(fname)

因此,最好将其替换为:filename = unicode(fname, 'iso-8859-1')如果您知道您的文件系统正在使用 ISO-8859-1 文件名。如果您的系统语言环境设置正确,那么应该可以找出您的文件系统正在使用的编码,然后直接进入:

filename = unicode(fname, sys.getfilesystemencoding())

尽管实际上如果设置正确,您可以通过要求 Python 将文件系统路径视为本机 Unicode 而不是字节字符串来跳过所有的编码/解码大惊小怪。您可以通过将 Unicode 字符串传递到os文件名接口来做到这一点:

for _,_,files in os.walk(u'/path/to/folder'): # note u'' string
    for fname in files:
        filename = fname  # nothing more to do!

PS。中的字符3″ Floppy实际上应该是 U+2033 Double Prime,但在 ISO-8859-1 中没有编码。从长远来看,最好使用 UTF-8 文件系统编码,这样您就可以包含任何字符。

于 2013-06-04T19:55:28.397 回答