__slots__的用法?

时间:2009-01-23 05:37:23

标签: python oop python-internals slots

Python中__slots__的目的是什么 - 特别是关于何时使用它,何时不使用?

11 个答案:

答案 0 :(得分:751)

  

在Python中,__slots__的目的是什么?应该避免这种情况的原因是什么?

TLDR:

特殊属性__slots__允许您明确说明您希望对象实例具有哪些实例属性,并具有预期结果:

  1. 更快属性访问。
  2. 记忆中
  3. 节省空间
  4. 节省空间来自

    1. 在插槽中存储值引用而不是__dict__
    2. 如果父类拒绝它们并且您声明__dict__,则拒绝__weakref____slots__创建。
    3. 快速警告

      小警告,您应该只在继承树中声明一次特定的插槽。例如:

      class Base:
          __slots__ = 'foo', 'bar'
      
      class Right(Base):
          __slots__ = 'baz', 
      
      class Wrong(Base):
          __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar
      

      当你弄错了(它可能应该)时,Python不会反对,否则问题可能无法表现出来,但是你的对象会占用比它们本来应该更多的空间。

      >>> from sys import getsizeof
      >>> getsizeof(Right()), getsizeof(Wrong())
      (64, 80)
      

      最大的警告是多重继承 - 多个"父类,非空插槽"不能合并。

      为了适应这种限制,请遵循最佳做法:除了一个或所有父母之外的其他所有人。抽象,它们的具体类和你的新具体类将共同继承 - 给抽象空槽(就像标准库中的抽象基类一样)。

      有关示例,请参阅下面有关多重继承的部分。

      要求:

      • 要让__slots__中指定的属性实际存储在广告位而不是__dict__中,类必须从object继承。

      • 为了防止创建__dict__,您必须从object继承,并且继承中的所有类都必须声明__slots__,并且它们都不能具有'__dict__'进入。

      如果你想继续阅读,有很多细节。

      为什么使用__slots__:更快的属性访问。

      Python的创建者,Guido van Rossum,states他实际创建__slots__以加快属性访问速度。

      显示可衡量的显着更快的访问是微不足道的:

      import timeit
      
      class Foo(object): __slots__ = 'foo',
      
      class Bar(object): pass
      
      slotted = Foo()
      not_slotted = Bar()
      
      def get_set_delete_fn(obj):
          def get_set_delete():
              obj.foo = 'foo'
              obj.foo
              del obj.foo
          return get_set_delete
      

      >>> min(timeit.repeat(get_set_delete_fn(slotted)))
      0.2846834529991611
      >>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
      0.3664822799983085
      

      在Ubuntu的Python 3.5中,插槽访问速度提高了近30%。

      >>> 0.3664822799983085 / 0.2846834529991611
      1.2873325658284342
      

      在Windows上的Python 2中,我测得它的速度提高了约15%。

      为什么要使用__slots__:内存节省

      __slots__的另一个目的是减少每个对象实例占用的内存空间。

      My own contribution to the documentation clearly states the reasons behind this

        

      使用__dict__节省的空间可能非常重要。

      SQLAlchemy attributes__slots__节省了大量内存。

      为了验证这一点,在Ubuntu Linux上使用Anaconda分发的Python 2.7,guppy.hpy(又名堆积)和sys.getsizeof,声明了没有__slots__的类实例的大小,以及没有别的,是64字节。 包含__dict__。再次感谢Python进行延迟评估,__dict__在被引用之前显然没有被调用,但没有数据的类通常是无用的。当调用存在时,__dict__属性另外至少为280字节。

      相反,声明为__slots__(无数据)的()的类实例仅为16个字节,插槽中有一个项目为56个字节,64个为2个。

      对于64位Python,我说明了Python 2.7和3.6中的内存消耗,对于__slots____dict__(没有定义的槽),对于dict在3.6中增长的每个点(除了0,1和2属性):

             Python 2.7             Python 3.6
      attrs  __slots__  __dict__*   __slots__  __dict__* | *(no slots defined)
      none   16         56 + 272†   16         56 + 112† | †if __dict__ referenced
      one    48         56 + 272    48         56 + 112
      two    56         56 + 272    56         56 + 112
      six    88         56 + 1040   88         56 + 152
      11     128        56 + 1040   128        56 + 240
      22     216        56 + 3344   216        56 + 408     
      43     384        56 + 3344   384        56 + 752
      

      因此,尽管Python 3中的代码较小,但我们看到实例的__slots__可以很好地缩放以保存内存,这是您希望使用__slots__的一个主要原因。

      为了完整记录,请注意,Python 2中类的64字节命名空间中每个插槽有一次性成本,Python 3中有72字节,因为插槽使用属性等数据描述符,称为"成员"。

      >>> Foo.foo
      <member 'foo' of 'Foo' objects>
      >>> type(Foo.foo)
      <class 'member_descriptor'>
      >>> getsizeof(Foo.foo)
      72
      

      __slots__的演示:

      要拒绝创建__dict__,您必须继承object

      class Base(object): 
          __slots__ = ()
      

      现在:

      >>> b = Base()
      >>> b.a = 'a'
      Traceback (most recent call last):
        File "<pyshell#38>", line 1, in <module>
          b.a = 'a'
      AttributeError: 'Base' object has no attribute 'a'
      

      或者继承另一个定义__slots__

      的类
      class Child(Base):
          __slots__ = ('a',)
      

      现在:

      c = Child()
      c.a = 'a'
      

      但:

      >>> c.b = 'b'
      Traceback (most recent call last):
        File "<pyshell#42>", line 1, in <module>
          c.b = 'b'
      AttributeError: 'Child' object has no attribute 'b'
      

      要在创建插槽对象的同时创建__dict__,只需将'__dict__'添加到__slots__(请注意,插槽已订购,您不应重复已插入的插槽父类):

      class SlottedWithDict(Child): 
          __slots__ = ('__dict__', 'b')
      
      swd = SlottedWithDict()
      swd.a = 'a'
      swd.b = 'b'
      swd.c = 'c'
      

      >>> swd.__dict__
      {'c': 'c'}
      

      或者你甚至不需要在你的子类中声明__slots__,你仍然会使用来自父母的插槽,但不限制__dict__的创建:

      class NoSlots(Child): pass
      ns = NoSlots()
      ns.a = 'a'
      ns.b = 'b'
      

      >>> ns.__dict__
      {'b': 'b'}
      

      但是,__slots__可能会导致多重继承问题:

      class BaseA(object): 
          __slots__ = ('a',)
      
      class BaseB(object): 
          __slots__ = ('b',)
      

      因为从具有非空插槽的父级创建子类失败:

      >>> class Child(BaseA, BaseB): __slots__ = ()
      Traceback (most recent call last):
        File "<pyshell#68>", line 1, in <module>
          class Child(BaseA, BaseB): __slots__ = ()
      TypeError: Error when calling the metaclass bases
          multiple bases have instance lay-out conflict
      

      如果你遇到这个问题,你可以从父母那里删除__slots__,或者如果你有父母的控制权,给他们空插槽,或者重构抽象:< / p>

      from abc import ABC
      
      class AbstractA(ABC):
          __slots__ = ()
      
      class BaseA(AbstractA): 
          __slots__ = ('a',)
      
      class AbstractB(ABC):
          __slots__ = ()
      
      class BaseB(AbstractB): 
          __slots__ = ('b',)
      
      class Child(AbstractA, AbstractB): 
          __slots__ = ('a', 'b')
      
      c = Child() # no problem!
      

      '__dict__'添加到__slots__以获得动态分配:

      class Foo(object):
          __slots__ = 'bar', 'baz', '__dict__'
      

      现在:

      >>> foo = Foo()
      >>> foo.boink = 'boink'
      

      因此,在插槽中使用'__dict__'时,我们会失去一些规模优势,因为它具有动态分配的优势,并且仍然拥有我们期望的名称的插槽。

      当你从一个没有插入的对象继承时,当你使用__slots__时,你会得到相同类型的语义 - __slots__中的名称指向时隙值,而任何其他值将放在实例__dict__中。

      避免__slots__因为您希望能够动态添加属性实际上不是一个好理由 - 如果需要,只需将"__dict__"添加到__slots__

      如果您需要该功能,也可以明确地将__weakref__添加到__slots__

      在子类化namedtuple时设置为空元组:

      namedtuple builtin使不可变的实例非常轻量级(基本上是元组的大小)但是为了获得好处,如果你将它们子类化,你需要自己做:

      from collections import namedtuple
      class MyNT(namedtuple('MyNT', 'bar baz')):
          """MyNT is an immutable and lightweight object"""
          __slots__ = ()
      

      用法:

      >>> nt = MyNT('bar', 'baz')
      >>> nt.bar
      'bar'
      >>> nt.baz
      'baz'
      

      尝试分配意外属性会引发AttributeError因为我们阻止了__dict__的创建:

      >>> nt.quux = 'quux'
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'MyNT' object has no attribute 'quux'
      

      允许__dict__创建__slots__ = (),但您不能使用非空__slots__元组的子类型。< / p>

      最大警告:多重继承

      即使多个父母的非空插槽相同,也不能一起使用:

      class Foo(object): 
          __slots__ = 'foo', 'bar'
      class Bar(object):
          __slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
      
      >>> class Baz(Foo, Bar): pass
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: Error when calling the metaclass bases
          multiple bases have instance lay-out conflict
      

      在父级中使用空__slots__似乎提供了最大的灵活性,允许孩子选择阻止或允许(通过添加'__dict__'来获得动态分配,见上文)创建__dict__

      class Foo(object): __slots__ = ()
      class Bar(object): __slots__ = ()
      class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
      b = Baz()
      b.foo, b.bar = 'foo', 'bar'
      

      你没有 拥有插槽 - 所以如果你添加它们,并在以后删除它们,它不应该导致任何问题。

      在这里走出来:如果您正在撰写mixins或使用abstract base classes,这些都不是要实例化的,那就是空的对于子类的灵活性而言,这些父母中的__slots__似乎是最好的方法。

      为了演示,首先,让我们创建一个包含我们想要在多重继承下使用的代码的类

      class AbstractBase:
          __slots__ = ()
          def __init__(self, a, b):
              self.a = a
              self.b = b
          def __repr__(self):
              return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
      

      我们可以通过继承和声明预期的插槽来直接使用上述内容:

      class Foo(AbstractBase):
          __slots__ = 'a', 'b'
      

      但我们并不关心那个琐碎的单一继承,我们需要另一个我们也可能继承的类,可能有一个嘈杂的属性:

      class AbstractBaseC:
          __slots__ = ()
          @property
          def c(self):
              print('getting c!')
              return self._c
          @c.setter
          def c(self, arg):
              print('setting c!')
              self._c = arg
      

      现在如果两个基地都有非空位,我们就不能做到以下几点。 (事实上​​,如果我们想要的话,我们可以给出AbstractBase非空插槽a和b,并将它们从下面的声明中删除 - 留下它们会出错:)

      class Concretion(AbstractBase, AbstractBaseC):
          __slots__ = 'a b _c'.split()
      

      现在我们通过多重继承获得了两种功能,并且仍然可以拒绝__dict____weakref__实例化:

      >>> c = Concretion('a', 'b')
      >>> c.c = c
      setting c!
      >>> c.c
      getting c!
      Concretion('a', 'b')
      >>> c.d = 'd'
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Concretion' object has no attribute 'd'
      

      其他避免插槽的情况:

      • 当您想要与另一个没有它们的课程(并且您无法添加它们)执行__class__作业时,请避免使用它们,除非插槽布局相同。 (我非常有兴趣了解这是谁以及为什么这样做。)
      • 如果你想为long,tuple或str等可变长度内置子类化,并且你想为它们添加属性,请避免使用它们。
      • 如果您坚持通过类属性为实例变量提供默认值,请避免使用它们。

      您可以从__slots__ documentation (the 3.7 dev docs are the most current)的其余部分中删除更多注意事项,我最近做出了重要贡献。

      对其他答案的批评

      目前最热门的答案引用了过时的信息,并且非常手工波动,并且在某些重要方面错过了标记。

      在实例化大量对象时不要使用__slots__&#34;

      我引用:

        

      &#34;如果要实例化同一类的大量(数百,数千)个对象,您可能希望使用__slots__。&#34;

      例如,来自collections模块的抽象基类未实例化,但为它们声明了__slots__

      为什么?

      如果用户希望拒绝创建__dict____weakref__,则必须在父类中提供这些内容。

      __slots__在创建接口或mixin时有助于重用。

      确实很多Python用户都没有写入可重用性,但是当你这样做时,可以选择拒绝不必要的空间使用是有价值的。

      __slots__没有打破酸洗

      在对开槽物品进行腌制时,您可能会发现它具有误导性TypeError抱怨:

      >>> pickle.loads(pickle.dumps(f))
      TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
      

      这实际上是不正确的。此消息来自最早的协议,这是默认协议。您可以使用-1参数选择最新协议。在Python 2.7中,这将是2(在2.3中引入),而在3.6中它是4

      >>> pickle.loads(pickle.dumps(f, -1))
      <__main__.Foo object at 0x1129C770>
      
      Python 2.7中的

      >>> pickle.loads(pickle.dumps(f, 2))
      <__main__.Foo object at 0x1129C770>
      
      Python 3.6中的

      >>> pickle.loads(pickle.dumps(f, 4))
      <__main__.Foo object at 0x1129C770>
      

      所以我会记住这一点,因为这是一个已解决的问题。

      批判(直到2016年10月2日)接受答案

      第一段是半短的解释,半预测。这是实际回答问题的唯一部分

        

      正确使用__slots__是为了节省对象中的空间。除了拥有允许随时向对象添加属性的动态dict之外,还有一个静态结构,它不允许在创建后添加。这为每个使用插槽的对象节省了一个dict的开销

      下半场是一厢情愿的想法,并且不合时宜:

        

      虽然这有时是一种有用的优化,但如果Python解释器足够动态,那么在实际添加对象时只需要dict就完全没有必要。

      Python实际上做了类似的事情,只在访问它时创建__dict__,但创建大量没有数据的对象是相当荒谬的。

      第二段过分简化并忽略了避免__slots__的实际原因。以下是避免插槽的真正原因(对于实际原因,请参阅上面的其余答案。):

        

      他们改变了具有插槽的对象的行为,这种方式可能会受到控制狂和静态打字的影响。

      然后继续讨论用Python实现这个不正当目标的其他方法,而不是与__slots__讨论任何事情。

      第三段是更加一厢情愿的想法。这主要是一些非常重要的内容,回答者甚至没有创作并为该网站的批评者提供弹药。

      内存使用证据

      创建一些普通对象和开槽对象:

      >>> class Foo(object): pass
      >>> class Bar(object): __slots__ = ()
      

      实例化其中的一百万:

      >>> foos = [Foo() for f in xrange(1000000)]
      >>> bars = [Bar() for b in xrange(1000000)]
      

      使用guppy.hpy().heap()进行检查:

      >>> guppy.hpy().heap()
      Partition of a set of 2028259 objects. Total size = 99763360 bytes.
       Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
           0 1000000  49 64000000  64  64000000  64 __main__.Foo
           1     169   0 16281480  16  80281480  80 list
           2 1000000  49 16000000  16  96281480  97 __main__.Bar
           3   12284   1   987472   1  97268952  97 str
      ...
      

      访问常规对象及其__dict__并再次检查:

      >>> for f in foos:
      ...     f.__dict__
      >>> guppy.hpy().heap()
      Partition of a set of 3028258 objects. Total size = 379763480 bytes.
       Index  Count   %      Size    % Cumulative  % Kind (class / dict of class)
           0 1000000  33 280000000  74 280000000  74 dict of __main__.Foo
           1 1000000  33  64000000  17 344000000  91 __main__.Foo
           2     169   0  16281480   4 360281480  95 list
           3 1000000  33  16000000   4 376281480  99 __main__.Bar
           4   12284   0    987472   0 377268952  99 str
      ...
      

      这与Python的历史一致,来自Unifying types and classes in Python 2.2

        

      如果您对内置类型进行子类化,则会自动向实例添加额外空间以容纳__dict____weakrefs__。 (__dict__在您使用之前未初始化,因此您不必为您创建的每个实例担心空字典占用的空间。)如果您不需要这个额外的空间,你可以添加短语&#34; __slots__ = []&#34;到你的班级。

