2016-07-22 64 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 __()'关心的。 –

相关问题