0. 写在前面

起因:之前写个数据预处理程序的时候遇到了点问题,用re模块的正则查找方法search时总是找不出来(找错了或者出乱码),于是捣鼓捣鼓。

经过:查资料,做实验,发现用utf8编码的str类型的字符串在search方法中行不通,因为str是字节串,和字符之间没有固定的一一对应的关系,正则没法用字节串来进行正确匹配。

结果:把正则式和目标字符串都使用unicode类型,unicode和字符之间是两个字节对应一个字符的关系,正则可以根据这个来对字符进行匹配。

后续:突然觉得应该总结一下编码问题,防止再次入坑。于是有了此文。

1. ascii, unicode, utf8

ascii码:最早的编码,只有127个字符,包含英文字母,数字,标点符号和一些其它符号。一个字节表示一个字符。

unicode(统一码):一个字节不够放,全世界有各种语言的字符需要编码,于是unicode给所有的字符都设定了唯一编码。通常都是用两个字节表示一个字符(有些生僻的字要用四个字节)。所以,要理解一点:下文中提到到的unicode编码是双字节编码(一个字符两个字节)。

uft8:对于ascii编码的那些字符,只需要1个字节,unicode给这些字符也设定2个字节,如果一篇文章全是英文(ascii字符),就浪费了很多空间(本来1个字节可以存储的,用了2个字节),所以产生了utf8。utf8是一种变长的编码方式,根据不同的符号变化字节长度,把ascii编码成1个字节,汉字通常编码成3个字节,一些生僻的字符编码成4~6个字节。

在计算机内存中,统一使用Unicode编码。

在python中,建议程序过程中统一使用unicode编码,保存文件和读取文件时使用utf8(在读写磁盘文件时候用utf8进行相应的decode和encode,关于decode和encode见下文第4点)。

2. encoding声明

python默认使用ascii编码去解释源文件。

如果源文件中出现了非ASCII码字符,不在开头声明encoding会报错。

可以声明为utf8,告诉解释器用utf8去读取文件代码,这个时候源文件有中文也不会报错。

# encoding=utf8 如果不加这一行会报错
print '解释器用相应的encoding去解释python代码'

3. python2.7中的str和unicode

debugger的时候会发现,python2.7中的字符串一般有两种类型,unicode和str。

str为字节码,会根据某种编码把字符串转成一个个字节,这个时候字符和字节没有所谓固定的一一对应的关系。

unicode则是用unicode编码的字符串,这个时候一个字符是对应两个字节的,一一对应。

直接赋值字符串,类型为str,str为字节串,会按照开头的encoding来编码成一个个的字节。

赋值的时候在字符串前面加个u,类型则为unicode,直接按照unicode来编码。

s1 = '字节串'
print type(s1) #输出 <type 'str'>,按照开头的encoding来编码成相应的字节。
print len(s1) #输出9,因为按utf8编码,一个汉字占3个字节,3个字就占9个字节。 s2 = u'统一码'
print type(s2) #输出 <type 'unicode'>,用unicode编码,2个字节1个字符。
print len(s2) #输出3,unicode用字符个数来算长度,从这个角度上看,unicode才是真正意义上的字符串类型

来看点现实的例子,比如我们要从一个文件中找出中所有后两位是'学习'的词语,在进行判断的时候:

s = '机器学习'
s[-2:] == '学习‘
# 返回false,平时写程序可能会以为相等。
# 这里的”学习是用开头的encoding声明解释的,我开头用的是utf8,汉字占3个字节,所以“学习”占了6个字节),而s[-2:]取的是最后两个”双字节“,所以不相同。 s = u'机器学习'
s[-2:] == u'学习’
# 返回true,这也是为什么说unicode是真正意义上的字符串类型。因为使用的是unicode,”学习“占的是两个”双字节“,一个"双字节“一个字。

对于经常处理中文字符串的人,统一用unicode就可以避免这个坑了。

虽然有些字符串处理函数用str也可以,应该是函数里面帮你处理了编码问题。

4. python2.7中的encode和decode

encode的正常使用:对unicode类型进行encode,得到字节串str类型。也即是unicode -> encode(根据指定编码) -> str

decode的正常使用:对str类型进行decode,得到unicode类型。也即是str -> decode(根据指定编码) -> unicode

注意:encode和decode的时候都是需要指定编码的。

因为在编码的时候要知道原来的编码是什么和按照什么新编码方式进行编码,要用到两种编码,这里默认有一个unicode,所以需要再指定一个编码方式。解码的时候也是一个道理。

这两个方法就是在unicode和str之间用指定编码进行转换。

s3 = u'统一码'.encode('utf8')
print type(s3) # 输出 <type 'str'> s4 = '字节串'.decode('utf8')
print type(s4) #输出 <type 'unicode'>

