Python 2 到 3 迁移过程 - 关于 Unicode 的差异

Python 2 to 3 Migration Process - Differences Regarding Unicode

我正在尝试将我的代码从 Python2 迁移到 Python3,因为 Python2 不再受支持。
但是,由于两个版本之间的差异,我在迁移过程中遇到了困难。 我知道 Python2 曾经同时拥有字符串和 unicode 对象,而 Python3 字符串的默认存储是 unicode。

在我的代码中的某处,我将元组的十六进制表示形式存储在数据库中。
我从用户填写的表单中得到这个元组,其中一个值是 unicode 类型。
由于Python3没有string和unicode的区分, 我最终得到了包含相同值的元组的不同十六进制表示。

这是显示我的问题的代码片段:

Python2 -

In [1]: from hashlib import sha1

In [2]: cred = ('user', 'pass')

In [3]: sha1(str(cred)).hexdigest()
Out[3]: '7cd99ee437e8166559f55a0336d4b48d9bc62bb2'

In [4]: unicode_cred = ('user', u'pass')

In [5]: sha1(str(unicode_cred)).hexdigest()
Out[5]: '807a138ff9b0dd6ce6a937e3df3bba3223b40fcd'

Python3 -

In [1]: from hashlib import sha1                                                

In [2]: cred = ('user', 'pass')                                                 

In [3]: sha1(str(cred)).hexdigest()                                             
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-847e91fdf4c5> in <module>
----> 1 sha1(str(cred)).hexdigest()

TypeError: Unicode-objects must be encoded before hashing

In [4]: sha1(str(cred).encode('utf-8')).hexdigest()                             
Out[4]: '7cd99ee437e8166559f55a0336d4b48d9bc62bb2'

In [5]: unicode_cred = ('user', u'pass')                                        

In [6]: sha1(str(unicode_cred).encode('utf-8')).hexdigest()                     
Out[6]: '7cd99ee437e8166559f55a0336d4b48d9bc62bb2'

如您所见,在 Python2 中 Out[3]Out[5] 相比具有不同的值, 而在 Python3 Out[4]Out[6] 中是相同的。

有没有办法重现 Python2 片段中所示的 Out[5] 的值?
作为迁移过程的一部分,我需要确保相同的输入产生相同的输出, 所以我不会向数据库中插入新记录,而是更新现有记录。

使用 str() 输出的十六进制摘要是问题所在。 str() 是一个版本相关的字符串,您需要完全相同的表示形式来形成十六进制摘要:

Python 2

>>> unicode_cred = ('user', u'pass')
>>> str(unicode_cred)
"('user', u'pass')"

Python 3(注意缺少的 'u')。 str() 的输出也是 Python 3 上的 Unicode 字符串,因此必须将其编码为字节才能与 sha1() 一起使用。 b 不是字符串的一部分,只是表示它现在是一个字节字符串。

>>> unicode_cred = ('user', u'pass')
>>> str(unicode_cred).encode('utf-8')
b"('user', 'pass')"

你需要用 u 组成相同的字符串才能得到相同的摘要,这有点难看。在这里,我使用 f-string 来自定义带有 u 的元组格式。我还使用 ascii 进行编码,因为非 ASCII 字符会产生额外的问题。希望您没有非 ASCII 的用户名和密码。

>>> from hashlib import sha1
>>> unicode_cred = ('user', u'pass')
>>> f"('{unicode_cred[0]}', u'{unicode_cred[1]}')"
"('user', u'pass')"
>>> sha1(f"('{unicode_cred[0]}', u'{unicode_cred[1]}')".encode('ascii')).hexdigest()
'807a138ff9b0dd6ce6a937e3df3bba3223b40fcd'