Python3在处理⼀些底层应⽤时(⽐如编程)会⽤到字节类型(bytes)。
单杠7练习⾸先Python2与Python3的字节⼤有不同,如果不幸看错了教程,那就悲剧了。以下内容均指Python3.
声明⼀个字节类型的对象
我们可以⽤单引号或双引号的字⾯量表⽰法得到⼀个字符串类型:
"Hello World"
在字符串的字⾯量表⽰前加上b字母,就可以得到⼀个字节类型的对象。
bt = b"Hello World"
>> b"\x48" # 0x48是字母"H"对应的ASCII码
b"H"
>> b"\x01\x02\x03"
b'\x01\x02\x03'
字符串的元素是字符,bytes对象的元素则是字节,我们可以⽤⽅括号来取得每个字节。本质上,字节对象就是⼀个整数数组
>> bt[0]
72
得到的是⼀个0~255的数字(即8位⼆进制数字所能表⽰的⼗进制数字),我们知道,数据在计算机是⽤⼀串0和1来存储,每个0或1为⼀个⽐特(bit)⼜称"位",8个⽐特组成1个字节,即8bits=1byte。(这就是Mbps和MB/s差⼋倍的原因,前者是兆⽐特每秒,后者是兆字节每秒)
我们可以⽤bin(bt[0])来直观地表⽰每个字节在内存中的储存⽅式:
>> bin(bt[0])
'0b1001000'
除字⾯量表⽰之外,我们还可以⽤bytes()函数来得到字节类型,对于其他对象,需要实现bytes⽅法。
字节类型可以存储任意格式的数据,远远不限于⽂本,可以说图⽚、视频、⾳频等等,所以字节类型有更⼴阔的使⽤空间,是进⾏底层编程的不可或缺的⼯具。
铜发晶字符串与字节类型的转换
解码与编码
字节类型转换为字符串类型的过程为解码,反之则是编码:
>> bt.decode() # 将字节类型解码为字符串类型
'Hello World'
>> s = 'Hello World' # 将字符串类型编码为字节类型
>> s.encode()
b'Hello World'
可以这样理解:数据以01的形式(机器码)存储在内存中,对⼈类来说是不可读的,所以要解码;反过来⼈类要把可读的⽂字存储到内存中,需要编码为计算机可以使⽤的数据。
decode() 和 encode()
decode() 函数有两个可选的参数: bytes.deocde(encoding="utf-8", errors="strict") ,encoding指编码,不指定则使⽤默认使⽤utf-8编码,error有三种取值:strict、ignore和replace,默认使⽤strict
strict 对于任何⽆法被解码的字符都抛出错误
ignore 忽略⽆法被解码的字符
replace 替换⽆法被解码的字符
(其它取值与ister_error(name, error_handler)有关,不再讨论)
很显然后两者都不是什么好办法,最好的⽅法是让它抛出错误,然后再检查数据是否存在问题。
encode()与前者相反,encode()将字符串编码为字节,参数与前者相同。 因为汉字或其他语⾔⽂字需要两到三个字节来存储,字节类型的字⾯量表⽰只⽀持ASCII字符:
>>> b = b"你好,世界"
File "", line 1
SyntaxError: bytes can only contain ASCII literal characters.
所以我们需要encode()来获得ASCII之外的字符(⽐如汉字)的字节类型:
>>> s = "你好,世界"
>>> s.encode()
b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
str()和bytes()
使⽤str()和bytes()函数同样可以实现⼆者的转换。
小时代 青木时代⾸先是str(),如果直接str(b),会出现下⾯的情况:
>>> str(b)
"b'Hello World'" # 给出的是字⾯量形式
加上encoding关键字参数,让str()知道你是要解码字符串:
>>> str(b, encoding='utf-8')
'Hello world'
bytes()可将字符串类型转换为字节类型,实际上不仅是字符串,它可以将任意可迭代整数序列转化为字节类型,⽽字符串本质上也是整数数组(前⾯也说字节对象也是整数数组,从这⾥可以看出⼆者只是表⽰⽅式不同⽽已)。
>>> bytes("你好,世界", encoding="utf-8") # 使⽤字符串做参数时需要指定编码
b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
>>> bytes([1,2,3,5]) # 输⼊可迭代的整数序列
b'\x01\x02\x03\x05'
除bytes()之外,我们还会接触到struct类,它提供了更⼴泛的转换功能。
处理⼆进制数据
这⾥会举出两个例⼦,只是为了演⽰如何⽤字节类型处理⼆进制数据,所以你不需要知道它们究竟是做什么⽤的。
构造Websockt帧
先举⼀个处理⼆进制数据的例⼦:Websocket帧。Websocket是⽤来⽹络通信的应⽤层协议,当然本⽂不需要知道Websocket是什么东西, 我们先来看如何构造⼀个Websocket帧.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
洗葡萄的方法|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
阅读⽅法是⼀⾏⼀⾏看,前两⾏是标尺,单位是⽐特(位)。
每8⽐特(位)为⼀字节,第⼀个字节中,FIN占1位,3个RSV各占1位,opcode占4位。 假设我们要构造⼀个Websocket帧,以下是需求:
数据没有切⽚或者是最后⼀个切⽚,即FIN置为1
三个RSV⽤于协议扩展,我们⽤不到,全部置为0
opcode指定帧类型
0x0:表⽰⼀个延续帧。当 Opcode 为 0 时,表⽰本次数据传输采⽤了数据分⽚,当前收到的数据帧为其中⼀个数据分⽚。
%x1:表⽰这是⼀个⽂本帧(frame)
%x2:表⽰这是⼀个⼆进制帧(frame)
%x3-7:保留的操作代码,⽤于后续定义的⾮控制帧。
%x8:表⽰连接断开。
%x8:表⽰这是⼀个 ping 操作。
%xA:表⽰这是⼀个 pong 操作。
%xB-F:保留的操作代码,⽤于后续定义的控制帧。
假设我们⽤Websocket协议发送的数据是⽂本,即取0x01。
那么第⼀个字节已经很清楚了,按照上⾯所讲的,可以得到这个websocket帧的第⼀个字节:
FIN RSV OPCODE
1 000 0001
连起来就是
10000001
在Python中使⽤0b前缀表⽰⼆进制字⾯量,得到对应的⼗进制数:
>>> 0b10000001
129
反过来,我们可以将它解析出来,常⽤的是位运算:
>>> FIN = 129 % 0b10000000
>>> OPCODE = 129 & 0b00001111
>>> FIN, OPCODE
(1, 1)
如此,我们完成了⼀个⼆进制数据处理任务(的⼀部分)。
读取mp3的元数据
有关mp3格式的知识在这⾥可以到:
先放好⼀会要⽤到的mp3⽂件。
⾸先读⼊⽂件:
# coding=utf-8
# ⬇使⽤⼆进制读⼊
f = open("想你的夜 - 关喆.mp3", 'rb')
⽤read()⽅法读⼊数据,该⽅法接受⼀个整数n,即读取前n个字节。我们先来尝试⼀下:
>>> f.read(3)
b'ID3'
ID3表⽰采⽤ID3储存元数据。
mp3⽂件由3部分组成:
ID3v2
⾳频数据部分
ID3v1
我们需要的数据在ID3v2中,也就是位于⽂件的⾸部的数据。ID3v2.3包含头部和标签,头部由⼗个字节组成:# coding=utf-8
f = open("想你的夜 - 关喆.mp3", 'rb')
header = f.read(10)
for i in header:
# hex将整数写成16进制的形式,这⾥只是为了更直观⼀些
print(hex(i), end=' ')
输出:
0x49 0x44 0x33 0x3 0x0 0x0 0x0 0x21 0x6a 0x19
按照ID3v2.3头部的格式,我们可以将得到的前⼗个字节划分⼀下。
0x49 0x44 0x33
0x3
0x0
0x0
0x21 0x6a 0x19
ID30x03 第三版
版本号
副版本号
标志字节
标签帧总长
其它的不⽤考虑,我们的⽬的是提取出包含歌曲信息的标签,只需要得到标签帧的⼤⼩,即最后三个字节的信息。
表⽰标签帧总长度的⼀共四个字节,但每个字节只⽤7位,最⾼位不使⽤恒为0。如果要得到标签帧的⼤⼩,计算⽅法如下:
>>> bits = [0x0, 0x21, 0x6a, 0x19] # 表⽰标签帧总长度所⽤的4个字节
>>> length = (bits[0] << 21) + (bits[1] << 14) + (bits[2] << 7) + bits[3] # 注意这⾥ + 运算符的优先级⼤于 <<
>>> length
554265 # 标签帧总长度是554265字节
吴秀波 海清往下读length长度的⽂件,获得整个标签帧
戚薇个人资料# coding=utf-8
f = open("想你的夜 - 关喆.mp3", 'rb')
header = f.read(10)
length_bytes = header[-4:]
length = (length_bytes[0] << 21) + (length_bytes[1] << 14) + (length_bytes[2] << 7) + length_bytes[3]
tags = f.read(length)
>>> tags
b'TSSE\x00\x00\x00\r\x00\x00\x00Lavf56.4.101COMM\x00\x00\x02\x07\x00\x00\x00XXX\x00163 key(Don\'t
modify):L64FU3W4YxX3ZFTmbZ+8/UxuvnbxeU5+kDskFDcKcS6vslLeGLnaFpCv2iWyLFsFuKR1caPKvpZFjUI2yT0ncllGm4lpA04dl
ok,现在我们来把标签解析出来。⼀个标签帧由四部分组成:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
发布评论