答案 1 :(得分:261)

引用Jacob Hallen

  

正确使用__slots__是为了节省对象中的空间。而不是拥有   一个动态dict,允许随时向对象添加属性,   有一个静态结构,在创建后不允许添加。   [__slots__的这种使用消除了每个对象的一个​​字典的开销。]虽然这有时是一个有用的优化,但它将是完全的   如果Python解释器足够动态,那就不必要了   实际上只有在添加对象时才需要dict。

     

不幸的是,插槽有副作用。他们改变了行为   具有插槽的对象可以被控制怪物滥用   和静态打字weenies。这很糟糕,因为控制狂应该   滥用元类和静态打字weenies应该滥用   装饰器,因为在Python中,应该只有一种明显的做法。

     

让CPython足够智能以处理没有__slots__的节省空间是一个主要问题   承诺,这可能是为什么它不在P3k(尚)的变化列表中。

答案 2 :(得分:120)

如果要实例化同一类的大量(数百,数千)个对象,则需要使用__slots____slots__仅作为内存优化工具存在。

非常不鼓励使用__slots__来约束属性创建,一般来说,你想要避免它,因为它会破坏pickle,以及python的其他一些内省特性。

答案 3 :(得分:58)

每个python对象都有一个__dict__ atttribute,它是一个包含所有其他属性的字典。例如当你键入self.attr时,python实际上在做self.__dict__['attr']。你可以想象使用字典存储属性需要一些额外的空间和访问它的时间。

