【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将这个字节流发送出去

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值