07月24, 2014

浅谈Python中的编码问题

对于Python的初学者来说,编码问题相当令人头疼。本文就根据我在学习过程中遇到的问题简单谈一下Python中的编码。首先简单介绍一下几种常见的编码。

一、几种常见的字符编码

ASCII码

ASCII码是基于拉丁字码的一套电脑编码系统。它对英语字符与二进制位之间的关系做了统一的规定,使用指定的7位或8为二进制数组合来表示128或256种可能的字符。标准ASCII码也叫基础ASCII码,使用7位二进制来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。

英语中英文字母用128个符号编码就够了,但是用来表示其他语言,128个符号显然是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。

Unicode

Unicode统一码、万国码、单一码)是一种在计算机上使用的字符编码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

UTF-8

UTF-8(8位元Universal Character Set/Unicode Transformation Format)是一种针对Unicode的可变长度字符编码。UTF-8最大的一个特点,是它是一种变长的编码方式。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节与ASCII相容。UTF-8是在互联网上使用最广的一种Unicode的实现方式。

二、Python中的字符串类型

Python中的字符串有两种类型:str类型和unicode类型。以字符串“中文”赋值给变量为例:

s='中文'
>>>type(s)
<type 'str'>

或者

s=u’中文’
>>>type(s)
<type 'unicode'>

在Python中,为了统一不同编码的字符串的表示,同时简化字符串的处理,其内部提供了一种统一化的文本类型unicode,即第二种形式的字符串。unicode类型的字符串只处理文本,并把文本以unicode形式在内部存储。unicode类型的作用只用于代码内部字符串的处理,而不关心外部文本的具体格式,可以看作是文本的抽象表示。前者定义了一个字符串,后者定义了一个unicode编码的字符串。

但是实际上外界文本的不同编码格式众多。比如向一个网站提交数据,其有可能要求utf-8的编码或者gbk的编码,不同的编码类型的内容是不同的, 这就需要将程序内部字符串转换成可以与外界交互的编码(如:utf-8,ascii,gdb等)。Python默认使用str类型来操作。严格来讲,str并不一定是文本,它也有可能是二进制的内容,它提供的其实是字节的组合(unicode类型提供的是unicode字符集的组合),只是如果str中刚好是某种形式编码的文本,它便可以当做文本处理(print等)。

三、python中常遇到的编码问题

以下问题只有在Python2.x版本中出现,因为3.X版本中python环境就只有unicode类型的字符串了,即所有程序中处理的都会自动转换成unicode字符串。

  1. 代码文件编码声明

编写Python脚本时,教程都会让我们把“# -- coding: utf-8 --”加在代码文件的第一行。这句话是告诉python这个文件里的文本用utf-8编码。Python默认将代码文件内容当做ASCII编码处理,因此当文件中存在中文时就会抛出异常。加上这句编码声明后,Python就会依照utf-8的编码形式解读其中的字符,然会转换成unicode编码内部处理使用。注意这句编码声明一定要放在第一行或者第二行才生效,我之前就将它放在了其他位置,结果将源代码文件从windows移动到Linux后,出现了编码问题,文件中的中文注释全成了乱码。因为Windows中默认编码为gbk,Linux默认编码为utf-8。

或者也可以使用下面的语句来设置编码方式:

import sys
reload(sys)
sys.setdefaultencoding('UTF-8')
  1. 编码转换

编写python过程中经常遇到报错“UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-1: ordinal not in range(128)”,为什么会出这样的报错呢?

第二节提到,字符串在Python内部使用unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。

decode的作用是将普通字符串按照参数中的编码格式进行解析,然后生成对应的unicode对象。如str1.decode(‘gb2312’),表示将字符串str1按照gb2312编码解析为unicode对象。

encode的作用正好相反,是将一个unicode对象转换为参数中指定的编码格式的普通字符。如str2.encode(‘gb2312′),表示将unicode对象str2转换成gb2312编码的字符串。

因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码。

代码中字符串的默认编码与代码文件本身的编码一致。也就是说,在utf8的文件中,字符串就是utf8编码,如果是在gb2312的文件中,则其编码为gb2312。通常,在没有指定特定的编码方式时,都是使用的系统默认编码创建的代码文件。

