python3下的编码与进制转换总结

最近因为需要做一些解码方面的工作,被python的编码与进制之间的转换弄得头大,所以将常见的编码转换方法做了一次小总结。

一、python编码方式

python下涉及到编码与进制的数据类型主有要下面三类:

1. 字符串(如,'12345')
2. 字节串(如,b'12345')
3. 数字(如,12345)

1.1 字符串1

字符串是我们日常使用过程最为常见的类型,具有可视性。只有字符串才编码方式,如unicode,utf-8,gb2312,ascii之类的。其中最为常见的是就是ASCII编码,使用单字(1Byte=8bit=2^8=256)表示256个字符。

显然,使用ASCII编码来描述整个世界各种符号是不可能的,为了解决这个问题,unicode出现了,它使用双字节来表示整个世界的编码(UCS-2),这样就可以表示256×256个字符了,不过后来有人觉得这个数量仍然不够,所以出现了(UCS-4),使用4个字节来表示整个所有字符。当然,目前来说使用最多的还是UCS-2。

虽然使用unicode能够解决编码问题,然而对于单纯使用英文的文本来说,相同的内容却需要两倍的内存进行存储,为了解决这个问题,可变长的UFT-8出现了。UTF-8兼容ASCII码,而当使用汉字的时候,却是使用三个字节表示,而部分不常用的字符更是使用四个字节表示。

>>> u'a'.encode('utf-8')
b'a'
>>> u'汉'.encode('uft-8')
b'\xe6\xb1\x89'

1.2 字节串

字节串是什么?就是一堆二进制的无编码的字节(1Byte),不表示任何东西。由于1个字节有8bit,所以字节可以使用数字来表示。使用二进制来表示明显过长,一般情况合使用两位数的16进制数表示,由于字节串和字符串本质是对应的关系,所以python中的字节串操作与字符串几乎相同。

由于十六进制的前128(0x00~0x80)个数与ASCII是一一对应的关系,所以在python中,表示字节串的时候将会出现16进制与ASCII码混合出现的情况。如:

>>> b'\x25\x90\x52'
b'%\x90R'
>>> [ hex(x) for x in b'%\x90R' ]
['0x25', '0x90', '0x52']

1.3 数字

数字涉及到进制问题,一般情况下均默认为十进制,另外也只有数字可以做运算,而字符串和字节串均无法做运算的。本文中主要用的都是整数。

二、转数字

由于数据的进制之间的转换不响影响数据大小和运算,所以python默认情况下均以10进制显示和运算。比如说:

>>> 0b10       #用二进制表示的数字
2
>>> 0o10       #用八进制表示的数字
8
>>> 0x10       #用十六进制表示的数字
16
>>> 0b10 + 0o10 + 0x10
26

从某种意义上说,数字之间不存在进制转换,所谓的转换均是以不同的方式进行表示,10进制是默认表示方式,而对于其它进制表示则需要借助字符串(也就是也就是将数字转换成字符串,参见数字转字符串章节即可)。

2.1 字符串转数字

字符串转数字可以使用python的内置函数intfloat,其中int还支持进制。如:

# 将16进制的'FF'或'0xFF'转成数字
>>> int('FF',16)
255
>>> int('0xFF',16)
255
# 将十进制的'111.111'转成数字
>>> float('111.111')
111.111

2.2 字节串转数字

字节串转数字需要借助struct模块。stuct模块最重要的三个函数是pack(),unpack(),calcsize()2

pack(fmt,v1,v2,...)     按照给定的格式(fmt),将数据封装成字节串
unpack(fmt,b_string)    按照给定的格式(fmt),解析字节串
calcssize()             计算fmt占用的字节数

给定的格式如下(不完全):

Format C type python 字节数
c char string of length 1
b signed char integer 1
B unsigned char integer 1
? Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
l unsigned int or long integer or long 4
L unsigned long long 4
q long long long 8
Q unsigned long long long 8
f float float 4
f double float 8
s char[] string 1

由于字节串转数字存在这样的一个问题:是从左往右读呢,还是从右往左读呢?这个就是大小端的问题。大小端的问题对于单字节来是不存在的,因为单字节从左往右和从右往左都是一样的。但是对于多字节来说,就出现颠倒问题。

大端机:存储数据大端在前,第一个字节是最大的
小端机:存储数据小端在前,第一个字节是最小的

所以,在将字节串转数字的过程,需要确定大小端问题(因为整型、浮点形都是多字节的)。而struct则用第一个字符来实现大小端控制。

< 小端
> 大端

下面就举例说明一下。

1.将字节串’b\x00\x29’转成相应的整数(无符号的):

>>> import struct
>>> unpack('<H',b'\x00\x29')
(10496,)
>>> unpack('>H',b'\x00\x29')
(41,)

2.将字节串b’\x00\x29’转成相应的数值(即:0x000x29)

>>> unpack('BB',b'\x00\x29')
(0,41)
# 另外一种更简单的方法:
>>> b'\x00'[0]
0
>>> b'\x29'[1]
41

所以字节串的拆分成单字串方式并不能这样直取

>>> b'\x00\x29'[0:1]
b'\x00'

3.将字节串b’\x00\x29’转成字面数值(即0029),显然直接获取是非常麻烦的,不过完全可以转进制然后转成string类,再转成数字。

>>> int(hex(b'\x00'[0])[2:])
0
>>> int(hex(b'\x29'[0])[2:])
29

三、转字符串

3.1 数字转字符串

数字转字符是非常简单的,因为数字和字符串两都之间不存在编码的问题,当然其中自然涉及到进制问题:即同一个数字(默认10进制)在不同的进制如何使用字符串表示。

# 将数字以不同的进制表示:
# 10进制
>>> str(12346.689)
'12346.689'
# 16进制(只支持整数)
>>> hex(10086)
'0x2766'
# 8进制(只支持整数)
>> oct(10086)
'0o13546'
# 2进制(只支持整数)
>> bin(10086)
'0b10011101100110'

3.2 字节串转字符串

字节串与字符串之间的转码是一个很常见的问题,其中涉及到编码。其中涉及到几个要点3:

1. Python2的默认编码是ASCII,Python3的默认编码为Unicode。
2. UNICDOE才是真正的字符串,而用ASCII、UTF-8、GBK等字符编码表示的是字节串。
3. 字节串与字符串之间的转换不会改变其中内容。

关于第一点的认识影响是非常大的。在python3中可以认为string与Unicode object是统一的,而在python2中则认为string是字节串(因为所有的字符均编码成ascii码),而Unicode object才是字符串。

python3中将字节串与字符串相互区分开了,使用编码与解码的概念来让字节串类型与字符串类型相互转换。

  • 编码(encode): 将unicode(字符串)按照某种规则转化为字节串
  • 解码(encode): 将字节串按照某种规则转化为unicode(字符串)

其中某种规则自然是种各种编码方式。根据这个原理,即可完成字符串与字节串之间的互转:

# 将'\x86\xb1\x89'依照utf-8规则解码
>>> b'\xe6\xb1\x89'.encode('utf-8')
'汉'

不过有些时候如果想要将字节串的字面值转换为字符串,如b’\xe6\xb1\x89’,转成字符串中’e6b189′,只需要将单字节码转成数字,然后让其16进制显示即可:

>>> s = ''
>>> for i in b'\xe6\xb1\x89':
>>>    s = s + hex(i)[2:]
>>>
>>> s
'e6b189'

四、转字节串

4.1 数字转字节串

数字转字节串最好用的方法自然是使用struct.pack了,这点在前面字节串转数字的过程中已经作了较为详细的说明.

# 采用大端,将10086转成四个字节的整型
>>> struct.pack('>i',10086)
b"\x00\x00'f"
# 采用小端,将10086转成四个字节的整型
>>> struct.pack('<i',10086)
b"f'\x00\x00"

另外Bytes类型本身也提供了一些初始化的函数,可以将数字转换为字节串,当然这是一种单字节转换方式:

>>> bytes([0x1f,0o23,0b0101,78])
b'\x1f\x13\x05N'

4.2 字符串转字节串

字符串转字节串一般通过编码实现,如下所示:

# 将Unicode'汉'按照utf-8编码规则编码
>>> '汉'.encode('utf-8')
b'\xe6\xb1\x89'
# 获取unicode码
>>> '汉'.encode('unicode-escape')
b'\\u6c49'

而对于单字节字符串的转换,一般情况下需要转成数字,然后再转成相应的字节串,当然bytes也提供了十六进制转换的函数.

>>> bytes([int('10101010',2)])
b'\xaa'
>>> bytes.fromhex('1f2f3f')
b'\x1f/?'

五、小结

下面对上面所说内容进行一次总结:

数字 字符串 字节串
数字转 str() hex() oct() bin() struct.pack(),bytes()
字符串转 int(),float() encode() bytes.fromhex()
字节串转 struct.unpack(),[]直取 decode()

看着虽多,但是仔细梳理了一下,觉得内容也不是很复杂。

Reference

此条目发表在CODING分类目录,贴了标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。