但是,当您使用__slots__时,为该类创建的任何对象都不会具有__dict__属性。相反,所有属性访问都是通过指针直接完成的。

因此,如果想要一个C风格的结构而不是一个完整的类,你可以使用__slots__来压缩对象的大小&amp;减少属性访问时间。一个很好的例子是包含属性x和amp;的Point类。年。如果您要获得很多积分,可以尝试使用__slots__以节省一些内存。

答案 4 :(得分:18)

除了其他答案之外,以下是使用__slots__

的示例
>>> class Test(object):   #Must be new-style class!
...  __slots__ = ['x', 'y']
... 
>>> pt = Test()
>>> dir(pt)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', 
 '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', 
 '__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
>>> pt.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']

因此,要实现__slots__,只需要一个额外的行(并且如果它已经没有,那么使你的类成为新式的类)。这样你就可以reduce the memory footprint of those classes 5-fold,代价是必须编写自定义pickle代码,如果有必要的话。

答案 5 :(得分:11)

插槽对于库调用非常有用,可以在进行函数调用时消除“命名方法调度”。 SWIG documentation中提到了这一点。对于希望减少使用插槽的常用函数的函数开销的高性能库,要快得多。

现在这可能与OP问题没有直接关系。它与构建扩展相关,而不是在对象上使用 slots 语法。但它确实有助于完成插槽使用的图片以及它们背后的一些推理。

