1. 程式人生 > >由 Python2 和 Python3 中 socket.inet_aton() 實現不同引發的血案

由 Python2 和 Python3 中 socket.inet_aton() 實現不同引發的血案

err 主動 __name__ for print 別人 most ddr whole

這幾天在做一個功能實現的時候,需要把別人用 Python2.6 寫好的腳步轉成 Python3.4 實現,大部分地方轉化都沒啥問題,但是在 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 bytes

object 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)

總結:

  1. Python3 新增了 bytes 類型,對於 bytes 的轉換邏輯要特別清楚,這地方涉及了編碼類型,要特別關註;
  2. 在使用一些函數前,一定要搞清楚這個函數的具體實現,必須清楚的知道使用了這個函數是什麽效果,而不僅僅是看到暫時的效果,或者經驗主義的去調用(上面例子的最後一步,其實我一開始不是用的 binascii,而是用的現成的 md5 轉換函數,導致 encode() 成了 utf-8 格式,而浪費了不少時間去定位);
  3. 解決問題過程中,思路一定要清晰,不能靠猜,越猜越錯;
  4. 先弄明白問題的根本原因,直接從根源上去解決,比一步步的就錯解錯,效果更好。

由 Python2 和 Python3 中 socket.inet_aton() 實現不同引發的血案