UnicodeDecodeErrorredirect到文件时

我在Ubuntuterminal(编码设置为utf-8)中运行这个代码片断两次,一次使用./test.py ,然后使用./test.py >out.txt

 uni = u"\u001A\u0BC3\u1451\U0001D10C" print uni 

没有redirect,它打印垃圾。 redirect,我得到一个UnicodeDecodeError。 有人可以解释为什么我只在第二种情况下才会出现错误,或者甚至可以更好地详细解释两种情况下的幕后情况?

这种编码问题的关键在于理解原则上有两个不同的“string”概念 :(1)string,(2)string/ 字节数组。 由于具有不超过256个字符(ASCII,Latin-1,Windows-1252,Mac OS Roman,…)的历史无处不在的编码,这种区别大部分被忽略了:这些编码将一组通用字符映射到0到255之间的数字(即字节); 在networking出现之前文件的相对有限的交换使得这种不兼容编码的情况可以容忍,因为大多数程序可以忽略这样一个事实,即只要它们产生了保留在同一操作系统上的文本,就有多种编码:将文本视为字节(通过操作系统使用的编码)。 正确的,现代的观点正确地分离了这两个string的概念,基于以下两点:

  1. angular色大多与计算机无关 :人们可以在粉笔板上画他们,比如بايثون,中蟒和🐍。 机器的“字符”还包括“绘图指令”,如空格,回车,设置书写方向的指令(用于阿拉伯语等),重音符号等。Unicode标准中包括一个非常大的字符列表 。 它涵盖了大部分已知的字符。

  2. 另一方面,计算机确实需要以某种方式表示抽象字符:为此,它们使用字节数组 (包括0到255之间的数字),因为它们的内存是以字节块的forms存在的。 将字符转换为字节的必要过程称为编码 。 因此,计算机需要编码才能表示字符。 计算机上显示的任何文本都被编码(直到显示出来),是否发送到terminal(期望以特定方式编码的字符),还是保存在文件中。 为了显示或正确“理解”(通过Python解释器),字节stream被解码为字符。 一些编码 (UTF-8,UTF-16,…)由Unicode定义为其字符列表(Unicode因此定义了这些字符的字符列表和编码 – 仍然有一些地方可以看到“Unicode编码“作为参考无处不在的UTF-8的一种方式,但是这是不正确的术语,因为Unicode提供了多种编码)。

总之, 计算机需要在内部用字节表示字符 ,并且通过两个操作来完成:

编码 :字符→字节

解码 :字节→字符

某些编码不能编码所有字符(例如ASCII),而(一些)Unicode编码允许编码所有Unicode字符。 编码也不一定是唯一的 ,因为一些字符可以直接表示或作为组合 (例如,基本字符和重音符号)来表示。

请注意, 换行符的概念增加了一层复杂性 ,因为它可以由依赖于操作系统的不同(控制)字符表示(这是Python的通用换行文件读取模式的原因 )。

现在,我所谓的“angular色”就是Unicode所谓的“ 用户感知angular色 ”。 一个单一的用户感知的字符有时可以用Unicode来表示,通过在Unicode列表中的不同索引处find的被称为“ 代码点 ”的字符部分(基本字符,重音,…) – 这些代码点可以组合在一起形成一个“字形集群”。 因此,Unicode导致了第三个string概念,由Unicode代码点序列组成,位于字节和string之间,与后者更接近。 我将称它们为“ Unicodestring ”(就像在Python 2中)。

虽然Python可以打印 (用户感知)string,但Python非字节string实际上是Unicode代码点的序列 ,而不是用户感知的字符。 代码点值是在Python的\u\U Unicodestring语法中使用的值。 他们不应该与字符的编码混淆(并且不必与它有任何关系:Unicode编码点可以以各种方式编码)。

具体而言,这意味着Python(Unicode)string的长度并不总是其用户感知字符的数量 :因此s = "\u1100\u1161\u11a8"; print(s, "len", len(s)) s = "\u1100\u1161\u11a8"; print(s, "len", len(s)) (Python 3)尽pipe有单个用户感知(韩文)字符(因为它用3个代码点表示,即使它不必,如print("\uac01")所示)。 但是,在许多实际情况下,string的长度是用户感知字符的数量,因为许多字符通常由Python作为单个Unicode代码点存储。

Python 2中 ,Unicodestring被称为…“Unicodestring”( unicodetypes,字面forms为u"…" ),而字节数组是“string”( strtypes,字节数组可以用string文字构造) "…" )。 在Python 3中 ,Unicodestring简单地称为“string”( strtypes,字面forms为"…" ),而字节数组为“字节”( bytestypes,字面forms为b"…" )。

有了这几个关键点,你应该能够理解大多数编码相关的问题!


通常情况下,当你将u"…" 打印 到terminal时 ,你不应该得到垃圾:Python知道你的terminal的编码。 实际上,您可以检查terminal所期望的编码:

 % python Python 2.7.6 (default, Nov 15 2013, 15:20:37) [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> print sys.stdout.encoding UTF-8 

如果你的input字符可以用terminal的编码进行编码,那么Python会这样做,并将相应的字节发送到你的terminal而不抱怨。 terminal将在解码完input字节后尽力显示字符(最坏的情况是terminal字体没有一些字符,而是会打印某种空白)。

如果您的input字符不能用terminal的编码进行编码,则意味着terminal没有configuration为显示这些字符。 Python会抱怨(在Python中UnicodeEncodeError因为string不能以适合您的terminal的方式进行编码)。 唯一可行的解​​决scheme是使用可以显示字符的terminal(通过configurationterminal,使其接受可以表示字符的编码或使用不同的terminal程序)。 当您分发可以在不同环境中使用的程序时,这一点很重要:您打印的消息应该可以在用户的​​terminal中performance出来。 有时最好坚持只包含ASCII字符的string。

但是,当您redirect或pipe道程序的输出时,通常不可能知道接收程序的input编码是什么,上面的代码返回一些默认的编码:无(Python 2.7)或UTF-8( Python 3):

 % python2.7 -c "import sys; print sys.stdout.encoding" | cat None % python3.4 -c "import sys; print(sys.stdout.encoding)" | cat UTF-8 

如果需要,stdin,stdout和stderr的编码可以通过PYTHONIOENCODING环境variables来设置 :

 % PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat UTF-8 

如果打印到terminal没有产生你所期望的,你可以检查你手动input的UTF-8编码是否正确; 例如, 如果我没有弄错 ,你的第一个字符( \u001A )是不可打印的。

欲了解更多信息: http : //wiki.python.org/moin/PrintFails 。 从这个链接你可以find这样的解决scheme,为Python 2.x:

 import codecs import locale import sys # Wrap sys.stdout into a StreamWriter to allow writing unicode. sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) uni = u"\u001A\u0BC3\u1451\U0001D10C" print uni 

对于Python 3,您可以检查之前在StackOverflow中提出的问题之一 。

Python在写入terminal,文件,pipe道等时总是编码Unicodestring。写入terminal时,Python通常可以确定terminal的编码并正确使用。 当写入文件或pipe道时,Python默认使用“ascii”编码,除非明确指出。 当通过PYTHONIOENCODING环境variablespipe道输出时,Python可以被告知该怎么做。 在将Python输出redirect到文件或pipe道之前,shell可以设置此variables,以便了解正确的编码。

在你的情况下,你已经打印了4个不常见的字符,你的terminal不支持字体。 下面是一些帮助解释行为的例子,其中terminal实际支持的字符(使用cp437,而不是UTF-8)。

例1

请注意, #coding编码注释表示源文件保存的编码。 我select了utf8,所以我可以支持源码中的字符,我的terminal不能。 编码redirect到stderr,以便redirect到文件时可以看到。

 #coding: utf8 import sys uni = u'αßΓπΣσµτΦΘΩδ∞φ' print >>sys.stderr,sys.stdout.encoding print uni 

输出(直接从terminal运行)

 cp437 αßΓπΣσµτΦΘΩδ∞φ 

Python正确地确定了terminal的编码。

输出(redirect到文件)

 None Traceback (most recent call last): File "C:\ex.py", line 5, in <module> print uni UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-13: ordinal not in range(128) 

Python无法确定编码(无),因此使用'ascii'默认值。 ASCII只支持转换Unicode的前128个字符。

输出(redirect到文件,PYTHONIOENCODING = cp437)

 cp437 

和我的输出文件是正确的:

 C:\>type out.txt αßΓπΣσµτΦΘΩδ∞φ 

例2

现在,我将在源码中引入一个不受我的terminal支持的字符:

 #coding: utf8 import sys uni = u'αßΓπΣσµτΦΘΩδ∞φ马' # added Chinese character at end. print >>sys.stderr,sys.stdout.encoding print uni 

输出(直接从terminal运行)

 cp437 Traceback (most recent call last): File "C:\ex.py", line 5, in <module> print uni File "C:\Python26\lib\encodings\cp437.py", line 12, in encode return codecs.charmap_encode(input,errors,encoding_map) UnicodeEncodeError: 'charmap' codec can't encode character u'\u9a6c' in position 14: character maps to <undefined> 

我的terminal不知道最后一个汉字。

输出(直接运行,PYTHONIOENCODING = 437:replace)

 cp437 αßΓπΣσµτΦΘΩδ∞φ? 

error handling程序可以用编码指定。 在这种情况下,未知的字符被replace为?ignorexmlcharrefreplace是一些其他选项。 使用UTF8(支持编码所有Unicode字符)时,将永远不会进行replace,但用于显示字符的字体仍必须支持。

在打印时对其进行编码

 uni = u"\u001A\u0BC3\u1451\U0001D10C" print uni.encode("utf-8") 

这是因为当你手动运行这个脚本时,python会在输出到terminal之前对它进行编码,当你pipe它的时候,python不会自己编码,所以你必须在做I / O时手动编码。