答案 6 :(得分:6)

类实例的属性有3个属性:实例,属性名称和属性值。

常规属性访问 中,该实例充当字典,该属性的名称充当该字典查找值的键。

实例(属性) - &gt;值

__ slots__ access 中,属性的名称充当字典,实例充当字典查找值的键。

属性(实例) - &gt;值

flyweight pattern 中,属性的名称充当字典,该值充当该字典中查找实例的键。

属性(值) - &gt;实例

答案 7 :(得分:2)

你基本上没有使用__slots__

当您认为可能需要__slots__时,您实际上想要使用轻量级 Flyweight 设计模式。这些是您不再需要使用纯Python对象的情况。相反,您需要围绕数组,结构或numpy数组的类似Python对象的包装器。

class Flyweight(object):

    def get(self, theData, index):
        return theData[index]

    def set(self, theData, index, value):
        theData[index]= value

类类包装器没有属性 - 它只提供了对底层数据起作用的方法。这些方法可以简化为类方法。实际上,它可以简化为仅在基础数据阵列上运行的函数。

答案 8 :(得分:2)

__slot__属性的一个非常简单的示例。

问题:没有__slots__

如果我的班级中没有__slot__属性,我可以为我的对象添加新属性。

class Test:
    pass

obj1=Test()
obj2=Test()

