python中理解字符串和编码为什么这么难

在学习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字符

推荐文章:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容