在学习python2的时候,字符串和编码可以说是最让人困惑的知识点,假如知其然而不知其所以然,则在后续的写代码和学习过程中会让人很痛苦,甚至会放弃,而对比PHP语言来说,即使完全不了解编码等知识,也可以写出代码,这是幸事,但反过来说太透明会让你失去很多能力.
python2字符串和编码难理解的原因在于,一方面很多书籍很少说这方面的知识,另外一方面是python设计导致的,编码问题和文件编码,系统环境,IO操作等都有关系,混杂在一块很让人头疼.
网络上也有很多中文资料去说明,但是在学习的时候只能借鉴,原因在于写的人理解的也是比较片面,很容易误导人,所以在学习过程中一定要去实践,要仔细琢磨.
自己综合学习了下,以自己的方式写了篇博客,能力有限,希望不要误导人.
编码
对于编码个人觉得理解概念即可,具体的转换规则,存储规则可以不用太仔细了解,这类似于进制,知道概念即可,不强制掌握进制转换的方法.
讲编码的文章很多,掌握以下概念即可.
世界上任何一个字符都可以用一个Unicode编码来表示,一旦字符的Unicode编码确定下来后,就不会再改变了,但是unicode存在二个局限性,第一一个Unicode字符在网络上传输或者最终存储起来的时候,并不见得每个字符都需要两个字节,所以可能会造成空间浪费,第二一个Unicode字符保存到计算机里面时就是一串01数字,那么计算机怎么知道一个2字节的Unicode字符是表示一个2字节的字符呢,还是表示两个1字节的字符呢.
Unicode只是规定如何编码,并没有规定如何传输、保存这个编码.
例如“汉”字的Unicode编码是6C49,可以用4个ascii数字来传输、保存这个编码,也可以用utf-8编码的3个连续的字节E6 B1 89来表示它,关键在于通信双方都要认可.
因此Unicode编码有不同的实现方式,比如:UTF-8、UTF-16等等
python下的编码
python2对于编码理解困难,很大一部分原因在于系统有很多编码,这里说明下
#windows环境和linux环境下的区别
sys.getdefaultencoding()
sys.getfilesystemencoding()
locale.getdefaultlocale()
locale.getpreferredencoding()
sys.stdout.encoding()
-
sys.getdefaultencoding()
不管在何种环境下返回都是ascii,所以默认情况下转码解码默认都是ascii - 对于str类型,
locale.getdefaultlocale()
决定了具体的编码格式.具体见下面说明 -
sys.stdout.encoding
表示输出使用的编码,同样的文件编码,同样的代码,不同的系统环境输出是有差异的
最佳实践
文件本身的编码和文件头编码(# coding=utf-8
)保持一致
Python2中str和unicode对象
首先声明下,自己运行的代码在windows和linux环境各有一份示例,且通过python交互式解析器来说明.
python解析器不用用户定义编码头,所以内部处理依赖于locale
环境.
在windows机器运行
>>> import locale
>>> locale.getdefaultlocale()
('zh_CN', 'cp936')
在linux机器运行
>>> import locale
>>> locale.getdefaultlocale()
('en_US', 'UTF-8')
在python中和字符串相关的数据类型,分别是str、unicode两种,他们都是basestring的子类.
在python代码中定义str,unicode类型,解析器是如何解析的呢
- 读出文件内容
- 将内容根据文件编码解码成为unicode
- 解析unicode字符串,假如定义是
u
开头,创建一个unicode对象 - 解析str字符串,将会从unicode按照文件编码再编码成为str对象
通过代码看看字符串在内部是如何存储的
str类型
#windows
>>> a="哈哈"
>>> type(a)
<type 'str'>
>>> a
'\xb9\xfe\xb9\xfe'
>>> len(a)
4
>>> a[1]
'\xfe'
#linux
>>> a = '哈哈'
>>> type(a)
<type 'str'>
>>> a
'\xe5\x93\x88\xe5\x93\x88'
>>> len(a)
6
>>> a[1]
'\x93'
str存储的是已经编码后的字节序列,输出时看到每个字节用16进制表示,以\x开头,
linux环境下每个汉字会占用3个字节的长度,windows环境下每个汉字会占用2个字节的长度
unicode类型
#linux和windows环境一样
>>> a=u'哈哈'
>>> type(a)
<type 'unicode'>
>>> a
u'\u54c8\u54c8'
>>> len(a)
2
>>> a[1]
u'\u54c8'
unicode是"字符"
串,存储的是编码前的字符,输出是看到字符以\u开头,每个汉字占用一个长度
通过上述可以看出:
- 定义unicode和系统环境没有联系,存储的是以u开头的unicode字符集
- str类似于字符数组,str类型定义内部存储则和系统环境有关系,假如系统环境是utf-8则存储utf-8规则的字符数组,假如系统环境是cp936则存储cp936规则的字符数组.
- str类型不要使用len这样的函数,因为截取出来可能就是所谓的乱码了.
python2中str和unicode如何转换
既然同时存在str和unicode类型,则就涉及到二者的转换了.
先说基本概念
- str = unicode.encode(字符编码),从unicode转换成指定编码的str对象
- unicode = str.decode(字符编码),特指从指定编码的str对象转换为unicode对象
注意:
- str转换为unicode的时候,必须知道原有字符串编码是什么类型的,假如指定错误则会报错
- str从一种编码转换为另外一种编码的时候,必须先转换为unicode,再转换成指定编码的str类型
一般情况下,不应该同时定义str和unicode类型,尽量使用unicode类型,假如都统一使用unicode类型,那为什么还要出现str类型呢,在python2中,一般在I/O操作的时候才会有编码转换,这在后面描述.
print字符串发生了什么
任何对象都默认包含内建方法str,在print的时候,该方法生效
假如print unicode对象,则根据默认编码解码为str对象.
假如print str对象,由于输出就是str对象,默认不用做任何解码.
看下面的例子,注意这里是通过python file.py的方式运行,在windows下运行是乱码,而在linux下运行显示正确,原因在于sys.stdout.encoding
#!/usr/bin/env python
#coding=utf-8
a = '哈哈'
print a
sys.stdout.encoding
表示print输出使用的编码.
在linux环境下,由于输出的编码本来就是utf-8,所以能正确显示
在windows环境下,str字符串存储的是utf-8序列,显示要求的却是gbk,则出现乱码,所以需要转码,修改如下:
#!/usr/bin/env python
#coding=utf-8
a = '哈哈'
print a.decode('utf-8').encode('gbk') #在内部存储gbk类型的str字符串
而定义uinicode对象的时候,不会涉及任何的转码问题,print的时候,unicode对象能够根据文件编码自动转换
以下代码在任何环境下都能正常运行
#!/usr/bin/env python
#coding=utf-8
a = u'哈哈'
print a
IO操作发生了什么
正因为有IO操作,str类型的对象可能才有存在的意义.或者说假如没有可恶的str对象,则世界就太平了.
内置的open函数打开文件时,read方法读取的是一个str,用你知道的编码把它解码成unicode
open函数打开文件之后的写操作,则需要将需要写入的字符串按照其编码encode为一个str.
是不是很熟悉,print语句输出和文件打开一样都是str类型,尽可能处理的时候抛弃str,确保处理的对象是unicode,只在需要的时候才转码.
通过下面的代码就能明白繁琐的转码和解码操作
#!/usr/bin/env python
#coding=utf-8
import os
def filewrite():
file1 = os.getcwd() + "\1.txt"
file2 = os.getcwd() + "\2.txt"
str= u'我们是中国人'
f = open(file1, "a")
f.write(str.encode('gbk'))
f.close()
f2 = open(file2, "a")
f2.write(str.encode('utf-8'))
f2.close()
def fileread():
file1 = os.getcwd() + "\1.txt"
file2 = os.getcwd() + "\2.txt"
f= open(file1,"r")
data = f.read()
print(data)
f= open(file2,"r")
data = f.read()
print(data.decode('utf-8'))
filewrite()
fileread()
IO操作使用codecs
codecs模块也提供了一个open函数,可以直接指定好编码打开一个文本文件,那读取到的文件内容则直接是一个unicode字符串.对应的指定编码后的写入文件,则可以直接将unicode写到文件中
#!/usr/bin/env python
#coding=utf-8
import codecs
import os
def codecswrite() :
file3 = os.getcwd() + "\3.txt"
str = u'哈哈'
f = codecs.open(file3,"w",'utf-8')
f.write(str)
f.close()
def codecsread():
file3 = os.getcwd() + "\3.txt"
f = codecs.open(file3,"r",'utf-8')
data = f.read()
print (data)
codecswrite()
codecsread()
字符串拼接发生了什么
unicode和str类型通过+拼接时,输出结果是unicode类型,相当于先将str类型的字符串通过decode()方法解码成unicode再拼接
#windows环境,python交互式运行
>>>a="中国"
>>>b=u"你好"
>>>a+b
会出现UnicodeDecodeError: 'ascii' codec can't decode byte 0xd6 in position 0: ordinal not in range(128)
错误,原因在于python自动将str类型的变量按照默认的编码格式sys.getdefaultencoding()来解码,默认编码即ascii,而这个字符不在ascii的范围内,就出现了错误,所以需要修改如下
#windows环境,python交互式运行
>>>a="中国"
>>>b=u"你好"
>>> a.decode('gbk')+b
u'\u4e2d\u56fd\u4f60\u597d'
让我们轻松下
这里看下python中的字符串的处理方式,其实在学习的时候和PHP比较下更让人有印象
单引号,双引号,转义
python中,字符串可以用单引号和双引号括起来,没有区别.
假如字符串用单引号括起来,则字符串中则不能有单引号,除非通过转义去处理,同理双引号也一样
str和repr
>>> "hello"
'hello'
>>> print "hello"
hello
通过python打印的字符串会被双引号括起来,这是因为python打印值的时候会保持该值在python代码中的状态.
而通过python语句则结果不一样.
这里就涉及到值被转换为字符串的两种机制:
str函数
:把值转换为合理形式的字符串,以便人类能够理解.
repr函数
:把值转换为python能够认识的值.
>>> print repr("hello")
'hello'
>>> print repr(1000L)
1000L
>>> print str("hello")
hello
>>> print str(1000L)
1000
input和raw_input
input假设输入的是合法的python表达式,不然会报错
>>> name=input("please:")
please:hello
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <module>
NameError: name 'hello' is not defined
应该变更为:
>>> name=input("please:")
please:"hello"
>>> print name
hello
而raw_input函数则将所有的输入当作原始数据
>>> name=raw_input("please:")
please:hello
>>> print name
hello
长字符串
假如要写多行的字符可以使用三个引号
str='''hello
world str
'''
print(str)
引号之间的内容原本是什么样输出也是什么样的,可以直接使用单双引号,不用转义
普通字符也可以跨行,只要一行之中最后一个字符是反斜线,那么换行就转义了
str='hello\
world'
print(str)
原始字符串
原始字符串对于反斜线不会特殊对待
str=r'hello\nworld'
print(str)
\n
字符在原始字符串里面就不是换行了而是原始的\n字符
推荐文章: