資料型別轉換是個很基礎的操作,很多語言中都要做這些轉換,例如前一段時間剛剛總結了《C/C++中string和int相互轉換的常用方法》,python 自從分離出 python3 版本之後,str
和 bytes
兩個型別弄蒙了一大票人,在這兩種型別的轉換上我可是花了不少時間,記住一點,別隨隨便便使用 str()
函數,很多資料使用 str()
變成字串之後再想恢復可就難了。
本文所有範例均在 Python 3.7.5
上測試,Python2
已經被我拋棄了,我來試著把常見的轉換都放到一起,把踩過的坑拿來開心一下,如果有些常用的型別轉換這裡沒有的話,也歡迎小夥伴們提出來,我將持續補充,好了,可以開始了。
數位中除了整數,還有浮點數、複數等,但是 int
是最常見的型別,所有轉換中的數位只涉及 int
數位型別。
num = 10
val = str(10)
print(type(val), val)
#<class 'str'> 10
num = 10
val = '{0}'.format(num)
print(type(val), val)
#<class 'str'> 10
num = 10
val = hex(num)
print(type(val), val)
#<class 'str'> 0xa
num = 10
val = bin(num).replace('0b','')
print(type(val), val)
#<class 'str'> 1010
這個轉換比較專一,只使用 int()
函數就可以了,這個函數實際上有兩個引數,第二個參數列示進位制,預設是10進位制,你可以改成2進位制或者16進位制,甚至是3進位制、5進位制等等
val = int('10')
print(type(val), val)
val = int('0xa', 16)
print(type(val), val)
val = int('a', 16)
print(type(val), val)
val = int('0b1010', 2)
print(type(val), val)
val = int('1010', 2)
print(type(val), val)
val = int('101', 3)
print(type(val), val)
val = int('60', 5)
print(type(val), val)
# 結果均為 <class 'int'> 10
使用 int()
函數的時候要主要注意一點,如果提供的字串不能轉換成指定進位制的數位,那麼會報異常,就像下面這樣,所以在使用這個函數的時候最好放到 try
語句中。
val = int('128', 2)
'''
Traceback (most recent call last):
File "D:\python\convert\convert.py", line 41, in <module>
val = int('128', 2)
ValueError: invalid literal for int() with base 2: '128'
[Finished in 0.1s with exit code 1]
'''
在列舉 bytes 相關的轉化前,我們來先認識一下這個型別,在 Python3 中 int
、str
、bytes
型別的變數實際上都是一個 「類」 的物件,而 bytes
相比 str
而言更接近底層資料,也更接近儲存的形式,它其實是一個位元組的陣列,類似於 C
語言中的 char []
,每個位元組是一個範圍在 0-255 的數位。
bytes
其實就是這樣一連串的數位,計算機世界所有的資訊都可以用這樣一串數位表示,一幅畫,一首歌,一部電影等等,如果對編碼感興趣可以看看這篇《簡單聊聊01世界中編碼和解碼這對磨人的小妖兒》,現在清楚bytes是什麼了,我們可以看看和它相關的轉化了。
num = 4665
val = num.to_bytes(length=4, byteorder='little', signed=False)
print(type(val), val)
#<class 'bytes'> b'9\x12\x00\x00'
這段程式碼就是把數位 4665 轉化成定長的4個位元組,位元組序為小端,我們來簡單看一下是怎麼轉換的:
上面我們提到 bytes
型別一串 0-255 範圍的數位,4665 肯定超出了這個範圍,可以先轉化成256進位制,就變成了 <18><57>,也就是 4665 = 18 * 256 + 57,我們發現兩個位元組就能儲存這個數位,一個18,一個57,要想組成4個位元組的陣列需要補充兩個空位,也就是補充兩個0,這時就涉及到一個排列順序,是 [0,0,18,57] 還是 [57, 18, 0, 0] 呢,這就是函數引數中的位元組序 byteorder
,little 表示小端,big 表示大端,這裡選擇的小端 [57, 18, 0, 0] 的排列。
看到這裡可能會迷糊,好像和結果不一樣啊,其實這只是一個表示問題,57 的 ASCII 碼對應這個字元 ‘9’,18 表示成16進位制就是 ‘0x12’,這裡寫成 b’9\x12\x00\x00’ 只是便於識別而已,實際上記憶體儲存的就是 [57, 18, 0, 0] 這一串數位對應的二進位制編碼 ‘00111001 00010010 00000000 00000000’。
參考上面的生成的陣列,可以通過陣列生成相同的結果
num_array = [57, 18, 0, 0]
val = bytes(num_array)
print(type(val), val)
#<class 'bytes'> b'9\x12\x00\x00'
num = 4665
val = struct.pack("<I", num)
print(type(val), val)
#<class 'bytes'> b'9\x12\x00\x00'
這裡的 "<I"
表示將一個整數轉化成小端位元組序的4位元組陣列,其他的型別還有:
引數 | 含義 |
---|---|
> | 大端序 |
< | 小端序 |
B | uint8型別 |
b | int8型別 |
H | uint16型別 |
h | int16型別 |
I | uint32型別 |
i | int32型別 |
L | uint64型別 |
l | int64型別 |
s | ascii碼,s前帶數位表示個數 |
明白了上面的轉化過程,從 bytes 轉化到 int 只需要反著來就行了
bys = b'9\x12\x00\x00'
val = int.from_bytes(bys, byteorder='little', signed=False)
print(type(val), val)
#<class 'int'> 4665
bys = b'9\x12\x00\x00'
val = struct.unpack("<I", bys)
print(type(val), val)
#<class 'tuple'> (4665,)
前面的這些轉化還算清晰,到了字串str 和位元組串 bytes,就開始進入了混沌的狀態,這裡會出現各種編碼,各種亂碼,各種報錯,牢記一點 str 到 bytes 是編碼過程,需要使用 encode()
函數, bytes 到 str 是解碼過程,需要使用 decode()
函數,請勿使用 str
函數,否則後果自負。
s = '大漠孤煙直qaq'
val = s.encode('utf-8')
print(type(val), val)
# <class 'bytes'> b'\xe5\xa4\xa7\xe6\xbc\xa0\xe5\xad\xa4\xe7\x83\x9f\xe7\x9b\xb4qaq'
bys = b'\xe5\xa4\xa7\xe6\xbc\xa0\xe5\xad\xa4\xe7\x83\x9f\xe7\x9b\xb4qaq'
val = bys.decode('utf-8')
print(type(val), val)
# <class 'str'> 大漠孤煙直qaq
從上面來看字串和位元組串的轉化蠻簡單的,甚至比整數的轉化都要簡單,但是你如果把一個 bytes 變數用 str() 轉化成字串,你就得手動來處理了,這個函數寫過n次了,暫時還沒找到好的處理辦法。
bys = b'\xe5\xa4\xa7\xe6\xbc\xa0\xe5\xad\xa4\xe7\x83\x9f\xe7\x9b\xb4qaq'
s = str(bys)
print(type(s), s)
#<class 'str'> b'\xe5\xa4\xa7\xe6\xbc\xa0\xe5\xad\xa4\xe7\x83\x9f\xe7\x9b\xb4qaq'
def str2bytes(str_content):
result_list = [];
pos = 0
str_content = str_content.replace("\\n", "\n").replace("\\t", "\t").replace("\\r", "\r")
content_len = len(str_content)
while pos < content_len:
if str_content[pos] == '\\' and pos + 3 < content_len and str_content[pos + 1] == 'x':
sub_str = str_content[pos + 2: pos + 4]
result_list.append(int(sub_str, 16))
pos = pos + 4
else:
result_list.append(ord(str_content[pos]))
pos = pos + 1
return bytes(result_list)
val = str2bytes(s[2:-1])
print(type(val), val)
# <class 'bytes'> b'\xe5\xa4\xa7\xe6\xbc\xa0\xe5\xad\xa4\xe7\x83\x9f\xe7\x9b\xb4qaq'
什麼時候會遇到這種情況,就是有些資料是以 bytes 的形式給的,但是經過中間人複製轉發變成了位元組流形式的字串,格式還不統一,有些已經翻譯成了字元,有些還保留了0x或者\x形式,這時就要手工處理了。
上面的轉化方式和解釋穿插在一起有些亂,這裡總結一個表格,便於今後拿來就用
源型別 | 目標型別 | 方式 | 結果 |
---|---|---|---|
int | str | str(10) 、'{0}'.format(10) | 10 => '10' |
int | str(16進位制) | hex(10) | 10 => '0xa' |
int | str(2進位制) | bin(10).replace('0b','') | 10 => '1010' |
str | int | int('10') | '10' => 10 |
str(16進位制) | int | int('0xa', 16) | '0xa' => 10 |
str(2進位制) | int | int('1010', 2) | '1010' => 10 |
int | bytes | num.to_bytes(length=4, byteorder='little', signed=False) | 4665 => b'9\x12\x00\x00' |
int | bytes | struct.pack("<I", 4665) | 4665 => b'9\x12\x00\x00' |
bytes | int | int.from_bytes(b'9\x12\x00\x00', byteorder='little', signed=False) | b'9\x12\x00\x00' => 4665 |
bytes | int | struct.unpack("<I", b'9\x12\x00\x00') | b'9\x12\x00\x00' => 4665 |
int[] | bytes | bytes([57, 18, 0, 0]) | [57, 18, 0, 0] => b'9\x12\x00\x00' |
bytes | int[] | [x for x in b'9\x12\x00\x00'] | b'9\x12\x00\x00' => [57, 18, 0, 0] |
str | bytes | '美好'.encode('utf-8') | '美好' => b'\xe7\xbe\x8e\xe5\xa5\xbd' |
str | bytes | bytes('美好', 'utf-8') | '美好' => b'\xe7\xbe\x8e\xe5\xa5\xbd' |
bytes | str | b'\xe7\xbe\x8e\xe5\xa5\xbd'.decode('utf-8') | b'\xe7\xbe\x8e\xe5\xa5\xbd' => '美好' |
bytes | bytes(無\x) | binascii.b2a_hex(b'\xe7\xbe\x8eqaq') | b'\xe7\xbe\x8eqaq' => b'e7be8e716171' |
bytes | bytes(有\x) | binascii.a2b_hex(b'e7be8e716171') | b'e7be8e716171' => b'\xe7\xbe\x8eqaq' |
bytes | str(hex) | b'\xe7\xbe\x8eqaq'.hex() | b'\xe7\xbe\x8eqaq' => 'e7be8e716171' |
str(hex) | bytes | bytes.fromhex('e7be8e716171') | 'e7be8e716171' => b'\xe7\xbe\x8eqaq' |
str
和 bytes
bytes
型別是一種位元流,它的存在形式是 01010001110 的形式,需要解碼成字元才容易被人理解pack()
和 unpack()
可以實現任意型別和 bytes
之間的轉換binascii.b2a_hex
和 binascii.a2b_hex
可以實現16進位制 bytes 的不同形式轉換,不過轉換前後長度發生了變化初識不知曲中意,再聞已是曲中人