【转载】python2与python3字符串的区别

原文出处:https://my.oschina.net/sallency/blog/1563298

python2

在 py2 中,有两种类型字符串:str 和 unicode。但严格的来说,str 并不是完全意义上的字符串,把它称作 字节码串更合适。unicode 则作为真正意义上的 字符串,但定义时需要使用 u"" 去声明。有人可能会有疑问,字符串 在内存单元中也是以字节进行存储的,怎么能将他俩区别对待呢?

字符串 是一种 数据结构。数据结构 = 原数据 + 数据描述(为了更容易理解),而字符串的 “数据描述” 即为何种字符集。

字节码 则是没有数据描述的。

py2 中,当我们读取 str 类型时,它只代表了一串数据,至于这串数据是何种编码,系统是不知道的,需要我们手动指定。而 unicode 类型,则存储的相应的字符集,系统在读取时便能根据此数据描述对数据进行识别和处理。

'\xc4\xe3\xba\xc3' 对计算机来说,就是 1 个字节的字节码串在一起。

u'\u4f60\u597d' 对计算机来说,则是每 2 个字节作为一个单位,去映射 unicode 字符集做运算。

image

当我们定义 str字符串 时,python 会根据我们当前的运行上下文:源文件 / cli 去设定 str 的字符集。比如我们在脚本开始处注释的 coding: utf-8 会将脚本中的 str 编码为 utf-8 的字节码串儿。

# -*- coding: utf-8 -*-

import sys

"""
作用是注明脚本上下文的字符集
比如当我们定义一个 str 类型的数据时
它是 utf-8 字符集的字节码
"""
str = "你好" #以 utf-8 字节码去编码

"""
但不要和 python 的默认字符集搞混
默认字符集会在一些隐式加解码时被运用
"""
# ascii
print sys.getdefaultencoding()

# 设置 python 的默认字符集
reload(sys)
sys.setdefaultencoding("utf-8")

"""
下面的代码存在一次隐式的 decode
在隐式或没有指定 decode 的字符集时
会使用 默认字符集(这里已将默认字符集设为 utf-8)进行解码
如果我们没有修改默认字符集而使用 ascii 的话
就会报错了 ascii 是没办法编码中文的
"""
"你好".encode("gb2312")

"""
py2中 write 写的为字节码串
当前源码编码字符集为 utf-8
所以写入时 "你好" 是以 utf-8 编码的
"""
with open("encode.txt", "w") as f:
    f.write("你好")

"""
读取的 str 也为 utf-8 的字节码串
"""
with open("encode.txt", "r") as f:
    str = f.read()

例如我的 cmd 是 gb2312 字符集,所以 "你好" 的字节码串如下(gb2312 一个中文字符用 2 个字节标识)。下面我们将其转换为 utf-8 字符集的字节码:

image

encode/decode 下面详细讲解。

python3

py3 则对字符串和字节码进行了更为规范的定义:str 和 bytes。str 终于成为了真正意义上的字符串,bytes 也形象的表征了字节码串儿(java 中的字节数组)

py3 默认支持 unicode 字符集,故不需要鸡肋的使用 py2 中的 u"xxx" 去定义一个 unicode 字符串。

简单来说,py2 想定义一个日常的字符串,需要手动 u"xxx","xxx" 定义的是当前编码设定下的字节码串儿,典型的本末倒置。在 py3 中,不再有 u"xxx" 的语法,"xxx" 定义的就是字符串(unicode编码),b"xxx" 定义的才是字节码串儿。

"""
py3 中消除了py2中字节码和字符串混乱的定义
str 就是字符串 bytes 才是字节码
在 py2 中 str 是字节码 unicode 才是字符串
"""
        py3        py2
字节码  bytes      str
字符串  str       unicode

所以在 py3 中,你的 coding: utf-8 的注释没有任何用处了。在 py2 中它会隐式的根据指定编码对 str 做字节码的定义,但在 py3 中已经没有这种隐式的转换和定义了。

import sys

# 默认字符集由 ascii 改为 utf-8
print("默认字符集为:%s" % (sys.getdefaultencoding()))

str = "你好"

# 2 个字符长度 py2 中就不会把 str 看为数据结构 而是字节码 所以结果会是 6
print(len(str))

