由 Python2 和 Python3 中 socket.inet_aton() 實現不同引發的血案
首先說一下,我在解決這個問題前期的思路有點問題,所以請關註最後的總結。
需求目的:把一個 ip 地址使用 socket.inet_aton() 轉化後和一個字符串組合,然後算出 MD5。
下面是 Python2.6 的實現代碼:
#!python2 # -*- coding: utf-8 -*- import socket import hashlib if __name__ == ‘__main__‘: ip = ‘192.168.1.12‘ base_str = ‘testSTR‘ ip_md5 = hashlib.md5(socket.inet_aton(ip) + base_str).digest().encode(‘hex‘) print(ip_md5)
運行後的輸出結果為:
fc138bb4748a18f885cc321c2c6396e2
如果原封不動的使用 Python3.4 運行後,報錯如下:
Traceback (most recent call last): File "socket34.py", line 25, in <module> test1() File "socket34.py", line 10, in test1 ip_md5 = hashlib.md5(socket.inet_aton(ip) + base_str).digest().encode(‘hex‘) TypeError: can‘t concat bytes to str
提示說的是,socket.inet_aton(ip) 的返回值是 bytes 類型,所以不能和 str 類型的 base_str 直接進行連接操作。
也就是說 Python2.6 和 python3.4 中對於 socket.inet_aton(ip) 的實現是有差異的,查官方文檔吧。
python2.6文檔說明:
socket.inet_aton(ip_string)
Convert an IPv4 address from dotted-quad string format (for example, ‘123.45.67.89’) to 32-bit packed binary format, as a bytesobject four characters in length.
python3.4 文檔說明:
socket.inet_aton(ip_string)
Convert an IPv4 address from dotted-quad string format (for example, ‘123.45.67.89’) to 32-bit packed binary format, as a string four characters in length.
好吧,返回值類型不同,為了保證和原腳本邏輯一致,我就做個轉化,把 bytes 主動轉換為 str 類型再連接,修改後的代碼如下:
#!python3
# -*- coding: utf-8 -*-
import socket
import hashlib
if __name__ == ‘__main__‘:
ip = ‘192.168.1.12‘
base_str = ‘testSTR‘
str_md5 = socket.inet_aton(ip).decode(‘gbk‘) + base_str
ip_md5 = hashlib.md5(str_md5).digest().encode(‘hex‘)
print(ip_md5)
代碼通過 decode 把 bytes 使用 gbk 的方式解碼成 str,至於為什麽用 gbk,是因為我對比了下,只有 gbk 編碼方式解碼後的輸出才和 python2.6 中的 str 返回值結果一致。
行,趕緊運行一把試試看。。。還是報錯了,這次的報錯內容變了:
Traceback (most recent call last):
File "socket34.py", line 34, in <module>
test1()
File "socket34.py", line 12, in test1
ip_md5 = hashlib.md5(str_md5).digest().encode(‘hex‘)
TypeError: Unicode-objects must be encoded before hashing
看起來 hashlib.md5() 在 Python2.6 和 Python3.4 中的實現也有差異,繼續看文檔。
python2.6文檔說明:
You can now feed this object with arbitrary strings using the update() method.
python3.4 文檔說明:
You can now feed this object with bytes-like objects (normally bytes) using the update() method.
依然是編碼格式的問題,Python2.6 中參數傳入的是 str,但是 Python3.4 中參數需要傳入 bytes,那就繼續轉碼吧。
再次轉碼後的代碼如下:
#!python3
# -*- coding: utf-8 -*-
import socket
import hashlib
if __name__ == ‘__main__‘:
ip = ‘192.168.1.12‘
base_str = ‘testSTR‘
str_md5 = socket.inet_aton(ip).decode(‘gbk‘) + base_str
ip_md5 = hashlib.md5(str_md5.encode(‘gbk‘)).digest().encode(‘hex‘)
print(ip_md5)
運行後再次報錯:
Traceback (most recent call last):
File "socket34.py", line 33, in <module>
test1()
File "socket34.py", line 11, in test1
ip_md5 = hashlib.md5(str_md5.encode(‘gbk‘)).digest().encode(‘hex‘)
AttributeError: ‘bytes‘ object has no attribute ‘encode‘
好吧,繼續看文檔。
python2.6文檔說明:
hash.digest()
Return the digest of the strings passed to the update() method so far. This is a string of digest_size bytes which may contain non-ASCII characters, including null bytes.
python3.4 文檔說明:
hash.digest()
Return the digest of the data passed to the update() method so far. This is a bytes object of size digest_size which may contain bytes in the whole range from 0 to 255.
這次更嚴重,encode() 直接用不了,換方法吧,更新後的代碼如下:
#!python3
# -*- coding: utf-8 -*-
import socket
import hashlib
import binascii
if __name__ == ‘__main__‘:
ip = ‘192.168.1.12‘
base_str = ‘testSTR‘
str_md5 = socket.inet_aton(ip).decode(‘gbk‘) + base_str
ip_md5 = binascii.hexlify(hashlib.md5(str_md5.encode(‘gbk‘)).digest()).decode()
print(ip_md5)
運行後的輸出結果:
fc138bb4748a18f885cc321c2c6396e2
終於得到了最終結果,激動,不過再回頭一看,如果知道這幾個函數的使用方式的話,就不需要 decode() 然後又 encode(),比如稍微優化後的代碼如下:
#!python3
# -*- coding: utf-8 -*-
import socket
import hashlib
import binascii
if __name__ == ‘__main__‘:
ip = ‘192.168.1.12‘
base_str = ‘testSTR‘
str_md5 = socket.inet_aton(ip) + base_str.encode()
ip_md5 = binascii.hexlify(hashlib.md5(str_md5).digest()).decode()
print(ip_md5)
總結:
- Python3 新增了 bytes 類型,對於 bytes 的轉換邏輯要特別清楚,這地方涉及了編碼類型,要特別關註;
- 在使用一些函數前,一定要搞清楚這個函數的具體實現,必須清楚的知道使用了這個函數是什麽效果,而不僅僅是看到暫時的效果,或者經驗主義的去調用(上面例子的最後一步,其實我一開始不是用的 binascii,而是用的現成的 md5 轉換函數,導致 encode() 成了 utf-8 格式,而浪費了不少時間去定位);
- 解決問題過程中,思路一定要清晰,不能靠猜,越猜越錯;
- 先弄明白問題的根本原因,直接從根源上去解決,比一步步的就錯解錯,效果更好。
由 Python2 和 Python3 中 socket.inet_aton() 實現不同引發的血案