Python中int、str、bytes相互轉化,還有2進位制、16進位製表示,你想要的都在這裡了

2021-03-09 12:00:20

前言

資料型別轉換是個很基礎的操作,很多語言中都要做這些轉換,例如前一段時間剛剛總結了《C/C++中string和int相互轉換的常用方法》,python 自從分離出 python3 版本之後,strbytes 兩個型別弄蒙了一大票人,在這兩種型別的轉換上我可是花了不少時間,記住一點,別隨隨便便使用 str() 函數,很多資料使用 str() 變成字串之後再想恢復可就難了。

本文所有範例均在 Python 3.7.5 上測試,Python2 已經被我拋棄了,我來試著把常見的轉換都放到一起,把踩過的坑拿來開心一下,如果有些常用的型別轉換這裡沒有的話,也歡迎小夥伴們提出來,我將持續補充,好了,可以開始了。

資料型別轉化

數位中除了整數,還有浮點數、複數等,但是 int 是最常見的型別,所有轉換中的數位只涉及 int 數位型別。

int -> str

使用 str() 函數

num = 10
val = str(10)
print(type(val), val)

#<class 'str'> 10

使用 format() 函數

num = 10
val = '{0}'.format(num)
print(type(val), val)

#<class 'str'> 10

使用 hex() 轉換成16進位制形式

num = 10
val = hex(num)
print(type(val), val)

#<class 'str'> 0xa

使用 bin() 轉換成2進位制形式

num = 10
val = bin(num).replace('0b','')
print(type(val), val)

#<class 'str'> 1010

str -> int

這個轉換比較專一,只使用 int() 函數就可以了,這個函數實際上有兩個引數,第二個參數列示進位制,預設是10進位制,你可以改成2進位制或者16進位制,甚至是3進位制、5進位制等等

使用 int() 進行各進位制數位轉換

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

在列舉 bytes 相關的轉化前,我們來先認識一下這個型別,在 Python3 中 intstrbytes 型別的變數實際上都是一個 「類」 的物件,而 bytes 相比 str 而言更接近底層資料,也更接近儲存的形式,它其實是一個位元組的陣列,類似於 C 語言中的 char [],每個位元組是一個範圍在 0-255 的數位。

bytes 其實就是這樣一連串的數位,計算機世界所有的資訊都可以用這樣一串數位表示,一幅畫,一首歌,一部電影等等,如果對編碼感興趣可以看看這篇《簡單聊聊01世界中編碼和解碼這對磨人的小妖兒》,現在清楚bytes是什麼了,我們可以看看和它相關的轉化了。

int -> bytes

使用 to_bytes() 轉換成定長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’。

使用 bytes() 函數把int陣列轉成bytes

參考上面的生成的陣列,可以通過陣列生成相同的結果

num_array = [57, 18, 0, 0]
val = bytes(num_array)
print(type(val), val)

#<class 'bytes'> b'9\x12\x00\x00'

使用 struct.pack() 函數把數位轉化成bytes

num = 4665
val = struct.pack("<I", num)
print(type(val), val)

#<class 'bytes'> b'9\x12\x00\x00'

這裡的 "<I" 表示將一個整數轉化成小端位元組序的4位元組陣列,其他的型別還有:

引數含義
>大端序
<小端序
Buint8型別
bint8型別
Huint16型別
hint16型別
Iuint32型別
iint32型別
Luint64型別
lint64型別
sascii碼,s前帶數位表示個數

bytes -> int

明白了上面的轉化過程,從 bytes 轉化到 int 只需要反著來就行了

使用 from_bytes() 把 bytes 轉化成int

bys = b'9\x12\x00\x00'
val = int.from_bytes(bys, byteorder='little', signed=False)
print(type(val), val)

#<class 'int'> 4665

使用 struct.unpack() 把 bytes 轉化成int

bys = b'9\x12\x00\x00'
val = struct.unpack("<I", bys)
print(type(val), val)

#<class 'tuple'> (4665,)

str 和 bytes