encode的不正常使用:对str类型进行encode,因为encode需要的是unicode类型,这个时候python会用默认的系统编码decode成unicode类型,再用你给出编码进行encode。(注意这里的系统编码不是开头的encoding,具体例子见下文第5点)

decode的不正常使用:对unicode类型进行decode,python会用默认的系统编码encode成str类型,再用你给出的编码进行decode。

所以改好对应的系统默认编码,就算不正常使用,也不会报错啦。不过多拐了一下路,个人不喜欢这样。

5. 修改系统默认编码

系统默认使用ascii编码,需要进行相应的修改。

这个编码和开头的encoding不同之处在于,开头的encoding是对于文件内容的编码。

这里的编码是一些python方法中默认使用的编码,比如对str进行encode的时候默认先decode的编码,比如文件写操作write的encode的编码(关于文件读写见下文第7点)

import sys
reload(sys)
sys.setdefaultencoding('utf8') s = '字节串str' s.encode('utf8')
#等价于
s.decode(系统编码).encode('utf8')

关于系统默认编码发挥作用的地方,来看看另一个例子。

import sys
print sys.getdefaultencoding() # 输出ascii s = 'u华南理工大学'
print s[-2:] == '大学' # 返回False,并有warning提醒 reload(sys)
sys.setdefaultencoding('utf8') print s[-2:] == '大学' # 返回True

根据结果得知:python在用==比较时,如果第一个操作符是unicode而第二个不是的话,会自动用系统默认编码帮第二个操作符decode。

PS:为什么需要reload(sys)呢。首先,reload是用于重新加载之前import的模块。

这里需要重新加载sys的原因是:python在加载模块时候删除了sys中的setdefaultencoding方法(可能是出于安全起见),所以需要reload这个sys模块。

这里再举个简单例子,比如我要修改keras的后端,从tensorflow改成theano,修改后需要重新加载keras的backend模块才能修改成功。

import keras.backend as K

k.backend()    # 一开始是u'tensorflow'

import os

os.environ['KERAS_BACKEND'] = 'theano'

K.backend() # 修改后还是u'tensorflow'

reload(K)

k.backend() # reload之后后端才变成u'theano'

6. 查看文件编码

import chardet
with open(filename,'r') as f:
data = f.read()
return chardet.detect(data)

7. 文件读写

首先要记住,读出和写入,这两个文件的关口都是用str类型的,就是一个个字节。

python中内置的默认open在读取文件的时候以字节串str的形式,读出一个个字节。读取后要用正确的编码才能decode成正确的unicode,所以要知道原来在文件中的编码。

写文件的时候也是一个道理,用str类型,以字节的形式写入,这个str是以某种编码方式编码的,要注意用正确的编码方式编码,一般是按utf8编码后写文件。

如果你用unicode类型写入,python会根据系统默认编码来把unicode编码成str再写入文件。因为写入文件需要的是str,是str就写,不是我就把你转成str再写。

简单原则,尽量用str写入,避免使用默认编码,这样也不用在开头修改默认编码。

python中模块codecs中的open方法可以指定一个编码。它保证了读入和写出的字节都是按照这个指定编码进行编码的。

这样在读文件的时候:会把读出的str按照指定编码decode成unicode。

写文件的时候:如果是unicode,会根据指定编码encode成str然后写入;如果是str,会根据系统默认编码把str进行decode得到unicode,再根据指定编码encode成str进行写入。

简单原则,尽量用unicode写入,避免使用默认编码,这样也不用在开头修改默认编码。

注意一下,对于其它方式读写文件,需要自行debugger看看编码的问题。比如我在python中读取excel的时候读出来就直接是unicode而不是str。

8. 一般的处理要点

(1) 首先把源文件的默认encoding和系统默认编码改为utf8

(2) 程序执行过程统一使用unicode类型

(3) 对于读写文件(用python内置的默认open来说),得到的是str,对str进行相应的encode和decode就可以了。

总结一下就是:

设置相应的默认编码为utf8;

读文件拿到str类型:str -> decode('utf8') -> unicode

程序处理:用unicode

写文件:unicode -> encode('utf8') -> str,用str类型写入文件

当然前提是文件都是utf8格式的啦,包括源文件和读写的数据文件。

另外想说一下:

对于写程序的过程中统一使用unicode类型这一点只是一个建议,因为统一unicode可以在处理字符串的时候减少麻烦。

觉得全部弄成unicode麻烦的,可以考虑平时统一用utf8编码的str,有些问题需要用unicode的再转为unicode,遇到编码问题时可以思考是不是没有统一用unicode的问题(本文开头就给出了一个需要统一用unicode的情况)

其实弄清楚上面的思路,遇到什么编码问题也能够查错。

