Python字符串编码
文本,他们通常指显示在屏幕上的字符或者其他的记号;但是计算机不能直接处理这些字符和标记;它们只认识位(bit)和字节(byte)。实际上,从屏幕上的每一块文本都是以某种 字符编码(character encoding) 的方式保存的。粗略地说就是,字符编码提供一种映射,使屏幕上显示的内容和内存、磁盘内存储的内容对应起来。有许多种不同的字符编码,有一些是为特定的语言,比如俄语、中文或者英语,设计、优化的,另外一些则可以用于多种语言的编码。
在实际操作中则会比上边描述的更复杂一些。许多字符在几种编码里是共用的,但是在实际的内存或者磁盘上,不同的编码方式可能会使用不同的字节序 列来存储他们。所以,你可以把字符编码当做一种解码密钥。当有人给你一个字节序列 -- 文件,网页,或者别的什么 -- 并且告诉你它们是文本时,就需要知道他们使用了何种编码方式,然后才能将这些字节序列解码成字符。如果他们给的是错误的密钥或者根本没有给你密钥,那就得 自己来破解这段编码,这可是一个艰难的任务。有可能你使用了错误的解码方式,然后出现一些莫名其妙的结果。
你所了解的关于字符串的知识都是错的。
你肯定见过这样的网页,在撇号( " )该出现的地方被奇怪的像问号的字符替代了。这种情况通常意味着页面的作者没有正确的声明其使用的编码方式,浏览器只能自己来猜测,结果就是一些正确的和意料之外的字符的混合体。如果原文是英语,那只是不方便阅读而已;在其他的语言环境下,结果可能是完全不可读的。
现有的字符编码各类给世界上每种主要的语言都提供了编码方案。由于每种语言的各不相同,而且在以前内存和硬盘都很昂贵,所以每种字符编码都为特定的语言做 了优化。上边这句话的意思是,每种编码都使用数字(0–255)来代表这种语言的字符。比如,你也许熟悉 ASCII 编码,它将英语中的字符都当做从0–127的数字来存储。(65表示大写的A,97表示小写的a, & c。)英语的字母表很简单,所以它能用不到128个数字表达出来。如果你懂得2进制计数的话,它只使用了一个字节内的7位。
西欧的一些语言,比如法语,西班牙语和德语等,比英语有更多的字母。或者,更准确的说,这些语言含有与变音符号(diacritical marks)组合起来的字母,像西班牙语里的 ñ 。这些语言最常用的编码方式是CP-1252,又叫做windows-1252,因为它在微软的视窗操作系统上被广泛使用。CP-1252和 ASCII 在0–127这个范围内的字符是一样的,但是CP-1252为 ñ (n-with-a-tilde-over-it, 241), Ü (u-with-two-dots-over-it, 252)这类字符而扩展到了128–255这个范围。然而,它仍然是一种单字节的编码方式;可能的最大数字为255,这仍然可以用一个字节来表示。
然而,像中文,日语和韩语等语言,他们的字符如此之多而不得不需要多字节编码的字符集。即,使用两个字节的数字 (0–255)代表每个字符。但是就跟不同的单字节编码方式一样,多字节编码方式之间也有同样的问题,即他们使用的数字是相同的,但 是表达的内容却不同。相对于单字节编码方式它们只是使用的数字范围更广一些,因为有更多的字符需要表示。
在没有网络的时代,文本由自己输入,偶尔才会打印出来,大多数情况下使用以上的编码方案是可行的。那时没有太多的纯文本。源代码使用 ASCII 编码,其他人也都使用字处理器,这些字处理器定义了他们自己的格式(非文本的),这些格式会连同字符编码信息和风格样式一起记录其中, & c。人们使用与原作者相同的字处理软件读取这些文档,所以或多或少地能够使用。
现在,我们考虑一下像email和web这样的全球网络的出现。大量的“纯文本”文件在全球范围内流转,它们在一台电脑上被撰写出来,通过第二 台电脑进行传输,最后在另外一台电脑上显示。计算机只能识别数字,但是这些数字可能表达的是其他的东西。Oh no! 怎么办呢。。好吧,那么系统必须被设计成在每一段“纯文本”上都搭载编码信息。记住,编码方式是将计算机可读的数字映射成人类可读的字符的解码密钥。失去 解码密钥则意味着混乱不清的,莫名其妙的信息,或者更糟。
现在我们考虑尝试把多段文本存储在同一个地方,比如放置所有收到邮件的数据库。这仍然需要对每段文本存储其相关的字符编码信息,只有这样才能正 确地显示它们。这很困难吗?试试搜索你的email数据库,这意味着需要在运行时进行编码之间的转换。很有趣是吧…
现在我们来分析另外一种可能性,即多语言文档,同一篇文档里来自几种不同语言的字符混在一起。(提示:处理这样文档的程序通常使用转义符在不同的模式 (modes)之间切换。噗!现在是俄语 koi8-r 模式,所以241代表 Я;噗噗!现在到了Mac Greek模式,所以241代表 ώ。)当然,你也会想要搜索 这些 文档。根本就没有所谓的纯文本。
--------------------------------------分割线 --------------------------------------
CentOS上源码安装Python3.4 http://www.linuxidc.com/Linux/2015-01/111870.htm
《Python核心编程 第二版》.(Wesley J. Chun ).[高清PDF中文版] http://www.linuxidc.com/Linux/2013-06/85425.htm
《Python开发技术详解》.( 周伟,宗杰).[高清PDF扫描版+随书视频+代码] http://www.linuxidc.com/Linux/2013-11/92693.htm
Python脚本获取Linux系统信息 http://www.linuxidc.com/Linux/2013-08/88531.htm
在Ubuntu下用Python搭建桌面算法交易研究环境 http://www.linuxidc.com/Linux/2013-11/92534.htm
Python 语言的发展简史 http://www.linuxidc.com/Linux/2014-09/107206.htm
Unicode
Unicode编码系统为表达 任意 语言的 任意 字符而设计。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所 有的数字都用上了,但是总数已经超过了65535,所以2个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的 语源学(etymological)理由使不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录模 式了。 U+0041 总是代表 "A" ,即使这种语言没有 "A" 这个字符。
初次面对这个创想,它看起来似乎很伟大。一种编码方式即可解决所有问题。文档可包含多种语言。不再需要在各种编码方式之间进行模式转换。但是很 快,一个明显的问题跳到我们面前。4个字节?只为了单独一个字符 这似乎太浪费了,特别是对像英语和西语这样的语言,他们只需要不到1个字节即可以表达所需的字符。事实上,对于以象形为基础的语言(比如中文)这种方法也 有浪费,因为这些语言的字符也从来不需要超过2个字节即可表达。
有一种Unicode编码方式每1个字符使用4个字节。它叫做 UTF-32 ,因为32位 = 4字节。UTF-32是一种直观的编码方式;它收录每一个Unicode字符(4字节数字)然后就以那个数字代表该字符。这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第 N 个字符,因为第 N 个字符从第 4×Nth 个字节开始。另外,它也有其缺点,最明显的就是它使用4个诡异的字节来存储每个诡异的字符…
尽管有Unicode字符非常多,但是实际上大多数人不会用到超过前65535个以外的字符。因此,就有了另外一种Unicode编码方式,叫做 UTF-16 (因为16位 = 2字节)。UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的星芒层(astral plane)内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF- 32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。并且,如果我们假设某个字符串不包含任何 星芒层中的字符,那么我们依然可以在常数时间内找到其中的第 N 个字符,直到它不成立为止这总是一个不错的推断…
但是对于UTF-32和UTF-16编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符 U+4E2D 在UTF-16编码方式下可能被保存为 4E 2D 或者 2D 4E ,这取决于该系统使用的是大尾端(big-endian)还是小尾端(little-endian)。(对于UTF-32编码方式,则有更多种可能的字节 排列。)只要文档没有离开你的计算机,它还是安全的 -- 同一台电脑上的不同程序使用相同的字节顺序(byte order)。但是当我们需要在系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档 的计算机就无法知道这两个字节 4E 2D 表达的到底是 U+4E2D 还是 U+2D4E 。
为了解决 这个 问题,多字节的Unicode编���方式定义了一个字节顺序标记(Byte Order Mark),它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,字节顺序标记是 U+FEFF 。如果收到一个以字节 FF FE 开头的UTF-16编码的文档,你就能确定它的字节顺序是单向的(one way)的了;如果它以 FE FF 开头,则可以确定字节顺序反向了。
不过,UTF-16还不够完美,特别是要处理许多 ASCII 字符时。如果仔细想想的话,甚至一个中文网页也会包含许多的 ASCII 字符 -- 所有包围在可打印中文字符周围的元素(element)和属性(attribute)。能够在常数时间内找到第 Nth 个字符当然非常好,但是依然存在着纠缠不休的星芒层字符的问题,这意味着你不能 保证 每个字符都是2个字节长,所以,除非你维护着另外一个索引,不然就不能 真正意义上的 在常数时间内定位第 N 个字符。另外,朋友,世界上肯定还存在很多的 ASCII 文本…
另外一些人琢磨着这些问题,他们找到了一种解决方法:
UTF-8
UTF-8是一种为Unicode设计的 变长(variable-length) 编码系统。即,不同的字符可使用不同数量的字节编码。对于ASCII 字符(A-Z, & c.) UTF-8 仅使用1个字节来编码。事实上, UTF-8 中前128个字符(0–127)使用的是跟ASCII 一样的编码方式。像ñ和ö这样的扩展拉丁字符(Extended Latin)则使用2个字节来编码。(这里的字节并不是像UTF-16中那样简单的Unicode编码点(unicode code point);它使用了一些位变换(bit-twiddling)。)中文字符比如则占用了3个字节。很少使用的星芒层字符则占用4个字节。
缺点:因为每个字符使用不同数量的字节编码,所以寻找串中第 N 个字符是一个O(N)复杂度的操作 -- 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。
优点:在处理经常会用到的 ASCII 字符方面非常有效。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。同时,(在这一条上你得相信我,因为我不打算给你展示它的数学原理。)由位操作的天性使然,使用UTF-8不再存在字节顺序的问题了。一份以 UTF-8 编码的文档在不同的计算机之间是一样的比特流。
Python源码的编码方式
Python 3会假定我们的源码 -- 即 .py 文件 -- 使用的是 UTF-8 编码方式。
Python 2里, .py 文件 默认 的编码方式为 ASCII 。Python 3的源码的默认编码方式为 UTF-8
如果想使用一种不同的编码方式来保存Python代码,我们可以在每个文件的第一行放置编码声明(encoding declaration)。以下声明定义 .py 文件使用windows-1252编码方式:
# -*- coding: windows-1252 -*-
从技术上说,字符编码的重载声明也可以放在第二行,如果第一行被类 UNIX 系统中的hash-bang命令占用了。
#!/usr/bin/python3 # -*- coding: windows-1252 -*-
了解更多信息,请参阅 PEP 263: 指定Python源码的编码方式。
在Python 3,所有的字符串都是使用Unicode编码的字符序列。不再存在以UTF-8或者CP-1252编码的情况。也就是说,这个字符串是以 UTF-8 编码的吗?不再是一个有效问题。 UTF-8 是一种将字符编码成字节序列的方式。如果需要将字符串转换成特定编码的字节序列,Python 3可以为你做到。如果需要将一个字节序列转换成字符串,Python 3也能为你做到。字节即字节,并非字符。字符在计算机内只是一种抽象。字符串则是一种抽象的序列。
>>> s = "深入 Python">>> len(s)9>>> s[0]"深">>> s + " 3""深入 Python 3"
-
为了创建一个字符串,将其用引号包围。Python字符串可以通过单引号( " )或者双引号( " )来定义。
-
内置函数 len() 可返回字符串的长度, 即 字符的个数。这与获得列表,元组,集合或者字典的长度的函数是同一个。Python中,字符串可以想像成由字符组成的元组。
-
Just like getting individual items out of a list, you can get individual characters out of a string using index notation.与取得列表中的元素一样,也可以通过下标记号取得字符串中的某个字符。
-
类似列表,可以使用 + 操作符来 连接(concatenate) 字符串。
- 本文来自:Linux教程