阅读python
源代码的时候不难发现许多文件开头都有这样一行内容:
# -*- coding: UTF-8 -*-
如果想在python
中使用中文,这样的一行声明也是必须的。python
中的编码在实际使用的时候也是经常让人感觉很混乱。以下详细研究一下python
的编码问题。
来源
python在上世纪九十年代研发发布,那时候研发者很显然没有想到语言有严重的编码问题。因此在python3
之前,python
编码默认使用的是ascii
。因此使用python
自带的str
的时候,实际上我们用的是默认的ascii
编码方式。
使用# -*- coding: UTF-8 -*-
这段代码用于声明代码中文本的编码是utf-8
。告诉解释器将文件中的文本视为utf-8
编码的字符串。当然使用的时候应该保证代码文件确实是以utf-8
的形式存储的。
查看python
的默认编码方式可以使用以下语句:
import sys
sys.getdefaultencoding()
python2
默认使用的是ascii
,python3
使用的是utf-8
。
扩展
编码发展历程
由于计算机首先在美国出现,编码最开始只需要表达出英文即可。因此首先出现的编码由8位组成一个字符。有256种状态。其中0到32表示特殊用途的字符,比如回车,响铃等等,用于控制当时的机器的行为。之后是空格标点数字字母等等。一直到127号。这个方案叫做ANSI
的ascii(American Standard Code for Information Interchange)
编码。
之后由于世界各地开始使用计算机,很多字符无法使用acsii
表示,因此开始采用127号之后的状态表示字符。这些字符被称为扩展字符集。
对于中国来说,由于没有足够的字节状态表示汉字,所以取消了之前127号之后的扩展字符集,用两个字节来表示汉字。当一个字节小于127的时候,还是表示之前的ascii
字符,当大于127的时候,两个字节在一起表示一个中文字符。前面一个字节(高字节)范围是0xA1
到0xF7
,后面一个字节(低字节)范围是0xA1
到0xFE
。这些多出来的组合用来表示简体汉字外,还将之前的数学符号,罗马希腊字母,日文假名以及之前的acsii
中的内容都编制进去了。这些两位字节的字符叫做全角字符,原来的127号以下的字符叫做半角字符。这个汉字方案叫做GB2312
。
之后,由于中国汉字太多,GB2312
方案也已经不够用来表示所有的汉字。所以不仅将之前没有用完的码位拿出来用上,还取消了低字节一定是127号之后的规定。只要第一个字节是大于127号的就表示这是一个汉字的开始。这个扩展之后的编码方案称为GBK
标准。GBK
标准增加了包括繁体字等等近20000个新的汉字和符号。
接下来,由于需要加入少数民族的文字,这个标准再次扩充,新加入了几千个少数民族文字,扩展成了GB18030
。
之前的这一系列汉字标准被称作DBCS(Double Byte Character Set)
双字节字符集。在DBCS
系列标准里,双字节长的汉字字符和单字节的英文字符并存,因此计算字符串的长度的时候一个汉字等于两个英文字符。
以上是中国的编码标准。由于之前没个国家都按照这个过程制定了一套自己的标准,因此这些编码互相不能相通,甚至台湾也使用了自己的一套DBCS
系统GIG5
。所以ISO(国际标准化组织)
提出了一种新的方案。废除了所有的地区性编码方案。制定了一套包含地球上所有文化字幕和符号的编码,Universal Multiple-Octet Coded Character Set
,简称UCS
,俗称unicode
。
这时由于储存器空间的极大发展,因此空间不再成为问题了。ISO
规定unicode
必须使用两个字节16位来表示所有的字符。之前acsii
里面的字符,也只是原编码不变,扩展为16位。因此这种方案会在保存英文文本的时候浪费一半的空间。此时,一个汉字和一个英文都是一个字符,不再像之前一样一个汉字是两个英文字符了。一个字符就是两个字节。
但是unicode
有很大的缺点。之前提到的字节浪费就是其中之一。另外当不知道编码方式的时候也很难区分acsii
码和unicode
码。
直到互联网的出现。由于网络传输再一次对编码精炼程度要求很大,为了解决unicode
在网上的传输问题,制定了许多UTF(UCS Transfer Format)
标准。其中之前提到的utf-8
就是每次传输8个位,utf-16
就是每次传输16个位。utf-8
时网络上使用最广的unicode
传输实现方式。utf-8
的特点在于它是一种变长的编码方式。使用一到四个字节表示一个符号,根据不同的符号变化字节长度。当字节在ascii
范围内的时候,用一个字节表示,这样就解决了英文的存储浪费和无法区分ascii
和unicode
的问题。对于中文来说unicode
中中文一个字节,utf-8
中一个中文三个字节。从unicode
到utf-8
需要经过一系列算法和规则。
python中字符串的编码
之前解释了python明文表示使用utf-8
来读取文件的作用。这只是解决了python
代码读取的问题。python
本身的字符串使用也相当混乱(对于3之前)。由于python
使用acsii
作为编码方式,因此代码里面的字符串默认的格式是ascii
。这样在包含中文等字符的操作的时候,由于编码问题就会报错。python3
默认的编码方式已经变成了utf-8
所以不会有这些问题。以下都是针对python2
来说。
python
中的字符串有两种,编码之前的unicode
和编码过后的str
类型。unicode
即字面上的编码方式,使用unicode
,按照上文说的,中文两个字节。str
可以使用不同的编码方式,比如ascii
,gbk
,utf-8
等等。默认使用的是ascii
码。
在上面两种模式中,unicode
作为在str
的各种编码类型之间转换的中间码形式存在。要想在不同的编码之间互相转化,需要先转化成unicode
,之后在转化成目的编码。例如:
testStr = "hello"
print type(testStr)
# <type 'str'>
print type(testStr.decode("ascii"))
# <type 'unicode'>
print type(testStr.decode('ascii').encode('utf-8'))
# <type 'str'>
以上过程即将一个ascii
编码的字符串转化成unicode
又转化成了utf-8
。当然,对于hello
这串西文字符来说,这两种编码方式没有区别。
虽然如问题中的声明方式可以将代码文件中的编码方式确定为utf-8
。但是代码中涉及到文件处理,网络流处理的时候,还需要进行适当的编码转化。Python
试图进行隐式的的转化,使用之前提到的默认编码进行字节串和unicode
之间的转化。在默认的编码方式是ascii
的情况下,大部分这种转化是错误的。这时候会报这样的错误:
a = '你好'
print a.decode()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
# 用ascii解码汉字报错
print a.decode('utf-8')
# u'\u4f60\u597d'
print a.decode('utf-8').encode()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
# 用ascii编码汉字报错
print a.decode('utf-8').encode('utf-8')
# 你好
因此,在使用python2
的时候,在编码方面要注意尽量使用unicode
来进行字符串的操作。字符串需要使用正确的编码标准来进行解码和编码。从外界读取的字符串都先解码成unicode
再进行操作,然后输出的时候再编码。