前面的這些轉化還算清晰,到了字串str 和位元組串 bytes,就開始進入了混沌的狀態,這裡會出現各種編碼,各種亂碼,各種報錯,牢記一點 str 到 bytes 是編碼過程,需要使用 encode() 函數, bytes 到 str 是解碼過程,需要使用 decode() 函數,請勿使用 str 函數,否則後果自負。

使用 encode() 函數完成 str -> bytes

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'

使用 decode() 函數完成 bytes -> str

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

假如使用了 str() 函數

從上面來看字串和位元組串的轉化蠻簡單的,甚至比整數的轉化都要簡單,但是你如果把一個 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形式,這時就要手工處理了。

轉化表格

上面的轉化方式和解釋穿插在一起有些亂,這裡總結一個表格,便於今後拿來就用

源型別目標型別方式結果
intstrstr(10)'{0}'.format(10)10 => '10'
intstr(16進位制)hex(10)10 => '0xa'
intstr(2進位制)bin(10).replace('0b','')10 => '1010'
strintint('10')'10' => 10
str(16進位制)intint('0xa', 16)'0xa' => 10
str(2進位制)intint('1010', 2)'1010' => 10
intbytesnum.to_bytes(length=4, byteorder='little', signed=False)4665 => b'9\x12\x00\x00'
intbytesstruct.pack("<I", 4665)4665 => b'9\x12\x00\x00'
bytesintint.from_bytes(b'9\x12\x00\x00', byteorder='little', signed=False)b'9\x12\x00\x00' => 4665
bytesintstruct.unpack("<I", b'9\x12\x00\x00')b'9\x12\x00\x00' => 4665
int[]bytesbytes([57, 18, 0, 0])[57, 18, 0, 0] => b'9\x12\x00\x00'
bytesint[][x for x in b'9\x12\x00\x00']b'9\x12\x00\x00' => [57, 18, 0, 0]
strbytes'美好'.encode('utf-8')'美好' => b'\xe7\xbe\x8e\xe5\xa5\xbd'
strbytesbytes('美好', 'utf-8')'美好' => b'\xe7\xbe\x8e\xe5\xa5\xbd'
bytesstrb'\xe7\xbe\x8e\xe5\xa5\xbd'.decode('utf-8')b'\xe7\xbe\x8e\xe5\xa5\xbd' => '美好'
bytesbytes(無\x)binascii.b2a_hex(b'\xe7\xbe\x8eqaq')b'\xe7\xbe\x8eqaq' => b'e7be8e716171'
bytesbytes(有\x)binascii.a2b_hex(b'e7be8e716171')b'e7be8e716171' => b'\xe7\xbe\x8eqaq'
bytesstr(hex)b'\xe7\xbe\x8eqaq'.hex()b'\xe7\xbe\x8eqaq' => 'e7be8e716171'
str(hex)bytesbytes.fromhex('e7be8e716171')'e7be8e716171' => b'\xe7\xbe\x8eqaq'

總結

  • Python3 對字串和二進位制資料流做了明確的區分,不會以任意隱式的方式混用 strbytes
  • bytes 型別是一種位元流,它的存在形式是 01010001110 的形式,需要解碼成字元才容易被人理解
  • struct 模組中的 pack()unpack() 可以實現任意型別和 bytes 之間的轉換
  • binascii.b2a_hexbinascii.a2b_hex 可以實現16進位制 bytes 的不同形式轉換,不過轉換前後長度發生了變化

==>> 反爬連結,請勿點選,原地爆炸,概不負責!<<==

初識不知曲中意,再聞已是曲中人

AlbertS CSDN認證部落格專家 Python C/C++ 分散式
一個手殘黨懷著對遊戲的好奇心踏上了開發之路,旅途中磕磕絆絆踩了不少坑,觸了不少雷,深知好記性不如爛筆頭的道理,於是記錄下學習路上的點點滴滴,努力在這個浮躁的年代做一個專注的人,攜碼起舞,與君共勉。
Coding是件有趣的事情,快樂的看待每一天,我真的非常幸福~