print(obj1.__dict__)  #--> {}
obj1.x=12
print(obj1.__dict__)  # --> {'x': 12}
obj1.y=20
print(obj1.__dict__)  # --> {'x': 12, 'y': 20}

obj2.x=99
print(obj2.__dict__)  # --> {'x': 99}

如果你看一下上面的例子,你可以看到 obj1 obj2 有自己的 x y attributes和python还为每个对象创建了一个dict属性( obj1 obj2 )。

假设我的班级测试有数千个这样的对象?为每个对象创建一个额外的属性dict将导致我的代码中的大量开销(内存,计算能力等)。

解决方案:使用__slots__

现在,在以下示例中,我的班级测试包含__slots__属性。现在我无法向对象添加新属性(属性x除外),而python不再创建dict属性。这消除了每个对象的开销,如果你有很多对象,这可能会变得很重要。

class Test:
    __slots__=("x")

obj1=Test()
obj2=Test()
obj1.x=12
print(obj1.x)  # --> 12
obj2.x=99
print(obj2.x)  # --> 99

obj1.y=28
print(obj1.y)  # --> AttributeError: 'Test' object has no attribute 'y'

答案 9 :(得分:2)

__slots__的另一个有些模糊的用法是从ProxyTypes包中添加属性到对象代理,以前是PEAK项目的一部分。它的ObjectWrapper允许您代理另一个对象,但拦截与代理对象的所有交互。它并不常用(并且没有Python 3支持),但我们已经使用它来实现基于龙卷风的异步实现的线程安全阻塞包装器,它通过ioloop反弹对代理对象的所有访问,使用线程安全concurrent.Future个对象可以同步并返回结果。