# 不指定字符集将使用默认的 utf-8 进行编码
str.encode() # b'\xe4\xbd\xa0\xe5\xa5\xbd'
str.encode("utf-8") # b'\xe4\xbd\xa0\xe5\xa5\xbd'
str.encode("gb2312") # b'\xc4\xe3\xba\xc3'

hello_utf8   = b'\xe4\xbd\xa0\xe5\xa5\xbd'
hello_gb2312 = b'\xc4\xe3\xba\xc3'

# utf-8 转 gb2312 / gb2312 转 utf-8
# 都是以 unicode 作为基准进行操作
hello_utf8.decode("utf-8").encode("gb2312")
hello_gb2312.decode("gb2312").encode("utf-8")

encode/decode

py 以 unicode 作为字符运算的基准。

bytes -- decode -- unicode -- encode-- bytes

unicode编码 至其他字符集(utf-8/gb2312),其他字符集 解码 至 unicode

在 py2 中经常遇到的错误:

*"你好".encode("utf-8")*

*UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 0: ordinal not in range(128)*

错误的原因是什么呢?py2 以 ascii 作为默认字符集,"你好" 直接 encode 至其他编码时会进行一次隐式的解码。因为 py 以 unicode 作为运算基准,所以会先 decode 至 unicode,你没使用 py 就隐式执行,使用默认字符集 ascii 做解码,自然无法解码中文字符的字节码。

而在 py3 中是不允许这种操作的。从 bytes 到另一 bytes 你必须先 decode 后才能 encode。

open/read/write

py2 中,open 方法并不会设定以何种字符集去打开文件,因为 py 要的不是字符串,而是字节码串儿。是的,read 和 write 操作的都是字节码串儿,字节码串儿本身并不具备表征字符的能力,我们需要手动的指定正确的字符集进行解码。

# write.py
# -*- coding: utf-8 -*-
with open("hello.txt", "w") as f:
    f.write("你好") # 这里其实是字节码

# read.py
# -*- coding: gbk -*-
# 结果为 6 仍然是以 utf-8 的字节码去计算的
with open("hello.txt", "r") as f:
    str = f.read()
    print len(str) # 读取的也是字节码

数据会以相应的字节码去保存

py3 中,因为存储时传入的是 unicode 字符串(而不再是 py2 的字节码),但数据存储肯定是以字节码的形式(物理存储)。所以 py3 肯定会将字符串转为字节码(这个过程是py3自己完成的,我们此时可是指定以何种字符集去编码),这时就涉及采用何种编码去进行隐式的 encode 存储和 decode 读取了。

中文系统,使用 python xxx.py 时上下文的编码为操作系统的编码:gbk。这里挺饶的,虽然我是在 gbk2312 的 cmd 里运行的,但并非是交互模式,所以输入时并非使用的 cmd 的字符集,而是 python 解释器启动时读取的本地系统的字符集:gbk。

#-*-coding: utf-8 -*-
"""
py3 的默认字符集为 utf-8 所以日常开发中我们是可以省略 encoding 的
"""
with open("hello.txt", "w", encoding = "utf-8") as f:
    f.write("你好")

with open("hello.txt", "r", encoding = "utf-8") as f:
    print(f.read())

# -*-coding: utf-8 -*- 就算我声明了此行注释
# py3 依然不会使用其作为 open 打开文件时默认的 encoding
# 也不会使用 py3 自身默认的 sys.getdefaultencoding
# 这里 open 的 encoding 默认会与你的本地系统相关
# 所以下面我需要使用 gbk 返回字节码,重新使用 utf-8 正确解码
with open("hello.txt", "r") as f:
    print(f.read().encode("gbk").decode('utf-8')) #

# 以 b(二进制) 方式读取时为读取的字节码串儿
# 所以数据并不会受编码影响
# 我们使用时需要指定正确的解码字符集
with open("hello.txt", "rb") as f:
    bytes = f.read()
    print(bytes)
    print(len(bytes)) # 6 utf-8 的字节码 一个中文3个字节
    print(bytes.decode("utf-8")) #正常解码 获得 "你好" unicode 字符串
    # bytes.decode("gb2312") #错误解码

所以,coding: utf-8 貌似已经没什么用处了

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

推荐阅读更多精彩内容