一文彻底解决python2的乱码问题

1. 前言

在使用python处理字符串和文件时,如果包含有中文,总是会遇到各种问题,虽然每次都通过查询资料解决了,但是并不是很明白,今天决定系统研究一下。在这里把研究的结果分享出来,也希望大家进行指导。

2. 编码与解码

首先,明确一点,计算机中存储的信息都是二进制的。
编码/解码本质上是一种映射(对应关系),比如‘a’用ascii编码则是65,计算机中存储的就是00110101,但是显示的时候不能显示00110101,还是要显示’a’,但计算机怎么知道00110101是’a’呢,这就需要解码,当选择用ascii解码时,当计算机读到00110101时就到对应的ascii表里一查发现是’a’,就显示为’a’
**编码:**真实字符与二进制串的对应关系,真实字符→二进制串
**解码:**二进制串与真实字符的对应关系,二进制串→真实字符

3. 字符集

说起字符的编码与解码,那就不得不提字符集了
在这里插入图片描述如图所示,我们所知的英文编码都是采用ansi编码,随着中国的崛起,国家也开始信息化,但是计算机不认识中国汉字,怎么办呢,我们制定自己统一的字符集,这就是GB2312,后来更新的字符集,GBK,GB18030,BIG5 ,都是在原来的基础之上增加一些新的元素的识别,比如一些生僻字,繁体字认识。
这样一来不是中文一套,英文一套,那后来的一些其它的国家文字不就都不能互相通用了,那干脆一锅端,将这些英文,中文,各国的语言文字大一统,Unicode就出现了,不仅兼容了这些文字,与方便了各国的信息交流.
在这里插入图片描述unicode编码系统是为表达任意语言而设计的,为了防止存储上的冗余(比如,对应ascii码的部分),其采用了变长编码,但变长编码给解码带来了困难,无法判断是几个字节表示一个字符。 在Unicode编码方式下,又存在 utf-8,utf-16,utf-32的编码方式。UTF-8是针对unicode变长编码设计的一种前缀码,根据前缀可判断是几个字节表示一个字符。

4. python中的编码与解码

通过上面的一些例子,我们可以大致对于编码和解码有一个大概的印象,那接下来我们看看python2.7.x对于编码是怎么处理的,还是先上一个图
在这里插入图片描述basestring下面有两个对象,unicode,str 那这两者的关系是怎么样的?
unicode encode> str #unicode经过encode变成str
str decode> unicode #str经过decode变成unicode
在python中,编码解码其实是不同编码系统间的转换,默认情况下,转换目标是Unicode,即编解码的中间对象是unicode对象。 熟悉Java的同学可能知道,Java编解码的中间对象是byte[]数组,即一个字符串如果要从一种编码方式转换成另一种编码方式,需要先按照原来的编码方式(如utf8)解码成byte[]数组,然后再使用新的编码方式(如gbk)编码成新的字符串。在python中没有byte[]类型,unicode的作用相当于Java中byte[],即编码unicode→str,解码str→unicode,其中str指的是字节流,而str.decode是将字节流str按给定的解码方式解码,并转换成utf-8形式,u.encode是将unicode类按给定的编码方式转换成字节流str。我理解的是python使用unicode编码作为中间编码,来完成不同编码的转换,一种编码想要转换成另一种编码,需要先转换到unicode编码,再从unicode编码转换到其他编码。 注意:unicode编码并不等同于utf-8编码。
注意调用encode方法的是unicode对象,生成的是字节流;调用decode方法的是str对象(字节流),生成的是unicode对象。若str对象调用encode会默认先按系统默认编码方式decode成unicode对象再encode,忽视了中间默认的decode的编码方式往往导致报错。 同样的,若unicode对象调用decode会默认先按系统默认编码方式encode成str对象再encode,忽视了中间默认的encode的的编码方式往往导致报错。

5. 文件开头的coding:utf-8

在python2中,在.py的源代码文件中,如果使用还有中文的字符串或者含有中文注释,会报错:

在这里插入图片描述
在这里插入图片描述
两种形式都会报错,报错信息如下:

SyntaxError: Non-ASCII character '\xe4' in file E:/PycharmProjects/Demo_local/encode/demo_encode.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

这是应为python系统默认使用ascii编码来编码.py文件,由于ascii不能编码中文,所以会报语法错误。
在文档的开头添加coding:utf-8,告诉python系统使用utf-8编码来编码源代码文件,这样就不会再报错了。
在这里插入图片描述
在这里插入图片描述
在python3就不需要这么做,因为python3默认使用utf-8编码.py文件,可以编码中文。

6. sys.defaultencoding(‘utf-8’)

毫无疑问这行代码是设置系统的默认编码方式,与之对应的还有一个sys.getdefaultencoding()可以获得系统的编码方式,这个系统是指python系统,不是指计算机系统。python2系统默认的编码方式是ascii。什么时候应该使用这个函数呢,下面来看一个例子。
在这里插入图片描述
在这里插入图片描述
可以看到在文件头加上coding:utf-8后不再报上面的,SyntaxError: Non-ASCII character '\xe4' in file ...而是在执行s.encode('gb2312')提示UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128),报错的信息表示,不能使用ascii对字符串s做解码,这验证了我们上面的说法,若str对象调用encode会默认先按系统默认编码方式decode成unicode对象再encode, 更验证了 忽视了中间默认的decode往往导致报错。 可见我们在执行s.encode('gb2312')的时候会先使用默认的编码方式ascii把s解码成unicode,然后再把unicode编码成gb2312。因为解码是python自动进行的,我们没有指明解码方式,python 就会使用 sys.defaultencoding 指明的方式来解码。很多情况下 sys.defaultencoding为ANSCII,但是我们上面说过,ascii是不能编码中文的,我们在第一行的位置,已经将源文件的编码方式设置成了utf-8,在python中直接写在源文件中的字符串,和源文件使用相同的编码方式, 所以现在s 中字符串的编码方式已经是utf-8,这时候再使用ascii解码就会出现问题,因为编码和解码的方式应该相同。 该怎么解决这个问题呢,有一下两种方法:

  1. 明确的指示出 s 的编码方式
# -*- coding: utf-8 -*- 
s = '中文字符' 
s.decode('utf-8').encode('gb2312') 
  1. 更改 sys.defaultencoding 为文件的编码方式
    文件的编码方式即,# -*- coding: utf-8 -*- 指定的源文件的编码方式(在python中直接写在源文件中的字符串,和源文件使用相同的编码方式)。
# -*- coding: utf-8 -*- 
import sys 
reload(sys) # Python2.5 初始化后删除了 sys.setdefaultencoding 方法,我们需要重新载入 
sys.setdefaultencoding('utf-8') 
str = '中文字符' 
str.encode('gb2312') #这时候默认解码使用的就是utf-8方式了

对于unicode对象调用类似的:在这里插入图片描述
在这里插入图片描述提示的异常是,encode问题,说明unicode如果调用decode方法,会先隐式地编码成str再解码。

7. 在字符串前面加u

在python中定义字符串,我们经常会看到字符转前面加了一个u,这个u表示使用unicode编码,即这个字符串是unicode对象,而不使用的文件的编码方式。
如下面这个例子:

# -*- coding: utf-8 -*-  #指定文件的编码方式为utf-8,默认为ascii,不指定不能编码中文
s1 = '中文字符' #字符串s1与文件的编码方式相同,即utf-8编码
s2 = u'中文字符' #字符串使用unicode编码
print (type(s1))
print(type(s2))
s2.encode('gb2312')
print s2
s1.encode('gb2312')
print s1

在这里插入图片描述
可以看到指定s2为unicode编码方式,在转换成gb2312编码时,就不用使用默认编码方式转换成unicode对象了,直接编码就行了。但是s1没有指定编码方式,其编码方式与文件的编码方式相同。会先使用默认的编码方式ascii把s解码成unicode,然后再把unicode编码成gb2312。具体原因和解决方法在参看第六部分。
在python的源码中只要出现中文就需要制定源码文件的编码方式,即使已指定字符串为unicode编码,如下面的例子:
在这里插入图片描述
如果不指定文件的编码方式会提示异常:

