2016-07-22 56 views
41

我最近嘗試下面的命令在Python:爲什麼以及如何Python函數可散列?

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

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

兩個dict創作的成功表明,這兩個拉姆達和常規功能是可哈希。 (類似於{[]: 'a'}TypeError: unhashable type: 'list'而失敗)。

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

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

最後命令顯示__hash__方法爲lambda S,即明確地定義的,這不是一些自動魔法事情的Python計算基於方式。

使函數變得可排序的動機是什麼?對於獎金,函數的散列是什麼?

+6

我真的覺得這是哪門子的問題,你有一個好的* *回答之前,你不應該考慮「爲什麼不呢?」 – Hurkyl

+1

@Hyrkyl。因爲它需要增加額外的維護負擔。有人必須設計和編寫'__hash__'函數,所以他們清楚地看到了它的好處。我想知道是什麼讓他們不是孤單一人。 –

+0

儘管給出了答案,但似乎禁用哈希函數將需要更多的工作,而不僅僅是從對象繼承它。所以實際上其中一個考慮因素可能是維護債務的減少。 –

回答

40

沒什麼特別的。正如你可以看到,如果你檢查的功能類型的綁定__hash__方法:

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

of 'object' objects部分意味着它只是繼承了默認的基於身份__hash__object。功能==hash按身份工作。 idhash之間的差異是正常的,任何類型的繼承object.__hash__

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

你可能認爲__hash__只應該對不可變對象進行定義,你會差不多吧,但缺少一個關鍵的細節。 __hash__只應該爲==比較中涉及的所有內容都是不可變的對象定義。對於==基於身份的對象,基於hash的標識也是完全標準的,因爲即使對象是可變的,它們也不可能以改變其身份的方式變化。基於身份的文件,模塊和其他可變對象==的行爲都是這樣。

+1

咦?我想這解釋了「如何」。但**爲什麼?** – wallyk

+3

@wallyk:我已經加了一些闡述爲什麼。 – user2357112

+0

因此,默認情況下,Python中的所有實例都是可散列的,除非它在類層次結構中的某處關閉?這確實是一個有趣的新聞,儘管直觀上它是有意義的,它基於你不能用來鍵入「dict」的類型。 –

3

函數是可散列的,因爲它是一個正常的,內置的,不可變的對象。

Python Manual

An object is hashable if it has a hash value which never changes during its lifetime (it needs a __hash__() method), and can be compared to other objects (it needs an __eq__() or __cmp__() method). Hashable objects which compare equal must have the same hash value.

Hashability makes an object usable as a dictionary key and a set member, because these data structures use the hash value internally.

All of Python’s immutable built-in objects are hashable, while no mutable containers (such as lists or dictionaries) are. Objects which are instances of user-defined classes are hashable by default; they all compare unequal (except with themselves), and their hash value is derived from their id() .

+10

它們實際上比你想象的要多得多。你可以給它們添加屬性,重新分配它們的'__name__','__module__'和'__doc__',甚至在你感覺瘋狂的時候重新分配它們的__code__。 – user2357112

+3

你也可以重新指定它們的'__hash__'函數:) –

+3

@MadPhysicist:雖然重新分配'f .__ hash__'實際上不會對'hash(f)'做任何事情。 – user2357112

21

它可以是有用的,例如,以創建集功能的對象,或以指數函數由一個字典。不可變對象正常支持__hash__。在任何情況下,在由deflambda定義的函數之間沒有內部差異 - 純粹是句法。

使用的算法取決於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 
+2

@JulienBernu,請參閱@ user2357112的迴應 - 就== ==而言,它是不可變的,這是'__hash __()'關心的。 –

相關問題