关于python字节流,整形,字符串,hex字符串一些转换方法
January 5th, 2022

字节流 <---> hex字符串

python3

首先说明下我用的是python3.7, 可能不能兼容所有的python3,比如early python3不一定有这些方法,但是newer python3应该是有的,具体有没有我以下用的方法要查官方文档。

我们来看一下什么是hex字符串,望文生义,它肯定是字符串类型,但是literal形式是hex,即16进制样子。
比如下面就是hex字符串

hexstring = ‘2b230948b6a30834017b6c80cd5773b2c4f08f13e424ca9a4fee1f11a9bfd9f4a8d9e8833ddabc8b09df8286’

我们可以分析出每两个字符代表一个字节,先在我要将其转成字节流,什么,你不知道字节流,好吧,我们再来看一下什么是字节流,
在python中要定义一个字节流很简单,如下:

bytesstream1 = b'ABC' # 一个字符占一个字节,表现出形式就是对应的ASCII编码
bytesstream2 = b'\xe4\xb8\xad\xe6\x96\x87'

他们在网络中都是一个字节一个字节地传输。
那么问题来了,在python中要怎么相互转换呢?

hex字符串 -> 字节流:

bytes.fromhex(string), 此 bytes 类方法返回一个解码给定字符串的 bytes 对象。 字符串必须由表示每个字节的两个十六进制数码构成,其中的 ASCII 空白符会被忽略。

>>>bytes.fromhex('2Ef0 F1f2  ')
b'.\xf0\xf1\xf2'

字节流 -> hex字符串:

hex(),返回一个字符串对象,该对象包含实例中每个字节的两个十六进制数字。

>>>b'\xf0\xf1\xf2'.hex()
'f0f1f2'

另外可以参看bytearray,区别可能就是,bytearray对应的对象是可变对应物,而bytes对应对象是不可变

python2

python2和python3有很大区别,上面说的函数好像是只有python3才有的(没有仔细了解),所以到了python2,那些函数基本上不可用,或者说用法也有很大的不一样。ok,接下来我们看看python2怎么去实现字节流和hex字符串相互转换。

python2 字符串对象中有两个函数,分别叫decode和encode,顾名思义decode是解码,encode是编码,解码就是以指定的格式解码成本来的样子,而编码就是以指定的方式编码成所要的样子(比如ASCII,unicode等等),我这样说的如果看不懂,就不要写代码了吧,回家种田养猪去吧。
假定我们现在有一串hex字符串:

>> hexstring = ‘2b230948b6a30834017b6c80cd5773b2c4f08f13e424ca9a4fee1f11a9bfd9f4a8d9e8833ddabc8b09df8286’

每两个字符为一个字节,那么总共有88个字符,那就是44个字节,所以我们要怎么将其变成b'\x2b\x23...',刚刚说了这个hex字符串是经过编码的,编码方式是hex。

hex字符串 -> 字节流:

str.decode([encoding[, errors]])

>>> hexstring.decode('hex')
'+#\tH\xb6\xa3\x084\x01{l\x80\xcdWs\xb2\xc4\xf0\x8f\x13\xe4$\xca\x9aO\xee\x1f\x11\xa9\xbf\xd9\xf4\xa8\xd9\xe8\x83=\xda\xbc\x8b\t\xdf\x82\x86'

以上得到不过是字符串,并不是字节流,经过查资料,发现并没有很好地方法转成b' '这种形式,所以只能先转成整形列表,这目前来看是最方便的,有其他更好的方法后续再更新(立个flag,大概率会忘记)

map(ord,hexsring.decode('hex'))

字节流 -> hex字符串:

>>> a =b'\x0b\xaa\xff\xee\xe0\x30\x6a
>>> a.encode('hex')
'0baaffeee0306a'

这样就很好将字节流转成hex字符串,what's more,发现了一个很奇怪的问题,请看代码:

>>> a ='\x0b\xaa\xff\xee\xe0\x30\x6a'
>>> a.encode('hex')
'0baaffeee0306a'

有没有发现,python2不分字节码,前面加b和不加b没有任何区别,单个字符本来也是ASCII编码也是一个字节,所以用字符串表示的也可以看做是字节流,所以字符串也是可以用encode()直接转成hex字符串。

参考

  1. 官方参考文档(python3)
  2. 官方参考文档](python2)

字节流<---->整形列表

python3

字节流我们已经很清楚了,就不再多说了,至于整形列表,简单说就是python 列表中元素都是整形数字,例如l=[100, 255, 129, 8, 90, 99, 71], 由于一个字节是8位,所以列表中数字我们一般认为范围在0 <= x <256,超过256或者小于0的我们会有其他方法进行转换,后面再说。

字节流 -> 整形列表:

字节流转换成整形列表是很容易的:

>>> a = b'\x0b\xaa\x77\x88\x55\x01'
>>> list(a)
[11, 170, 119, 136, 85, 1]

1. bytes 字面值中只允许 ASCII 字符(无论源代码声明的编码为何)。 任何超出 127 的二进制值必须使用相应的转义序列形式加入 bytes 字面值。
2. bytes 对象的表示使用字面值格式 (b'...'),因为它通常都要比像 bytes([46, 46, 46]) 这样的格式更好用。 你总是可以使用 list(b) 将 bytes 对象转换为一个由整数构成的列表
上述抄取官方文档,使用list函数就能将bytes对象转换成整数列表。

整形列表 -> 字节流:

说到这个,我脑海第一时间想到便是struct的库,但是又觉得应该还有其他方法,不管怎样,咱们先来看看strcut如何使用的,talk is cheap,show you the code:

import struct
b = struct.pack('!{}B'.format(len(a)), *a)
print(b)
b'\x0b\xaaw\x88U\x01'

其中!是大端序,一般用于网络传输,需要其它字节序,请更改,B是unsigned char 整形占一个字节,字节长度就是列表长度,特别注意的是,传入参数,由于列表长度是6个,当你如果传入参数只写a,会提示需要6个参数,但是只传了1个,所以需要加以改正,但是我们总不可能手动一个一个的写传入的参数,假设列表是100个,手写100个参数是不是要累死。在python中,* 会自动将参数打包成tuple传给函数,多参数可以这样做,如果是要传入指定参数,比如a = 1,b = 2,这种可以用**

除了这种方法外,参考官方文档后,发现还要更好的方法:

a = bytes([11, 170, 119, 136, 85, 1])
print(a)
b'\x0b\xaaw\x88U\x01'

字面值:a = b'saa\x0b\xaa\x77\x88\x55\x01'除了字面值形式,bytes 对象还可以通过其他几种方式来创建:

  • 指定长度的以零值填充的 bytes 对象: bytes(10)
  • 通过由整数组成的可迭代对象: bytes(range(20))
  • 通过缓冲区协议复制现有的二进制数据: bytes(obj)

当然还有bytearray对象也可以像bytes一样 ,唯一不同就是bytearray对象是可变序列。
另外使用int.to_bytes函数是将一个整数分解成一个字节一个字节形式。

python2

咱们再来看看使用同样的方法应用在python2上,发现结果完全不一样,这是为什么?

字节流 -> 整形列表

>>> a = b'\x0b\xaa\x77\x88\x55\x01'
>>> list(a)
['\x0b', '\xaa', 'w', '\x88', 'U', '\x01']

经过一番仔细探究,咱们再来看看

>>> a = b'\x0b\xaa\x77\x88\x55\x01'
>>> type(a)
<type 'str'>

在python2中,是没有字节串这一说的,也就说字节串和字符串是一个意思。
咱们再来看看python3。

a = b'\x0b\xaa\x77\x88\x55\x01'
type(a)
<class 'bytes'>

看到没有在python3中是有bytes这个类型的。
所以要想在python2中将字节串(其实还是字符串啦)转成整数列表,得曲线救国了。
here it is:

>>> map(ord, a)
[11, 170, 119, 136, 85, 1]

另外多说一句,map函数在python2中返回时列表,在python3中有所不同,具体参考官方文档。

整形列表 -> 字节流

当然,python2中也是有struct的,所以咱们还是先来试试struct效果

>>> b = struct.pack('!{}B'.format(len(a)), *a)
>>> print(b)
獁圲
>>> print(repr(b))
'\x0b\xaaw\x88U\x01'

可以看到python2 和 python3 有所不同,python3使用print可以直接打印出字节串,但是用直接python2打印会乱码,所以用repr转换婴一下就能看到效果了,刚刚说过python2中不存在字节这一说的,所以显示的直接是字符串而不是以b开头的字节串。

参考文档

  1. bytes官方文档
  2. struct官方文档

字节流 <----> 字符串

python3

字符串经过某种编码方式就转换成了字节串,解码后就变成它原本的样子,比如s = ‘hello,world',这个是字符串,但是这个仅仅是我们看到的样子,或者说是它对外表现的形式,但是在计算机内存中肯定是一堆二进制数码,那么s对应的数码应该是多少了,所以这个时候编码就派上用场了。大家还记得大明湖畔的夏雨荷.....错了,还记得每次写py文件开头写的是东西么,比如这个

-*- coding:utf-8 -*-

这个就说明文件默认以utf-8编码,文件中字符串变量那么就是以utf-8编码的。知道这个后,我们就知道如何将字符串变成字节串了。

字节流 -> 字符串

由于ascii只有128个,可显示的95,不可显示33,所以字节转字符,字节数值只能小于等于128
话不多说,看代码:

a = b'\x20\x24\x26\x2c\x2f\x4b\x50\x6e'
a.decode('utf-8')
' $&,/KPn'

字节串就是经过utf-8编码后的存储形式,所以通过utf-8解码得到字符串。其实你用其他的方式解码也能得到正确的字符串,这是为什么呢?
仔细想想吧,反正我知道我也不想告诉你,自己动手查查。

字符串 - > 字节流

当然所有转成字节流都可以用struct去搞定,这里我就不再赘述了,各位看官自己尝试着用struct搞定。这里我用其他方法搞定,当然是bytes.

a = 'hello,world'
a.encode('utf-8')
b'hello,world'

当然这样也可以:

bytes(a, 'utf-8')
b'hello,world'

python2

说真的,我已经不太想研究python2了,毕竟马上就是2020年了,python2的末日已然来临,还有什么理由不拥抱python3,是为了那些在python3 上不能用的库吗,还是习惯使然,如果是第二种,那么请改变自己保守的性格,去迎接新事物吧。吐槽归吐槽,代码还是要写的。

字节流 -> 字符串

刚刚说过python2中字符串和字节串是不区分的,所以基本上可以认为字节串。当然你也可以用decode。

字符串 -> 字节流

刚刚说过python2中字符串和字节串是不区分的,所以基本上可以认为字节串。当然你也可以用encode。

参考

  1. str编码官方文档
  2. bytes解码官方文档
Subscribe to program tech record
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from program tech record

Skeleton

Skeleton

Skeleton