【Python】中的bytes问题

bytes是什么

由上篇文章几种字符编码, 我们已经知道了ASCII Unicode UTF-8的关系。而且,计算机只能识别0和1,那显然,文件存储在计算机中也只能是以二进制的形式存储,字符编码在计算机中的工作机制是怎样的呢?

在计算机内存中(你打开电脑上的一个文件是要从硬盘读取到内存中的),统一使用Unicode编码。在需要保存到硬盘或需要传输时,就转化为UTF-8编码(由上篇文章可知,这样可以节省空间,提高传输速度)。

如,在记事本编辑时,从文件读取的UTF-8字符被转化为Unicode字符到内存里,编辑完成,保存时在将内存中的Unicode字符转化为UTF-8保存到文件:

mark

浏览网页时,服务器会把动态生成的Unicode字符转化为UTF-8字符再传输到浏览器:

mark

所以你看到很多网页的源码上会有类似<meta charset="UTF-8" />的信息,表示该网页正在使用UTF-8编码。

在python中,字符串是以Unicode编码的,而python的字符串类型是str,内存中以Unicode表示。要在网络上进行传输或保存到磁盘中,就需要将str转化为以字节为单位的bytes

要获取字符的bytes表示,可以使用encode()方法,如

>>> 'ABC'.encode('ascii')
b'ABC'
>>>'ABC'.encode('utf-8')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

纯英文的str,可以用ascii编码为bytes,其内容与utf-8相同。含中文的str不能用ascii编码为bytes,其超过了ascii编码的范围,会报错。

bytes中,无法显示为ASCII字符的字节,会以b\x##的形式显示。

使用type可以查看b'abc'或b'\xe4\xb8\xad\xe6\x96\x87'的数据类型,是一个bytes类

>>> type(b'\xe4\xb8\xad\xe6\x96\x87')
<class 'bytes'>

相反,从网络上或磁盘中读取到了字节流,读到的就是bytes,需要用decode()方法解码为str

>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

字节字符串与文本字符串

b'ABC' 与 'ABC'是不同的,前者是bytes,也叫字节字符串;后者是str,也称为文本字符串。前者一个字符占一个字节(中文一个汉字占三个字节),str类型在内存中以Unicode表示,一个字符占若干字节。

>>> len('下'.encode('utf-8'))
3

在读取二进制数据的时候,字节字符串和文本字符串可能会引起错误。特别需要注意的是,索引和迭代动作返回的是字节的值而不是字节字符串。

>>> # Text string
>>> t = 'Hello world'
>>> for x in t:
...     print(x)
...     
H
e
l
l
o
w
o
r
l
d
>>> # Byte string
>>> b = b'Hello world'
>>> for x in b:
...     print(x)
...     
72
101
108
108
111
32
119
111
114
108
100

Base64:显示与打印二进制数据

Base64是一种用64个字符表示任意二进制数据的方法。

当我们用记事本打开bmp, exe, jpg文件时,会出现一大堆乱码:

mark

这是因为它们不是文本文件,是二进制文件,而二进制文件包含很多无法显示和打印的字符,所以,如果想让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符的转换方法,Base64是一种最常见的二进制编码方法。

注意:通常我们说编码都是将字符编码成二进制,将二进制解码为字符。而现在我们说的是将二进制编码为字符文本,将字符文本解码为二进制。不要弄混,笔者在最开始学的时候全程懵逼,完全搞不明白到底是在解码还是编码。

方法:

准备一个包含64个字符的数组:

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']

对二进制数据进行处理,每三个字节一组,共3 * 8 = 24bit,划为4组,每组6个bit:

mark

我们得到四个数字作为索引,查表,得到对应的4个字符,就是编码后的字符串。

所以,我们是将3个字节的二进制数据编码为4个字节的文本数据,长度增加33%,好处是编码后的文本可以在邮件正文、网页中正常显示。

python内置的base64模块可以提供base64的编解码功能:

>>> import base64
>>> base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
b'abcd++//'
>>> base64.b64decode(b'abcd++//')
b'i\xb7\x1d\xfb\xef\xff'

当要编码的二进制数据不是3的倍数,最后会剩下1或2个字符时怎么办?Base64会自动在末尾用b\x00补足后在进行解码,再在编码的结尾加上1或2个=,以表示在二进制数据末尾加了几个b\x00

但是,在很多Base64编码中会把=去掉,因为它会在URL,Cookies中造成歧义:

# 标准Base64:
'abcd' -> 'YWJjZA=='
# 自动去掉=:
'abcd' -> 'YWJjZA'

去掉=怎么解码呢,因为Base64编码后的长度永远是4的整数倍,所以将不是4的整数倍的Base编码自动添加相应数量=后使其变为4的整数倍后再解码即可

你可能会想,上面我们已经说了可以用decode解码二进制数据,为什么现在还需要对二进制数据编码再显示呢?

原因是:上文针对的是文本文件中的字符,而像jpg bmp mp3等二进制格式文件,其中的二进制数据不能正常解析为字符:

>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x56\x87'.decode('utf-8')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 3-4: invalid continuation byte

可很多时候我们会在一些文件头加一些与文件属性有关的数据,如在jpg文件头加数据表示该图片的大小、分辨率、色彩等信息,这时我们就需要通过对二进制进行编码读取这些信息了。

struct bytes与其他数据类型的转换

Bytes之间可以进行加法(无减法操作)组成一个新的bytes:

>>> m = b'hello '
>>> b = b'world'
>>> m+b
b'hello world'
>>> m+b'world'
b'hello world'

根据前文已知,将字符转换成二进制可以使用encode()方法,那如果是非字符型数据如整数、浮点数怎么转换成二进制数据呢?

Python提供了一个struct模块来解决bytes与其他数据类型之间的转换:

>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'

pack第一个参数是处理指令,'>I'的意思是:

>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数,unsigned int

后面参数个数要与处理指令一致,大小也要在指定的参数范围内:

>>> struct.pack('>2H', 10245599)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
struct.error: pack expected 2 items for packing (got 1)

>>> struct.pack('>2H', 102456565599)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
struct.error: pack expected 2 items for packing (got 1)

H表示整数,两字节无符号整数,usigned short

struct模块定义的数据类型可以参考python的官方文档

mark

相反,unpack指令就用来将字节流bytes按给定参数转化为我们想要的格式:

>>> struct.unpack('>I', b'\x00\x9c@c')
(10240099,)

该句指令的意思是:将给定的字节流转换成unsigned int类型的4字节无符号整数。unpack同样也可以将字节流转换为字符数据,更换参数即可。

unpack返回的是tuple类型

应用场景

有时需要用python处理二进制数据,比如存取文件,socket操作时。这时可以用python的struct模块来完成,比如可以用struct处理c语言中的结构体。

比如有一个结构体:

struct Header
{
    unsigned short id;
    char[4] tag;
    unsigned int version;
    unsigned int count;
}

通过socket.recv接收到了上面的结构体数据,存在字符串s中,bytes格式,现在把它解析出来,可以使用unpack函数:

import struct
id, tag, version, count = struct.unpack('!H4s2I', s)

!表示网络字节顺序,因为数据是从网络上接收到的,再网络上传送时他是网络字节顺序的。后面的H4s2I表示1个unsigned int4s表示4字节的字符串,2个unsigned short

通过一个unpack就将id, tag, version, count数据解析好了。

同样,也可以使用pack再将本地数据pack成struct格式

ss = struct.pack('>I4s2I', id, tag, version, count)

pack函数按照指定格式转换成了结构体Header,ss现在是一个字节流,可以通过socket将这个字节流发送出去

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

推荐阅读更多精彩内容