UnicodeDecodeError when redirecting to file
我在Ubuntu终端(编码设置为utf-8)中运行了这段代码两次,一次使用
1 2 | uni = u"\u001A\u0BC3\u1451\U0001D10C" print uni |
如果没有重定向,它将打印垃圾。通过重定向,我得到一个unicodedecode错误。有人能解释为什么只有在第二种情况下我才会出错,或者更好地详细解释在这两种情况下帘子后面发生了什么吗?
这种编码问题的关键是要理解原则上有两个不同的"字符串"概念:(1)字符串和(2)字符串/字节数组。这种区别在很长一段时间内都被忽略了,因为不超过256个字符(ascii,拉丁语-1,windows-1252,mac-os-roman,…)的编码在历史上无处不在:这些编码将一组通用字符映射到0到255之间的数字(即字节);在web mad出现之前,文件交换相对有限。e这种不兼容编码的情况是可以容忍的,因为大多数程序可以忽略这样一个事实,即只要它们生成的文本保持在同一操作系统上,就有多个编码:这样的程序只将文本视为字节(通过操作系统使用的编码)。基于以下两点,正确的现代观点正确地将这两个字符串概念分开:好的。
字符大多与计算机无关:例如,人们可以在黑板上绘制它们,等等????????"机器的"字符"还包括"绘图指令",例如空格、回车、设置书写方向的指令(阿拉伯语等)、重音符号等。Unicode标准中包括一个非常大的字符列表;它涵盖了大多数已知字符。好的。
另一方面,计算机确实需要以某种方式表示抽象字符:为此,它们使用字节数组(包括0到255之间的数字),因为它们的内存是字节块。将字符转换为字节的必要过程称为编码。因此,计算机需要编码来表示字符。您计算机上的任何文本都会被编码(直到显示出来),无论是发送到终端(需要以特定方式编码字符),还是保存到文件中。为了显示或正确地"理解"(例如,通过python解释器),字节流被解码为字符。一些编码(utf-8,utf-16,…)是由Unicode为其字符列表定义的(Unicode因此定义了字符列表和这些字符的编码在某些地方仍然可以看到表达式"Unicode编码"是指无处不在的utf-8,但这是不正确的术语,因为Unicode提供了多个编码)。好的。
总之,计算机需要在内部用字节表示字符,它们通过两个操作来实现这一点:好的。
Encoding: characters → bytes
Ok.
Decoding: bytes → characters
Ok.
某些编码不能对所有字符(例如,ASCII)进行编码,而(某些)Unicode编码允许您对所有Unicode字符进行编码。编码也不一定是唯一的,因为有些字符可以直接表示,也可以组合表示(例如,基字符和重音)。好的。
注意,换行的概念增加了一层复杂性,因为它可以由依赖于操作系统的不同(控制)字符表示(这是Python通用换行文件读取模式的原因)。好的。
现在,我上面所说的"字符"就是Unicode所说的"用户感知字符"。单用户感知的字符有时可以用Unicode表示,通过组合Unicode列表中不同索引处的字符部分(基本字符、重音符号等),这些代码点被称为"代码点"——这些代码点可以组合在一起形成"图形簇"。因此,Unicode导致了字符串的第三个概念,由一系列Unicode代码点组成,这些代码点位于字节和字符串之间,并且更接近后者。我将称它们为"unicode字符串"(就像在python中一样)。好的。
虽然python可以打印(用户感知的)字符字符串,但python非字节字符串本质上是Unicode代码点的序列,而不是用户感知的字符。代码点值是python的
这有一个重要的后果:python(unicode)字符串的长度是它的代码点数量,而不是它的用户感知字符数量:因此,尽管
在python 2中,unicode字符串称为"unicode字符串"(
有了这几个关键点,您应该能够理解大多数与编码相关的问题!好的。
通常,当您将
1 2 3 4 5 6 7 | % 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 |
如果您的输入字符可以使用终端的编码进行编码,那么Python将这样做,并将相应的字节发送到您的终端,而不会抱怨。然后,终端将尽最大努力在解码输入字节后显示字符(最坏情况下,终端字体没有某些字符,而是打印某种空白)。好的。
如果您的输入字符不能使用终端的编码进行编码,则意味着终端没有配置为显示这些字符。python会抱怨(在使用
但是,当您重定向或传输程序的输出时,通常不可能知道接收程序的输入编码是什么,上面的代码返回一些默认编码:none(python 2.7)或utf-8(python 3):好的。
1 2 3 4 | % python2.7 -c"import sys; print sys.stdout.encoding" | cat None % python3.4 -c"import sys; print(sys.stdout.encoding)" | cat UTF-8 |
但是,如果需要,可以通过
1 2 | % PYTHONIOENCODING=UTF-8 python2.7 -c"import sys; print sys.stdout.encoding" | cat UTF-8 |
如果打印到终端不能产生您期望的结果,您可以检查手动输入的UTF-8编码是否正确;例如,如果我没有弄错,您的第一个字符(
在http://wiki.python.org/moin/printfails,您可以找到一个类似于下面的解决方案,用于python 2.x:好的。
1 2 3 4 5 6 7 8 9 | 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总是对unicode字符串进行编码。当写入终端时,python通常可以确定终端的编码并正确使用它。当写入文件或管道时,除非另有明确说明,否则python默认为"ascii"编码。当输出通过
在您的情况下,您已经打印了4个不常见的字符,而您的终端在其字体中不支持这些字符。下面是一些例子来帮助解释这种行为,其中包含我的终端实际支持的字符(它使用CP437,而不是UTF-8)。
例1注意,
1 2 3 4 5 | #coding: utf8 import sys uni = u'α?ΓπΣσμτΦΘΩδ∞φ' print >>sys.stderr,sys.stdout.encoding print uni |
输出(直接从终端运行)
1 2 | cp437 α?ΓπΣσμτΦΘΩδ∞φ |
python正确地确定了终端的编码。
输出(重定向到文件)1 2 3 4 5 | 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个字符。
输出(重定向到文件,pythonioencoding=cp437)1 | cp437 |
我的输出文件是正确的:
1 2 | C:\>type out.txt α?ΓπΣσμτΦΘΩδ∞φ |
例2
现在,我将在源中插入一个终端不支持的字符:
1 2 3 4 5 | #coding: utf8 import sys uni = u'α?ΓπΣσμτΦΘΩδ∞φ马' # added Chinese character at end. print >>sys.stderr,sys.stdout.encoding print uni |
输出(直接从终端运行)
1 2 3 4 5 6 7 | 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> |
我的终端不懂最后一个汉字。
输出(直接运行,pythonioencoding=437:replace)1 2 | cp437 α?ΓπΣσμτΦΘΩδ∞φ? |
可以使用编码指定错误处理程序。在这种情况下,未知字符被
打印时对其进行编码
1 2 | uni = u"\u001A\u0BC3\u1451\U0001D10C" print uni.encode("utf-8") |
这是因为当您手动运行脚本时,python会在将其输出到终端之前对其进行编码,当您通过管道传输时,python不会对其本身进行编码,因此在执行I/O时必须手动进行编码。