__slots__ 如何避免字典查找?

Posted

技术标签:

【中文标题】__slots__ 如何避免字典查找?【英文标题】:How does __slots__ avoid a dictionary lookup? 【发布时间】:2012-12-16 14:44:44 【问题描述】:

我听说__slots__ 通过避免字典查找使对象更快。我的困惑来自 Python 是一种动态语言。在静态语言中,我们通过进行编译时优化以将索引保存在我们运行的指令中,从而避免了对a.test 的字典查找。

现在,在 Python 中,a 可以很容易地成为另一个具有字典或一组不同属性的对象。看起来我们仍然需要进行字典查找 - 唯一的区别似乎是我们只需要一个字典来存储类,而不是每个对象都需要一个字典。

有了这个理性,

    __slots__ 如何避免字典查找? 槽是否可以更快地访问对象?

【问题讨论】:

@JBernardo 不,这个问题是关于它应该如何使用,而这是关于它是如何实现的(一个特定方面)。 我认为不是,您第二段中的推理似乎是正确的。您从哪里听说它避免了 dict 查找? @delnan:因为文档声明它删除了实例__dict__;描述符被用于每个命名槽。 @MartijnPieters 那是消除字典,这与消除字典查找不同。由于 OP 的原因(我同意),仍然有一个查找,只是在类的字典中。 @delnan:我完全同意这一点,只是解释了为什么 OP 可能会混淆两者。 :-) 【参考方案1】:

__slots__ 不会(显着)加快属性访问:

>>> class Foo(object):
...     __slots__ = ('spam',)
...     def __init__(self):
...         self.spam = 'eggs'
... 
>>> class Bar(object):
...     def __init__(self):
...         self.spam = 'eggs'
... 
>>> import timeit
>>> timeit.timeit('t.spam', 'from __main__ import Foo; t=Foo()')
0.07030296325683594
>>> timeit.timeit('t.spam', 'from __main__ import Bar; t=Bar()')
0.07646608352661133

使用__slots__的目的是节省内存;而不是在实例上使用.__dict__ 映射,该类为__slots__ 中命名的每个属性都具有descriptors objects,并且实例具有分配的属性无论是否它们具有实际值:

>>> class Foo(object):
...     __slots__ = ('spam',)
... 
>>> dir(Foo())
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'spam']
>>> Foo().spam
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: spam
>>> Foo.spam
<member 'spam' of 'Foo' objects>
>>> type(Foo.spam)
<type 'member_descriptor'>

所以python仍然需要查看Foo实例上每个属性访问的类(以找到描述符)。任何未知属性(例如Foo.ham)仍将导致 Python 通过 MRO 类查找该属性,其中包括字典搜索。你仍然可以为 class 分配额外的属性:

>>> Foo.ham = 'eggs'
>>> dir(Foo)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'ham', 'spam']
>>> Foo().ham
'eggs'

插槽描述符在创建类时创建,并访问分配给每个实例的内存以存储和检索对关联值的引用(跟踪实例引用计数的同一块内存和对类对象的引用)。如果没有插槽,则使用 __dict__ 的描述符以相同的方式访问对 dict 对象的引用。

【讨论】:

“不显着”?在我的书中,单行更改 10% 的改进是相当显着的。当然,如果您了解 __slots__ 的晦涩副作用 :) 另见:http://code.activestate.com/recipes/532903-how-__slots__-are-implemented/ 对于好奇的人:如果您不使用 __slots__ 而是在类级别声明成员,您会看到速度 penalty 超过 5%普通的非插槽版本。【参考方案2】:

它可能会加快程序的速度,在该程序中,您实例化 很多 个相同类的对象,真正永远不会更改它们的属性,并且所有这些重复字典上的缓存未命中会带来真正的性能问题。

这只是一般情况下的一个特例,节省空间有时也可以节省时间,而缓存是限制因素。

因此,它可能不会使访问 一个 对象更快,但可能会加快访问 许多 相同类型的对象。

另见this question。

【讨论】:

以上是关于__slots__ 如何避免字典查找?的主要内容,如果未能解决你的问题,请参考以下文章

Python 避免字典和元组的多重嵌套

__slots__(三十六)

__slots__(面向对象进阶)

__slots__和运算符重载中的反向方法

Python内置函数__slots__

slots与迭代器