簡體   English   中英

為什么以及如何 Python 函數是可散列的?

[英]Why and how are Python functions hashable?

我最近在 Python 中嘗試了以下命令:

>>> {lambda x: 1: 'a'}
{<function __main__.<lambda>>: 'a'}

>>> def p(x): return 1
>>> {p: 'a'}
{<function __main__.p>: 'a'}

兩個dict創建的成功表明 lambda 和常規函數都是可散列的。 (類似於{[]: 'a'}失敗, TypeError: unhashable type: 'list' )。

哈希顯然不一定是函數的 ID:

>>> m = lambda x: 1
>>> id(m)
140643045241584
>>> hash(m)
8790190327599
>>> m.__hash__()
8790190327599

最后一個命令顯示__hash__方法是為lambda顯式定義的,也就是說,這不是 Python 基於類型計算的一些自動的東西。

使函數可哈希化的動機是什么? 作為獎勵,函數的哈希值是多少?

這沒什么特別的。 如您檢查函數類型的未綁定__hash__方法__hash__

>>> def f(): pass
...
>>> type(f).__hash__
<slot wrapper '__hash__' of 'object' objects>

of 'object' objects部分意味着它只是從object繼承默認的基於身份的__hash__ 函數==hash按身份工作。 idhash之間的區別對於任何繼承object.__hash__類型都是正常的:

>>> x = object()
>>> id(x)
40145072L
>>> hash(x)
2509067

您可能認為__hash__只應該為不可變對象定義,而且您幾乎是對的,但這缺少一個關鍵細節。 __hash__應該只為==比較中涉及的所有內容都是不可變的對象定義。 對於==基於身份的對象,基於身份的hash也是完全標准的,因為即使對象是可變的,它們也不可能以改變其身份的方式可變。 具有基於身份的==文件、模塊和其他可變對象都以這種方式運行。

它可能很有用,例如,創建函數對象集,或按函數索引字典。 不可變對象通常支持__hash__ 在任何情況下,由def或由lambda定義的函數之間沒有內部差異 - 這純粹是語法上的。

使用的算法取決於 Python 的版本。 看起來您正在 64 位機器上使用最新版本的 Python。 在這種情況下,函數對象的散列是其id()右旋轉 4 位,結果被視為一個有符號的 64 位整數。 完成右移是因為對象地址( id()結果)通常是對齊的,因此它們的最后 3 或 4 位始終為 0,這對於散列函數來說是一個有點煩人的屬性。

在您的具體示例中,

>>> i = 140643045241584 # your id() result
>>> (i >> 4) | ((i << 60) & 0xffffffffffffffff) # rotate right 4 bits
8790190327599  # == your hash() result

一個函數是可散列的,因為它是一個普通的、內置的、非可變的對象。

Python 手冊

如果一個對象的哈希值在其生命周期內永遠不會改變(它需要一個__hash__()方法),並且可以與其他對象進行比較(它需要一個__eq__()__cmp__()方法),那么它就是可哈希的。 比較相等的可散列對象必須具有相同的散列值。

哈希能力使對象可用作字典鍵和集合成員,因為這些數據結構在內部使用哈希值。

Python 的所有不可變內置對象都是可散列的,而沒有可變容器(例如列表或字典)是可散列的。 默認情況下,作為用戶定義類實例的對象是可散列的; 它們都比較不相等(除了它們自己),它們的哈希值來自它們的id()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM