重定向到文件时发生UnicodeDecodeError [英] UnicodeDecodeError when redirecting to file

查看:21
本文介绍了重定向到文件时发生UnicodeDecodeError的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在Ubuntu终端(编码设置为utf-8)中运行了这段代码两次,第一次是使用./test.py,然后是./test.py >out.txt

uni = u"u001Au0BC3u1451U0001D10C"
print uni

没有重定向,它会打印垃圾。使用重定向时,我会收到UnicodeDecodeError。有人能解释一下为什么我只在第二种情况下得到错误,或者更好地给出两种情况下幕后发生的事情的详细解释吗?

推荐答案

此类编码问题的整个关键是要了解,原则上有两个截然不同的概念:(1)字符的字符串和(2)字节的字符串/数组。这一区别长期以来大多被忽视,因为历史上普遍存在不超过256个字符的编码(ASCII、拉丁文-1、Windows-1252、Mac OS Roman、…):这些编码将一组公共字符映射到0到255之间的数字(即字节);在Web出现之前相对有限的文件交换使得这种不兼容编码的情况是可以容忍的,因为大多数程序可以忽略这样一个事实,即存在多种编码,只要它们产生保留在同一操作系统上的文本:这样的程序将简单地将文本视为字节(通过操作系统使用的编码)。正确的、现代的视图基于以下两点将这两个字符串概念恰当地分开:

  1. 字符大多与计算机无关:可以将它们画在粉笔板上等,例如بايثون,中蟒和🐍。用于机器的字符还包括绘制说明,例如空格、回车、设置书写方向的说明(用于阿拉伯语等)、重音等。Unicode标准中包含了very large character list;它涵盖了大多数已知字符。

  2. 另一方面,计算机确实需要以某种方式表示抽象字符:为此,它们使用字节数组(包括0到255之间的数字),因为它们的内存是以字节块为单位的。将字符转换为字节的必要过程称为编码。因此,计算机需要编码才能表示字符。您的计算机上存在的任何文本都会被编码(直到它被显示),无论它是被发送到终端(它需要以特定方式编码的字符),还是保存在文件中。为了显示或正确理解字节流(例如,通过Python解释器),字节流被解码为字符。A few encodings(Utf-8、Utf-16、…)是由Unicode为其字符列表定义的(Unicode因此定义了一个字符列表和这些字符的编码--在某些地方,人们仍然将Unicode编码看作是指普遍存在的UTF-8的一种方式,但这是不正确的术语,因为Unicode提供了多种编码)。

总的来说,计算机需要在内部用字节表示字符,它们通过两个操作来实现:

编码:字符→字节

解码:字节→字符

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

请注意换行符adds a layer of complication的概念,因为它可以由不同的(控制)字符表示,具体取决于操作系统(这就是使用universal newline file reading mode的原因)。


如果您感兴趣,请参阅有关Unicode、字符和代码点的更多信息:

现在,我上面所说的字符就是Unicode所说的用户感知字符&。用户可感知的单个字符有时可以通过组合字符部分(基本字符、重音、…)来用unicode表示。位于Unicode列表中的不同indexes,称为<<3-6]>-这些代码点可以组合在一起形成";字素簇。 因此,Unicode引出了第三个字符串概念,它由一系列Unicode代码点组成,位于字节和字符串之间,更接近后者。我将它们命名为";Unicode字符串&q;(就像在Python2中一样)。

虽然Python可以打印(用户可识别的)字符串,但Python非字节字符串本质上是Unicode代码点的序列,而不是用户可识别的字符序列。代码点值是在Python的uUUnicode字符串语法中使用的值。不应将它们与字符的编码混淆(并且不必与其有任何关系:Unicode代码点可以以各种方式编码)。

这产生了一个重要的结果:Python(Unicode)字符串的长度是它的代码点的数量,而不是总是它的用户感知的字符数:因此s = "u1100u1161u11a8"; print(s, "len", len(s))(Python3)提供了각 len 3,尽管s只有一个用户感知的(韩语)字符(因为它由3个代码点表示-即使它不是必须的,如print("uac01")所示)。但是,在许多实际情况中,字符串的长度是其用户可识别的字符的数量,因为许多字符通常由Python存储为单个Unicode代码点。

Python2中,unicode字符串称为…Unicode字符串(unicode类型,文字形式u"…"),而字节数组是&q;字符串&q;(str类型,其中字节数组可以例如用字符串文字"…"构造)。在Python3中,Unicode字符串被简单地称为";字符串";(strtype,字面形式"…"),而字节数组则是字节";字节";(bytes类型,字面形式b"…"))。因此,类似"🐍"[0]的代码在Python2('xf0',一个字节)和Python3("🐍",第一个也是唯一的字符)中产生不同的结果。

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


正常情况下,当您打印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会这样做,并会毫无怨言地将相应的字节发送到您的终端。然后,终端将在对输入字节进行解码后尽最大努力显示字符(在最坏的情况下,终端字体没有某些字符,而是打印某种空白)。

如果您输入的字符不能用终端的编码进行编码,则意味着终端没有配置为显示这些字符。Python会抱怨(在带有UnicodeEncodeError的Python中,因为字符串不能以适合您的终端的方式进行编码)。唯一可能的解决方案是使用可以显示字符的终端(通过配置终端以使其接受可以表示您的字符的编码,或者通过使用不同的终端程序)。当您分发可在不同环境中使用的程序时,这一点很重要:您打印的消息应该在用户的终端中可表示。因此,有时最好使用仅包含ASCII字符的字符串。

但是,当您重定向或管道程序的输出时,通常不可能知道接收程序的输入编码是什么,并且上面的代码返回一些默认编码:NONE(Python2.7)或UTF-8(Python3):

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

如果需要,可以通过PYTHONIOENCODING环境变量set对stdin、stdout和stderr进行编码:

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

如果打印到终端没有产生预期的结果,您可以检查手动输入的UTF-8编码是否正确;例如,您的第一个字符(u001A)不可打印,if I'm not mistaken

http://wiki.python.org/moin/PrintFails上,您可以找到如下针对Python2.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"u001Au0BC3u1451U0001D10C"
print uni

对于Python3,您可以在StackOverflow上检查one of the questions asked previously

这篇关于重定向到文件时发生UnicodeDecodeError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