SyntaxError: Non-ASCII character '\xe4' in file E:/PycharmProjects/Demo_local/encode/demo_encode.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

首行加上#coding:utf-8后异常消失。
在这里插入图片描述
在这里插入图片描述

8. 文件读写时的编码

8.1 python2自带的open()函数

首先需要说明的是,写入文件的是使用一定方式编码的str对象的字节流,读取的时候也是读取的编码的str对象的字节流。但是write函数接受的参数却是unicode类型的,写入文件是隐式编码成str,再把str的字节流写入。读取也是同样的会将读取的str的字节流隐式地解码成unicode。所以如果忽略隐式地操作,我们可认为写入和读取出的都是unicode。
在python2中open()函数不接受encoding参数,隐式解码和编码的方式是ascii码,可以使用如下方式改变:

reload(sys)
sys.setdefaultencoding('utf-8')

而在python3或者使用codecs的open()函数可以使用encoding参数指定隐式编码解码的方式。
看下面的这个例子:

#coding=utf-8
# test.txt是一个以gbk2312编码(简体中文windows系统中的默认文本编码)的文本文件
# 文本写入
with open('test.txt', 'a') as f:
    f.write('test') # 正常写入
    f.write('测试') # 正常写入,乱码
    f.write(u'测试') # 写入错误,触发UnicodeEncodeError异常
#文本读取
with open('test.txt') as f:
    for line in f:
        print line, type(line)

上面的例子f.write('test')能够正常写入,f.write('测试')可以下入文件但是使用ANSI编码(Windows新建txt文件的默认编码)查看文件时使乱码,使用utf-8编码查看是可以正常显示。说明f.write('测试')是以源文件编码utf-8编码写入文件的。由于脚本源文件中的字符为utf-8编码,而文本文档中的字符为gb2312编码,所以以str类型字符串直接写入文件,此时str字符串的编码与文件编码不同,导致乱码。
使用ANSI编码查看:
在这里插入图片描述
使用UTF-8编码查看:
在这里插入图片描述
f.write(u'测试')会提示异常UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128),提示信息说明**python2在写入unicode字符串时会自动尝试转码为ascii编码,而ascii编码并不能处理中文。 **
直接str类型参数传递给write方法容易导致乱码问题,直接传递Unicode类型字符串作为write的参数,会导致UnicodeEncodeError错误。 知道了问题的根源,首先想到的解决方法就是对源字符串按照文件进行编码,保证编码正确。

#解决方法1
with open('test.txt', 'w') as f:
	f.write('测试'.decode('utf-8').encode('gb2312'))
	f.write(u'测试‘.ecode('gb2312')

如果是str类型的字符串,需要使用decode(因为我在脚本中设定#coding=utf8,所以使用decode(‘utf-8’))将其改变为python内部使用的Unicde编码然后使用encode转换成对应的编码类型。
通过以上的例子可以看出,python2的open接受的参数是,函数写入的是编码后的str对象,读出的也是编码后的str对象。python2的open函数在读写文件时不能指定编码方式

8.2 codecs的open()函数

读写操作都需要进行编码转换是个容易导致错误而且烦人的问题,python中提供了codecs这个内置自然语言处理模块方便我们进行不同编码语言的处理,codecs模块的open方法可以指定encoding参数设定文件的编码格式,以后codecs会自动处理文件的读写编码问题,读取的字符串和写入时的字符串参数统一使用python的Unicode类型。使用codecs的open方法代替原来的open方法发可以摆脱烦人的文件编码问题。

#coding:utf-8
#写如文件
import codecs
with codecs.open('test.txt', 'w', encoding='utf-8') as f:
    f.write(u'测试1')
    f.write('测试2') #UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)

写入结果:
在这里插入图片描述

#coding:utf-8
#读取文件
import codecs
with codecs.open('test.txt', encoding='utf-8') as f:
    for line in f:
        print line, type(line) # output: 测试<type 'unicode'>

可以看到写如文件是时,codecs可以指定文件的编码方式,如果传入的是unicode编码的字符串,codecs会将字符串编码成encoding参数指定的编码格式,然后写入文件。如果是str对象,会先使用python默认的编码方式ascii解码成unicode对象,然后codecs会将字符串编码成encoding参数指定的编码格式,然后写入文件。写入文件时f.write('测试2')之所以会报错就是默认的解码方式和编码的方式不匹配,使用第6部分中的方法,设置sys.defaultencoding(‘utf-8’)可以解决上述错误。或者使用f.write('测试2').decode('utf-8')

通过上面的例子可以看到,写入时传给codecs的open函数的参数是unicode对象,如果不是的话,python会做默认转换,默认转换不成功的会报错。 codecs将接受到的unicode对象按照encoding参数指定的编码格式编码后存储到文件。
读取时,python会将读取到编码后的字符串按照encoding参数指定的编码格式解码成unicode对象。所以可以说读取时codecs的open函数返回的是unico的对象,写入时接受的也是unicode对象。

9. 使用print函数输出字符串

#coding:utf-8
s1 = '测试1'
print s1
s2 = u'测试2'
print s2
s3 = '测试3'
print type(s3.decode('utf-8'))
print s3.decode('utf-8')

s4 = '测试4'
print s4.decode('utf-8').encode('gbk')
print s4.decode('utf-8').encode('gbk').decode('gbk')
print s4.decode('utf-8').encode('gbk').decode('gbk').encode('utf-8')

输出结果如下:
在这里插入图片描述

#coding:gbk
s1 = '测试1'
print s1
print s1.decode('gbk').encode('utf-8')
s2 = u'测试2'
print s2
s3 = '测试3'
print type(s3.decode('gbk'))
print s3.decode('gbk')

在这里插入图片描述

通过以上测试可知,print 语句可以输出unicode对象和使用utf-8编码的str对象。

参考文章:

  1. python读写文件,和设置文件的字符编码比如utf-8
  2. 解决python2.x文件读写编码问题
  3. python中的编码与解码
  4. [python基础]关于中文编码和解码那点事儿
  5. python3.0 之 深入理解 print()
  6. python学习番外篇之print输出函数用法及原理总结
  7. python中sys.setdefaultencoding(‘utf-8’)的作用
  • 18
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Python面向对象编程(Object-Oriented Programming,简称OOP)是一种程序设计方法,它将数据和操作数据的方法组合成对象,通过定义类(class)来创建对象。下面是一些概念和原则,可以帮助你更好地理解Python面向对象编程。 1. 类和对象: - 类是一种抽象的数据类型,它定义了对象的属性和方法。 - 对象是类的实例,它具有类定义的属性和方法。 2. 属性和方法: - 属性是对象的数据,可以是整数、字符串、列表等。 - 方法是对象的行为,可以是函数或过程。 3. 封装: - 封装是将数据和对数据的操作封装在一起,以创建一个独立的实体。 - 使用类来封装数据和方法,可以隐藏实现细节,提高代码的可读性和可维护性。 4. 继承: - 继承是一种机制,允许一个类继承另一个类的属性和方法。 - 子类可以重用父类的代码,并且可以添加新的属性和方法。 5. 多态: - 多态是指同一个方法可以在不同的类中具有不同的实现方式。 - 多态可以提高代码的灵活性和可扩展性。 下面是一个简单的例子,展示了如何定义一个类、创建对象并调用对象的方法: ```python class Person: def __init__(self, name, age): self.name = name self.age = age def say_hello(self): print(f"Hello, my name is {self.name} and I'm {self.age} years old.") # 创建对象 person = Person("Alice", 25) # 调用对象的方法 person.say_hello() ``` 这个例子定义了一个名为`Person`的类,它有两个属性(`name`和`age`)和一个方法(`say_hello`)。我们通过`Person`类创建了一个名为`person`的对象,并调用了它的`say_hello`方法。 希望这个简单的例子能帮助你更好地理解Python面向对象编程。如果你有其他问题,请随时提问。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值