首页 > 代码库 > python字符编码惯用法
python字符编码惯用法
本文总结在实际应用中遇到的python字符编码问题,制定一套编码相关的约定,避免编码上的错误。
在写猥琐宝典时需要总结soj上做过的题,准备在总结过程中顺便写一个soj上的题解。题解使用python可读,也就是python可以直接eval的格式,以便于处理。写题解老是copy soj上的题目id,title不是太方便,所以就准备自动生成一个空的题解,里面包含了我做过的题。然而直接从soj上只能拿到自己过了的题的id列表,缺乏其它信息。缺乏的信息可以抽象为soj数据库,其中包含了一个以id为主键的表,表中有题目所有的信息。于是代码分为两部分,一部分是soj的工具,其中包含了数据库操作,根据id获取AC的题目列表,另一部分是基于soj工具,负责题解数据的操作。
学python比较晚,当时面临python2和python3的选择,看过一些区别,感觉python3的设计更加合理,而python2比较随意。尤其是字符串部分,python2的概念不清楚,把字节的序列和字符串混在一起,导致一些混乱。首先用python3写的工具,然后想把代码作为web后台,把结果以网页的形式展示出来,这就涉及到python2了,因为在生产环境中还是以python2为主。在转为python2的过程中,不得不重新审视一下字符串的部分,然后总结出这篇文章。
在python2中使用string和unicode string类型,而在python3中则使用bytes和string类型,他们对等:python2.string = python3.bytes, python2.unicode string = python3.string。在python2中,感觉字符串相关东西混乱的原因在于命名失误:以偏概全,用的是string的名表达的是bytes的概念。
bytes的概念:
bytes表达了字节为单位的序列,数据本身的意义很有限,只有当这些数据是按一定规则组织的,进而表达了某个概念,才存在编码问题,数据才有意义。
比如我们可以认为bytes每4字节表示一个32位整数,其中整数每连续8位作为一个字节放在一起,且低位放在前面,这样的bytes就是有编码的,表达了32位整数的概念。
同样的我们也可以认为bytes表示了utf8编码的字符串,每1到6个字节对应一个unicode字符,我们称bytes具有utf8编码,表达了字符串的概念。
string的概念:
如果要表达字符串概念,[Char]才是比较准确的(字符的列表)。至于一个Char占用多少存储,完全不知道。可以是utf16,用2或4字节表示一个Char,也可以是utf32用4字节表达一个Char,还可以是utf8用1到6字节来表示一个Char,这是实现者的事,不应该对我们的使用产生任何影响。
所以,我们需要bytes用来表示string时,需要指定编码,将string转为bytes,对应的函数是encode。当我们认为bytes具有某个字符编码表达的是一个字符串的时候,通过decode并指定该编码得到string。严格地说来在python2中,我们不应该在string对象上调用encode方法,不应该在unicode string对象上调用decode方法。
更进一步,任何抽象都可以encode得到对应的bytes,通过decode得到对应的抽象。
在上述的基础上,引入一些编码约定,目标是避免编码错误。
1.源代码编码,这个编码通过#coding:xxx指出,在python2和python3中概念都很明确。
惯用法(约定):
源代码只使用utf8编码。
2.string literal的类型和编码:
python2中一个形如"xxxx"的string literal具有string类型,编码和源代码编码一致。
而形如u"xxxx"的string literal具有unicode string类型,存在一个自动的源代码编码向unicode string decode的过程。无法转换时会报错,这个时候需要查通过注释指出的编码以及源代码的真实编码。
python3中一个形如"xxxx"的string literal具有string类型,此时,存在自动的源代码编码向unicode的decode过程。
而b"xxxx"的string literal具有bytes类型,表示了源代码的一部分。所以,当其中内容是字符串时,我们也称之是一个和源代码编码一致的字符串。
惯用法:
python2中用string来表达字符串的概念,并使用utf8编码。而python2中unicode string和string混用时,string被认为具有源代码编码,并decode为unicode string。于是存在陷阱,使用的变量的类型失去控制,不知道是string还是unicode string,所以在这里要特别注意。如果用string表达字符串的概念,同时用其它编码,也是可以的,主要是看编码对应的字符集和应用是不是能很好结合在一起。
而python3则string来表达字符串的概念,不关心编码问题。python3不存在混用问题,bytes和string一结合使用,就会报错。
3.urlopen(xxx).read()后的处理:
很明确,这里返回的概念是python3.bytes。
如果确定返回的东西是一个网页的文本,我们可以调用decode(encoding=‘网页编码‘, errors=‘ignore‘)来得到对应的字符串。
但是,注意2中python2的约定,我们的字符串的类型是python2.string的,所以在python2中我们还要有一个encode的过程:encode(encoding=‘源代码编码‘, errors=‘ignore‘)
4.写数据文件:
在这里写的是一个特殊的文件,文件可以被看成是一段python代码并执行,所以:
惯用法:
文件编码使用utf8。
在python2中
with open(file, ‘wb‘) as tempf:
tempf.write(data)
with open(file, ‘w‘) as tempf:
tempf.write(data)
都是可以的,因为string无法区别是字节还是字符串。
而在python3中则要使用:
with open(file, ‘wb‘) as tempf:
tempf.write(data.encode(encoding=‘utf8‘,errors=‘ignore‘))
或
with open(file, ‘w‘) as tempf:
tempf.write(data)
因为前者写入的是bytes而后者是string。
上述可行性是建立在我们约定数据文件和源代码编码都是utf8的情况下得到的,如果没有这些约定,我们看看这些代码的语义:
python2中两份代码语义有点乱:
如果已知data是一个字符串,那么第一份代码理论上错误,但是实际上两份代码都完成了把data写到文件,数据文件编码和data一致的目标。
如果已知data是一个二进制流,那么第二份代码理论上错误,但是实际上两份代码也都完成任务,数据文件不存在编码问题。
python3中第一份代码的语义是,写入data这个字符串,数据文件编码是utf8。
而第二份代码语义是:写入data这个字符串,数据文件编码和当前源文件编码(在这是认为默认编码等于当前源文件编码)一致。
5.读数据文件:
惯用法:
在python2中
with open(file, ‘rb‘) as tempf:
tempf.read()
和
with open(file, ‘r‘) as tempf:
tempf.read()
均可,因为前者返回一些字节,但是用的是string作容器。而后者返回的还是string。
而在python3中则要用:
with open(file, ‘rb‘) as tempf:
tempf.read().decode(encoding=‘utf8‘,errors=‘ignore‘)
或
with open(file, ‘r‘) as tempf:
tempf.read()
因为打开的模式不同,前者返回了bytes,后者返回的是string(在内部尝试bytes尝试用源文件encoding解码)。
同样的这些代码的可行性也是建立在若干约定的基础上,如果没有这些约定,也来看看其语义:
python2中第一份代码的语义是读回一些二进制数据。
python2中第二份代码的语义是读入一些字符串。(理论上会有一个先读二进制再根据当前默认编码decode然后再向当前默认编码encode的过程,但是在这里认为两个变换是恒等的,所以没动作。显然,在出现非法字符的情况下,两个变换不恒等。)
python3中第一份代码的语义是读入二进制数据,根据utf8进行decode。
python3中第二份代码的语义是隐式地读入二进制数据,根据当前默认编码进行decode。
在我的应用场景里,读回的数据还有个eval的过程。显然,python2直到eval才可能出现编码错误,而python3能更早发现错误。
在写猥琐宝典时需要总结soj上做过的题,准备在总结过程中顺便写一个soj上的题解。题解使用python可读,也就是python可以直接eval的格式,以便于处理。写题解老是copy soj上的题目id,title不是太方便,所以就准备自动生成一个空的题解,里面包含了我做过的题。然而直接从soj上只能拿到自己过了的题的id列表,缺乏其它信息。缺乏的信息可以抽象为soj数据库,其中包含了一个以id为主键的表,表中有题目所有的信息。于是代码分为两部分,一部分是soj的工具,其中包含了数据库操作,根据id获取AC的题目列表,另一部分是基于soj工具,负责题解数据的操作。
学python比较晚,当时面临python2和python3的选择,看过一些区别,感觉python3的设计更加合理,而python2比较随意。尤其是字符串部分,python2的概念不清楚,把字节的序列和字符串混在一起,导致一些混乱。首先用python3写的工具,然后想把代码作为web后台,把结果以网页的形式展示出来,这就涉及到python2了,因为在生产环境中还是以python2为主。在转为python2的过程中,不得不重新审视一下字符串的部分,然后总结出这篇文章。
在python2中使用string和unicode string类型,而在python3中则使用bytes和string类型,他们对等:python2.string = python3.bytes, python2.unicode string = python3.string。在python2中,感觉字符串相关东西混乱的原因在于命名失误:以偏概全,用的是string的名表达的是bytes的概念。
bytes的概念:
bytes表达了字节为单位的序列,数据本身的意义很有限,只有当这些数据是按一定规则组织的,进而表达了某个概念,才存在编码问题,数据才有意义。
比如我们可以认为bytes每4字节表示一个32位整数,其中整数每连续8位作为一个字节放在一起,且低位放在前面,这样的bytes就是有编码的,表达了32位整数的概念。
同样的我们也可以认为bytes表示了utf8编码的字符串,每1到6个字节对应一个unicode字符,我们称bytes具有utf8编码,表达了字符串的概念。
string的概念:
如果要表达字符串概念,[Char]才是比较准确的(字符的列表)。至于一个Char占用多少存储,完全不知道。可以是utf16,用2或4字节表示一个Char,也可以是utf32用4字节表达一个Char,还可以是utf8用1到6字节来表示一个Char,这是实现者的事,不应该对我们的使用产生任何影响。
所以,我们需要bytes用来表示string时,需要指定编码,将string转为bytes,对应的函数是encode。当我们认为bytes具有某个字符编码表达的是一个字符串的时候,通过decode并指定该编码得到string。严格地说来在python2中,我们不应该在string对象上调用encode方法,不应该在unicode string对象上调用decode方法。
更进一步,任何抽象都可以encode得到对应的bytes,通过decode得到对应的抽象。
在上述的基础上,引入一些编码约定,目标是避免编码错误。
1.源代码编码,这个编码通过#coding:xxx指出,在python2和python3中概念都很明确。
惯用法(约定):
源代码只使用utf8编码。
2.string literal的类型和编码:
python2中一个形如"xxxx"的string literal具有string类型,编码和源代码编码一致。
而形如u"xxxx"的string literal具有unicode string类型,存在一个自动的源代码编码向unicode string decode的过程。无法转换时会报错,这个时候需要查通过注释指出的编码以及源代码的真实编码。
python3中一个形如"xxxx"的string literal具有string类型,此时,存在自动的源代码编码向unicode的decode过程。
而b"xxxx"的string literal具有bytes类型,表示了源代码的一部分。所以,当其中内容是字符串时,我们也称之是一个和源代码编码一致的字符串。
惯用法:
python2中用string来表达字符串的概念,并使用utf8编码。而python2中unicode string和string混用时,string被认为具有源代码编码,并decode为unicode string。于是存在陷阱,使用的变量的类型失去控制,不知道是string还是unicode string,所以在这里要特别注意。如果用string表达字符串的概念,同时用其它编码,也是可以的,主要是看编码对应的字符集和应用是不是能很好结合在一起。
而python3则string来表达字符串的概念,不关心编码问题。python3不存在混用问题,bytes和string一结合使用,就会报错。
3.urlopen(xxx).read()后的处理:
很明确,这里返回的概念是python3.bytes。
如果确定返回的东西是一个网页的文本,我们可以调用decode(encoding=‘网页编码‘, errors=‘ignore‘)来得到对应的字符串。
但是,注意2中python2的约定,我们的字符串的类型是python2.string的,所以在python2中我们还要有一个encode的过程:encode(encoding=‘源代码编码‘, errors=‘ignore‘)
4.写数据文件:
在这里写的是一个特殊的文件,文件可以被看成是一段python代码并执行,所以:
惯用法:
文件编码使用utf8。
在python2中
with open(file, ‘wb‘) as tempf:
tempf.write(data)
with open(file, ‘w‘) as tempf:
tempf.write(data)
都是可以的,因为string无法区别是字节还是字符串。
而在python3中则要使用:
with open(file, ‘wb‘) as tempf:
tempf.write(data.encode(encoding=‘utf8‘,errors=‘ignore‘))
或
with open(file, ‘w‘) as tempf:
tempf.write(data)
因为前者写入的是bytes而后者是string。
上述可行性是建立在我们约定数据文件和源代码编码都是utf8的情况下得到的,如果没有这些约定,我们看看这些代码的语义:
python2中两份代码语义有点乱:
如果已知data是一个字符串,那么第一份代码理论上错误,但是实际上两份代码都完成了把data写到文件,数据文件编码和data一致的目标。
如果已知data是一个二进制流,那么第二份代码理论上错误,但是实际上两份代码也都完成任务,数据文件不存在编码问题。
python3中第一份代码的语义是,写入data这个字符串,数据文件编码是utf8。
而第二份代码语义是:写入data这个字符串,数据文件编码和当前源文件编码(在这是认为默认编码等于当前源文件编码)一致。
5.读数据文件:
惯用法:
在python2中
with open(file, ‘rb‘) as tempf:
tempf.read()
和
with open(file, ‘r‘) as tempf:
tempf.read()
均可,因为前者返回一些字节,但是用的是string作容器。而后者返回的还是string。
而在python3中则要用:
with open(file, ‘rb‘) as tempf:
tempf.read().decode(encoding=‘utf8‘,errors=‘ignore‘)
或
with open(file, ‘r‘) as tempf:
tempf.read()
因为打开的模式不同,前者返回了bytes,后者返回的是string(在内部尝试bytes尝试用源文件encoding解码)。
同样的这些代码的可行性也是建立在若干约定的基础上,如果没有这些约定,也来看看其语义:
python2中第一份代码的语义是读回一些二进制数据。
python2中第二份代码的语义是读入一些字符串。(理论上会有一个先读二进制再根据当前默认编码decode然后再向当前默认编码encode的过程,但是在这里认为两个变换是恒等的,所以没动作。显然,在出现非法字符的情况下,两个变换不恒等。)
python3中第一份代码的语义是读入二进制数据,根据utf8进行decode。
python3中第二份代码的语义是隐式地读入二进制数据,根据当前默认编码进行decode。
在我的应用场景里,读回的数据还有个eval的过程。显然,python2直到eval才可能出现编码错误,而python3能更早发现错误。
python字符编码惯用法
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。