默认情况下,对代理对象的任何属性访问都将为您提供代理对象的结果。如果需要在代理对象上添加属性,可以使用__slots__

from peak.util.proxies import ObjectWrapper

class Original(object):
    def __init__(self):
        self.name = 'The Original'

class ProxyOriginal(ObjectWrapper):

    __slots__ = ['proxy_name']

    def __init__(self, subject, proxy_name):
        # proxy_info attributed added directly to the
        # Original instance, not the ProxyOriginal instance
        self.proxy_info = 'You are proxied by {}'.format(proxy_name)

        # proxy_name added to ProxyOriginal instance, since it is
        # defined in __slots__
        self.proxy_name = proxy_name

        super(ProxyOriginal, self).__init__(subject)

if __name__ == "__main__":
    original = Original()
    proxy = ProxyOriginal(original, 'Proxy Overlord')

    # Both statements print "The Original"
    print "original.name: ", original.name
    print "proxy.name: ", proxy.name

    # Both statements below print 
    # "You are proxied by Proxy Overlord", since the ProxyOriginal
    # __init__ sets it to the original object 
    print "original.proxy_info: ", original.proxy_info
    print "proxy.proxy_info: ", proxy.proxy_info

    # prints "Proxy Overlord"
    print "proxy.proxy_name: ", proxy.proxy_name
    # Raises AttributeError since proxy_name is only set on 
    # the proxy object
    print "original.proxy_name: ", proxy.proxy_name

答案 10 :(得分:0)

最初的问题是关于一般用例,不仅仅是关于记忆。 所以在这里应该提到的是,在实例化大量对象时,你也会获得更好的性能 - 例如,有趣的是将大型文档解析为对象或从数据库解析时。

以下是使用插槽和无插槽创建具有一百万个条目的对象树的比较。作为对树使用普通dicts(OSX上的Py2.7.10)的性能的参考:

********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict

测试类(ident,appart from slots):

class Element(object):
    __slots__ = ['_typ', 'id', 'parent', 'childs']
    def __init__(self, typ, id, parent=None):
        self._typ = typ
        self.id = id
        self.childs = []
        if parent:
            self.parent = parent
            parent.childs.append(self)

class ElementNoSlots(object): (same, w/o slots)

testcode,详细模式:

na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
    print '*' * 10, 'RUN', i, '*' * 10
    # tree with slot and no slot:
    for cls in Element, ElementNoSlots:
        t1 = time.time()
        root = cls('root', 'root')
        for i in xrange(na):
            ela = cls(typ='a', id=i, parent=root)
            for j in xrange(nb):
                elb = cls(typ='b', id=(i, j), parent=ela)
                for k in xrange(nc):
                    elc = cls(typ='c', id=(i, j, k), parent=elb)
        to =  time.time() - t1
        print to, cls
        del root

    # ref: tree with dicts only:
    t1 = time.time()
    droot = {'childs': []}
    for i in xrange(na):
        ela =  {'typ': 'a', id: i, 'childs': []}
        droot['childs'].append(ela)
        for j in xrange(nb):
            elb =  {'typ': 'b', id: (i, j), 'childs': []}
            ela['childs'].append(elb)
            for k in xrange(nc):
                elc =  {'typ': 'c', id: (i, j, k), 'childs': []}
                elb['childs'].append(elc)
    td = time.time() - t1
    print td, 'dict'
    del droot