关于你不想知道的所有Python3 unicode特性
我的读者知道我是一个喜欢痛骂Python3 unicode的人。这次也不例外。我将会告诉你用unicode有多痛苦和为什么我不能闭嘴。我花了两周时间研究Python3,我需要发泄我的失望。在这些责骂中,仍然有有用的信息,因为它教我们如何来处理Python3。如果没有被我烦到,就读一读吧。
这次吐槽的内容会不一样。不会关联到WSGI或者HTTP及与其相关的东西。通常,我被告知我应该停止抱怨Python3 Unicode系统,因为我不写别人经常写的代码(HTTP库之类的东西),所以我这次准备写点别的东西:一个命令行应用程序。我写了一个很方便的库叫click来让编写它更加简单。
注意,我做的是每一个新手Python程序员做的事情:写一个命令行应用程序。Hello World程序。但是不同以往,我想要确保应用程序是稳定的并且对于Python2和Python3的Unicode都是支持的,还能够进行单元测试。所以接下来的就是如何来实现它。
我们想做什么
在Python3我们作为开发者需要好好使用Unicode。显然,我觉得这意味着所有的文本数据都是Unicode,所有非文本数据都是字节。在这么美妙的世界里所有的东西只有黑与白,Hello World的例子非常直截了当。所以让我们来写一些shell工具吧。
这是用Python2形式实现的应用程序:
import sys import shutil for filename in sys.argv[1:]: f = sys.stdin if filename != '-': try: f = open(filename, 'rb') except IOError as err: print >> sys.stderr, 'cat.py: %s: %s' % (filename, err) continue with f: shutil.copyfileobj(f, sys.stdout)
显然,命令在处理任何命令行选项的时候也不是特别好,不过至少能够用。所以我们开始码代码吧。
UNIX里的UNICODE
上面的代码在Python2是不行的,因为你暗中处理字节。命令行参数是字节,文件名是字节,文件内容也是字节。语言卫道士会指出这是不对的,这样会引发问题,但如果你开始更多考虑它,你会发现这是个不固定的问题。
UNIX是字节,已经被定义成了这样,并且一直会是这样。为了理解为什么你需要观察数据传输的不同场景。
- 终端
- 命令行参数
- 操作系统输入输出层
- 文件系统驱动
顺便提一下,这不是数据可能通过的唯一东西,但是我们来了解一下,在多少场景下我们能了解一个编码。答案是一个也没有。至少我们需要理解一个编码是终端输出区域信息。这个信息可以用来展现转换,也能够理解文本信息所拥有的编码。
举个例子,如果LC_CTYPE的值为en_US.utf-8告诉应用程序系统使用US English,并且大部分文本数据是utf-8编码。实际上还有很多别的变量,不过我们假定这是我们唯一需要看的。注意LC_CTYPE并不代表所有的数据都是utf-8编码的。它代替通知应用程序如何分类文本特性并且什么时候需要应用转换。
这很重要,原因是因为c locale。c locale是POSIX唯一指定的现场,它说所有ASCII编码和来自命令行工具的回复会按照POSIX spec里定义的来对待。
在我们上面的cat工具里,如果它是比特,没有别的方法来对待这些数据。原因是shell里没有指定这数据是什么。例如你调用cat hello.txt,终端会在对应用程序编码的时候对hello.txt进行编码。
但是现在想想这个例子echo *。Shell会把目前目录的所有文件名传递给你的应用程序。那它们是什么编码?文件名没有编码!
UNICODE疯狂
现在一个用Windows的人看到这里会说:弄UNIX的人在搞什么呢。但这还不算悲惨。产生这些工作的原因是一些聪明的人设计得这个系统能够向后兼容。不像Windows把每个API都定义两次,在POSIX上,最好的处理方法是为了显示的目的将其假定为字节,用默认的编码方式来编码。
用上面的cat命令来举例。比如有一个关于文件无法打开的错误信息,原始是因为它们不存在或者它们是受保护的,或者其他任何的原因。我们假定文件是用latin1编码的,因为它是来自1995年外部驱动。终端会获取标准输出,它将会试着把它用utf-8编码,因为这是它认为的编码。因为字符串是latin1编码的,因为它无法顺利得解码。但是不怕,不会有什么崩溃,因为你的终端在无法处理它的时候会无视它。
它在图形界面上怎样?每种有两个版本。在一个像Nautilus 这样的图形界面上列出所有的文件。它把文件名和图标关联起来,能够双击并且试着使文件名能够显示出来,因而把它解码。例如它会尝试用utf-8解码,错误的地方用问题记号来替代。你的文件名可能不是完全可读的但那是你仍能打开文件。