如果字符串是这样定义:s=u’中文’

则该字符串就被定义为unicode对象了,即python的内部编码,而与代码文件本身的编码无关。因此,对于这种情况做编码转换,只需要直接使用encode方法将其转换成指定编码即可。

如果一个字符串已经是unicode了,再进行解码则将出错,因此通常要对其编码方式是否为unicode进行判断:

isinstance(s, unicode) #用来判断s是否为unicode

同样,用非unicode编码形式的str来encode会报错 。

因此,程序会提示:UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-1: ordinal not in range(128),是因为Python调用了ASCII编码解码程序去处理unicode对象,导致抛出异常(ordinal not in range(128))。解决办法则是用上一小节提到的第二种设置编码的方式来修改默认的编码模式即可。

  1. 输出打印

我们在windows控制台下打印中文时,经常出现屏幕上打印出的字和我们想要的结果不一致的情况。这是由于python编码与控制台编码不一致造成的。Windows下控制台中的编码默认使用的是gbk,而在代码中使用的是utf-8,python按照utf-8编码打印到gbk编码的控制台下自然就不能打印出正确的汉字。

解决办法一个是将源代码的编码方式改成gbk,也就是将源代码文件的第一行改成:

# -*- coding: gbk -*-

另一种方式是保持源代码文件的utf-8编码方式不变,而是在声明带中文的变量前u,如:s1=u’中文’,这样就可以正确打印中文了。

这里的u表示将后面跟的字符串以unicode格式存储。python会根据代码第一行声明的utf-8编码识别代码中的汉字,然后转换成unicode对象以unicode格式存在于内存中,而如果不加u,表明这仅仅是一个使用某种编码的字符串,编码格式取决于python对源码文件编码的识别,这里就是utf-8。Python在向控制台输出unicode对象的时候会自动根据输出环境的编码进行转换,但如果输出的不是unicode对象而是普通字符串,则会直接按照字符串的编码输出字符串,从而出现上面的现象。

  1. 文件的读取

在对文件内容进行读取时也经常出现编码问题。这里我们首先来了解一下文件编码。

文件编码即文件的编码方式。严格意义上来说,文件没有编码之说,都是按二进制格式保存在硬盘中的,只是在写入读取时需使用对应的编码进行处理,以便操作系统配合相关软件/字体,绘制到屏幕中给人看。所以关键问题是得知道文件内容是使用什么方式编码成二进制码存入到磁盘中的。

Linux中Vim下可使用命令set fileencoding来查看文件编码。

Windows中txt文件点击“文件”–>“另存为”,查看“编码”显示的编码方式(ANSI:非Unicode编码方式,对于英文系统即ASCII编码,中文系统则为GB2312或Big5编码;其余三种为“Unicode”(对应UTF-16 LE)、“Unicode big endian”(对应UTF-16 BE)和“UTF-8”)。

知道文件编码方式后,只需要在读取文件时使用相同的编码方式对内容进行解码就能获得正确的文件内容。

# _*_ coding:utf-8 _*_
input_stream=open('temp.txt','w')
str1 = "你好"
input_stream.write(str1)#使用utf-8的编码方式将内容写入文件
input_stream.close()

input_stream = open('temp.txt','r') 
line = input_stream.readline()
print line.decode('utf-8') #读取utf-8编码的文件需按utf-8解码

但很多情况下我们无从知晓文件的编码方式,此时可以使用python提供的Chardet包。

Character encoding auto-detection(自动字符探测器),Python中一个强力的编码检测包。使用方式非常简单。

Import chardet
f = open('file','r') 
fencoding = chardet.detect(f.read()) 
print fencoding 
#fencoding:{'confidence':0.96630842899499614,'encoding':'GB2312'}} 
#表示f中的内容使用“GB2312”编码的概率为0.966

知道字符串的编码后就可以利用decode和encode实现编码的转换得到正确的文件内容了。

因此,Python中的编码问题解决方式总结起来就是:保证字符串的编码及解码方式一致,了解了文中提到相关知识相信能解决Python中大部分的编码问题了。

本文链接:http://blogs.360.cn/post/浅谈python中的编码问题.html

-- EOF --

Comments