编码期间的UnicodeDecodeError?

时间:2013-06-04 12:37:28

标签: python unicode

我们遇到了一个问题(描述为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()构造函数时得到这个!!!

我认为根本原因在上述维基文章http://wiki.python.org/moin/UnicodeDecodeError中有所描述。请阅读第二段关于如何“矛盾的是,当编码 ... 时可能会发生UnicodeDecodeError”。 Wiki文章非常准确地描述了我们正在经历的事情 - 但是虽然它详细阐述了这些内容,但它没有提出解决方案的建议。

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

由于作为开发人员,我不习惯“无法从此处获取”信息,因此我认为有兴趣为Stack Overflow提供其他人的经验。

4 个答案:

答案 0 :(得分:4)

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

os.walk(".")将文件名(和目录名称等)作为当前代码页中编码的字符串返回。它将静默删除当前代码页中不存在的字符(see this question for a striking example)。

因此,如果您的文件/目录名称包含编码范围之外的字符,那么您肯定需要使用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字符串。

答案 1 :(得分:3)

for fname in files:
    filename = unicode(fname)

如果fname不是ASCII,第二行会投诉。如果要将字符串转换为Unicode,而不是unicode(fname),则应该fname.decode('<the encoding here>')

我建议使用编码,但是您没有告诉我们\xab文件中的.link是什么。你可以在谷歌搜索编码,所以它会像这样:

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

更新:例如, IF 文件系统名称的编码为ISO-8859-1,然后\ xab char为“«”。要将它读入python,你应该这样做:

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

希望这有帮助!

答案 2 :(得分:2)

据我了解,您的问题是os.walk(unicode_path)无法将某些文件名解码为Unicode。 Python 3.1+中修复了此问题(请参阅PEP 383: Non-decodable Bytes in System Character Interfaces):

  

文件名,环境变量和命令行参数   定义为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)

Python 3.3:

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

您的str2uni()函数尝试(引入模糊名称)来解决与Python 3上的“surrogateescape”错误处理程序相同的问题。如果您希望文件名无法解码,请在Python 2上使用bytestrings作为文件名使用sys.getfilesystemencoding()

答案 3 :(得分:1)

'\xab'

字节,编号171。

u'\xab'

字符,U + 00AB左指双角引号(«)。

u'\xab'是说u'\u00ab'的简便方法。它与字节'\xab'不同(甚至不是相同的数据类型);在Unicode字符串文字IMO中总是使用\u语法可能更清楚,但现在解决这个问题为时已晚。

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

>>> unicode('\xab')
UnicodeDecodeError

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

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

由于u'\xab'(或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()方法

这通常不是一个好主意。 UnicodeError是Python告诉你你误解了字符串类型的一些东西;忽略这个错误而不是在源头修复它意味着你更有可能隐藏稍后会咬你的微妙失败。

filename = unicode(fname)

如果你知道你的文件系统使用的是ISO-8859-1文件名,那么用filename = unicode(fname, '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文件系统编码会更好,因此您可以包含任何字符。