查看: 1789|回复: 3

UnicodeDecodeError when redirected to the file

[复制链接]

13

主题

53

帖子

131

积分

注册会员

Rank: 2

积分
131
发表于 2018-9-20 14:42:31 | 显示全部楼层 |阅读模式
我在Ubuntu终端(编码设置为utf-8)中运行此代码两次,一次使用./test.py,另一次使用./test.py >out.txt:
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
没有重定向它会输出一个图形。通过重定向,我得到一个UnicodeDecodeError。有人可以解释为什么我只在第二种情况下得到错误,或者甚至更好地详细解释两种情况下背后发生了什么?
回复

使用道具 举报

22

主题

107

帖子

266

积分

中级会员

Rank: 3Rank: 3

积分
266
发表于 2018-9-20 14:43:52 | 显示全部楼层
本帖最后由 马猴烧酒 于 2018-9-20 14:53 编辑

有两个截然不同的概念:(1)字符串的字符,(2)数组字节。这种区别在很长一段时间内都被忽略了,不超过256个字符(ASCII,Latin-1,Windows-1252,Mac OS Roman,...):这些编码将一组常见字符映射到0到255之间的数字(即字节),保留在同一操作系统上的文本,就会有多种编码:这样的程序只会简单将文本视为字节(通过操作系统使用的编码)。根据以下两点正确地分离了这两个字符串概念:
1.     字符大多与计算机无关:可以在粉笔板等上绘制它们,例如بايثون,中python和蛇图形。机器的“字符”还包括“绘图指令”,例如空格,回车,设置书写方向的指令(用于阿拉伯语等),重音符号等.Unicode标准中包含非常大的字符列表。它涵盖了大多数已知角色。
2.     另一方面,计算机确实需要以某种方式表示抽象字符:为此,它们使用字节数组(包括0到255之间的数字),因为它们的内存以字节块的形式出现。将字符转换为字节的必要过程称为编码。因此,计算机需要编码才能表示字符。计算机上存在的任何文本都会被编码(直到显示),无论是发送到终端(需要以特定方式编码的字符),还是保存在文件中。为了显示或正确地“理解”(例如,通过Python解释器),字节流被解码成字符。一些编码(UTF-8,UTF-16,...)由Unicode定义为其字符列表(Unicode因此定义了这些字符的字符列表和编码 - 仍然有人将表达式“Unicode编码”视为UTF-8,但这是不正确的,因为Unicode提供了多种编码)。
总之,计算机需要在内部用字节表示字符,它们通过两个操作来完成:
编码:字符→字节
解码:字节→字符
某些编码不能编码所有字符(例如ASCII),而(某些)Unicode编码允许你编码所有Unicode字符。编码也不一定是唯一的,因为一些字符可以直接表示或作为组合表示(例如,基本字符和重音符号)。
请注意,换行 的概念增加了一层复杂性,因为它可以由依赖于操作系统的不同(控制)字符表示(这是Python的通用换行文件读取模式的原因)。
现在,我所谓的“字符”就是Unicode所谓的“ 用户感知角色 ”。单个字符有时可以通过组合Unicode列表中不同索引处的字符部分(基本字符,重音符号......)来表示,这些字符部分称为“ 代码点 ” - 这些代码点可以组合在一起形成一个“字形集群”。Unicode因此导致字符串的第三个概念,由一系列Unicode代码点组成,位于字节和字符串之间,并且更接近后者。虽然Python可以打印字符串,但Python非字节字符串本质上是Unicode代码点的序列,而不是字符的序列。代码点值是Python \u和\UUnicode字符串语法中使用的值。它们不应与字符的编码混淆(这有一个重要的结果:PythonUnicode)字符串的长度是它的代码点数,它并不总是字符的数量:因此s ="\u1100\u1161\u11a8"; print(s, "len", len(s))(Python 3)각 len 3尽管s是一个韩语字符(因为它用3个代码点表示)。但是,在许多实际情况中,字符串的长度是其字符的数量,因为许多字符通常由Python存储为单个Unicode代码点。
Python 2,Unicode字符串被称为“Unicode字符串”(unicode类型,文字形式u"…"),而字节数组是“字符串”(str类型,其中字节数组可以例如用字符串文字构造"…")。在Python 3,Unicode字符串简称为“字符串”(str类型,文字形式"…"),而字节数组则是“字节”(bytes类型,文字形式b"…")。
通过这几个关键点,你应该能够理解大多数与编码相关的问题!
通常,当你打印 u"…" 到终端时,你不应该得到一个图形:Python知道终端的编码。实际上,你可以检查终端的编码:
% 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将这样做,并将相应的字节发送到你的终端而不会抱怨。然后终端将在解码输入字节后尽力显示字符(最坏的情况是终端字体没有一些字符,而是会打印某种空白)。
如果需要,可以通过环境变量设置 stdin,stdout和stderr的编码PYTHONIOENCODING:
% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8
你的第一个字符(\u001A)是不可打印的。

回复

使用道具 举报

10

主题

72

帖子

180

积分

注册会员

Rank: 2

积分
180
发表于 2018-9-20 14:56:16 | 显示全部楼层
Python在写入终端,文件,管道等时总是编码Unicode字符串。当写入终端时,Python通常可以确定终端的编码并正确使用它。当写入文件或管道时,Python默认为'ascii'编码。可以告诉Python在通过PYTHONIOENCODING环境变量输出管道时要做什么。shell可以在将Python输出重定向到文件或管道之前设置此变量,因此已知正确的编码。
在你的情况下,你已经打印了4个不常见的字符,你的终端不支持其字体。这里有一个例子来帮助解释行为,我的终端实际支持的字符(使用cp437,而不是UTF-8)。
例1
请注意,#coding注释指示保存源文件的编码。我选择了utf8所以我可以支持我的终端无法支持的源代码。编码重定向到stderr,以便在重定向到文件时可以看到它。
#coding: utf8
import sys
uni = u'αßΓπΣσµτΦΘΩδ∞φ'
print >>sys.stderr,sys.stdout.encoding
print uni
输出(直接从终端运行)
cp437
αßΓπΣσµτΦΘΩδ∞φ
Python正确地确定了终端的编码。
输出(重定向到文件)
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)
cp437
我的输出文件是正确的:
C:\>type out.txt
αßΓπΣσµτΦΘΩδ∞φ

回复

使用道具 举报

4

主题

31

帖子

86

积分

注册会员

Rank: 2

积分
86
发表于 2018-9-20 14:58:58 | 显示全部楼层
解释的这么详细的吗,有心啦
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表