python2.7中的字符编码问题的更多相关文章

  1. 浅析白盒审计中的字符编码及SQL注入

    尽管现在呼吁所有的程序都使用unicode编码,所有的网站都使用utf-8编码,来一个统一的国际规范.但仍然有很多,包括国内及国外(特别是非英语国家)的一些cms,仍然使用着自己国家的一套编码,比如g ...

  2. 001. Java内存中的字符编码

    Java内存中的字符编码 Unicode字符集及utf-8 .utf-16.utf-32 等字符编码方式 字符集:字符表示的数字集合,元素称为码点或码位: 字符编码:字符实际的储存表示: 码点:一个码 ...

  3. APACHE2.4 指定目录中的字符编码

    APACHE2.4 指定目录中的字符编码 xampp 的 apache2.4 默认字符编码是西文,中文字符显示乱码,在 httpd.conf 没有 AddDefaultCharset utf-8 这样 ...

  4. SpringBoot(八):SpringBoot中配置字符编码 Springboot中文乱码处理

    SpringBoot中配置字符编码一共有两种方式 方式一: 使用传统的Spring提供的字符编码过滤器(和第二种比较,此方式复杂,由于时间原因这里先不介绍了,后续补上) 方式二(推荐使用) 在appl ...

  5. Python学习笔记 (2.2)Python中的字符编码问题及标准数据类型之String(字符串)

    Python3中的String类型 首先,Python中没有字符类型,只有字符串类型.单个字符按照长度为1的字符串处理,这对于曾是OIER的我来说有点不适应啊. 字符串的表示方法 最常用的就是用一对双 ...

  6. .NET Framework 中的字符编码

    字符是可用多种不同方式表示的抽象实体. 字符编码是一种为受支持字符集中的每个字符进行配对的系统,配对时使用的是表示该字符的某些值. 例如,摩尔斯电码是一种为罗马字母表中的每个字符进行配对的字符编码,配 ...

  7. java中的字符编码方式

    1. 问题由来 面试的时候被问到了各种编码方式的区别,结果一脸懵逼,这个地方集中学习一下. 2. 几种字符编码的方式 1. ASCII码 我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符 ...

  8. servlet中的字符编码过滤器的使用

    一:简介 Servlet过滤器是客户端和目标资源的中间层组件,主要是用于拦截客户端的请求和响应信息.如当web容器收到一条客户端发来的请求 web容器判断该请求是否与过滤器相关联,如果相关联就交给过滤 ...

  9. linux中修改字符编码

    一. ubuntu修改字符编码 1. 添加字符编码,例如zh_CN.UTF-8,有两种方式 方法1:locale-gen zh_CN.UTF-8   #locale-gen命令只在ubuntu中才有 ...

随机推荐

  1. 完成blog后台一枚

    技术实现:纯jfinal+AmazeUI

  2. Visual Studio 2010自动添加头部注释信息

    在日常的开发中我们经常需要为我们的类库添加注释和版权等信息,这样我们就需要每次去拷贝粘贴同样的文字,为了减少这种重复性的工作,我们可以把这些信息保存在Visual Studio 2010类库模版文件里 ...

  3. 更改MVC注册Areas的顺序,掌控Areas的运作

    [转自:http://www.cnblogs.com/dozer/archive/2010/04/14/change-order-of-MVC-Areas.html] 一.前言 首先,有人要问,为什么 ...

  4. JS刷新页面后滚动条的位置不变

    有时候,在网页中点击了页面中的按钮或是刷新了页面后,页面滚动条又 会回到顶部,想看后面的记录就又要拖动滚动条,或者要按翻页键,非常不方便,想在提交页面或者在页面刷新的时候仍然保持滚动条的位置不变,最好 ...

  5. 【vijos】1892 树上的最大匹配(树形dp+计数)

    https://vijos.org/p/1892 这个必须得卡评测机+手动开栈才能卡过QAQ 手动开栈我百度的... int size=256<<20; //256MB char *p=( ...

  6. WebSocket入门教程(五)-- WebSocket实例:简单多人聊天室

    from:https://blog.csdn.net/u010136741/article/details/51612594 [总目录]   WebSocket入门教程--大纲   [实例简介]   ...

  7. 1.2_php验证码

    使用php生成动态的验证码图片 <!DOCTYPE html> <html> <head> <meta charset="utf-8"&g ...

  8. 使用NSKeyedArichiver进行归档、NSKeyedUnarchiver进行解档

    一.使用archiveRootObject进行简单的归档 使用NSKeyedArichiver进行归档.NSKeyedUnarchiver进行接档,这种方式会在写入.读出数据之前对数据进行序列化.反序 ...

  9. ubuntu微信

    方法1 – 使用Snap来安装微信 依次在terminal 执行一下命令 sudo apt install snapd snapd-xdg-open sudo snap install electro ...

  10. onchange事件可以使用于: <input>, <select>, 和 <textarea>。

    onchange 事件会在域的内容改变时发生. onchange 事件也可用于单选框与复选框改变后触发的事件.