__slots__的用法?

slots在Python中的用途是什么 - 特别是关于何时使用它以及何时不使用它?

回答(11)

2 years ago

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

TLDR:

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

  • faster 属性访问 .

  • space savings 在记忆中 .

节省的空间来自

  • 在槽中存储值引用而不是 __dict__ .

  • 如果父类拒绝它们并且声明 __slots__ ,则拒绝创建 __dict____weakref__ .

快速警告

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

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个具有两个 .

对于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中有较小的dicts,但我们看到实例可以很好地保存内存,这也是您想要使用 __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__ ,或者如果你有父母的控制权,给他们空插槽,或重构抽象:

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__ 中的名称指向时隙值,而任何其他值都放在实例的_1311171中 .

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

如果需要该功能,可以类似地将 __weakref__ 明确添加到 __slots__ .

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

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

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'

您可以通过不使用 __slots__ = () 来允许 __dict__ 创建,但是您不能将非空 __slots__ 与元组的子类型一起使用 .

最大警告:多重继承

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

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__ 似乎提供了最大的灵活性, allowing the child to choose to prevent or allow (通过添加 '__dict__' 来获取动态分配,请参阅上面的部分) the creation of a dict

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

您不必拥有插槽 - 因此,如果您添加它们,并在以后删除它们,它不应该导致任何问题 .

Going out on a limb here :如果您正在编写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__”

我引用:

“如果你打算实例化同一类的很多(数百,数千)个对象,你会想要使用__slots__ . ”

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

为什么?

如果用户希望拒绝 __dict____weakref__ 创建,那么这些内容必须在父类中不可用 .

__slots__ 在创建接口或mixin时有助于可重用性 .

确实,许多Python用户并没有为可重用性而写作,但是当你,可以选择拒绝不必要的空间使用是有 Value 的 .

__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__在你使用之前不会被初始化,所以你不必担心你创建的每个实例的空字典占用的空间 . )如果你不需要这个额外的空间,你可以添加短语“slots” = []“给你的 class .

2 years ago

引用Jacob Hallen

正确使用__slots__是为了节省对象中的空间 . 除了拥有允许随时向对象添加属性的动态dict之外,还有一个静态结构,它不允许在创建后添加 . [__slots__的这种使用消除了每个对象的一个dict的开销 . ]虽然这有时是一个有用的优化,但如果Python解释器足够动态,那么它只需要在实际添加时才需要dict . 物体 . 不幸的是,插槽有副作用 . 它们改变了具有插槽的对象的行为,这种方式可能被控制怪物和静态类型中断所滥用 . 这很糟糕,因为控制狂应该滥用元类,而静态类型weenies应该滥用装饰器,因为在Python中,应该只有一种明显的做法 . 让CPython足够聪明以便在没有__slots__的情况下处理节省空间是一项重大任务,这可能就是为什么它不在P3k(尚未)的更改列表中 .

2 years ago

如果要实例化同一类的很多(数百,数千)个对象,您可能希望使用 __slots__ . __slots__ 仅作为内存优化工具存在 .

非常不鼓励使用 __slots__ 来约束属性创建,并且通常你想要避免它,因为它打破了pickle,以及python的一些其他内省特性 .

2 years ago

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

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

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

2 years ago

除了其他答案,以下是使用 __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__ ,它只需要一个额外的行(并且如果它还没有,则使您的类成为新式类) . 这样你就可以_1111244_,代价是必须编写自定义pickle代码,如果有必要的话 .

2 years ago

插槽对于库调用非常有用,可以在进行函数调用时消除"named method dispatch" . 这在SWIG documentation中提到 . 对于希望减少使用插槽的常用函数的函数开销的高性能库,要快得多 .

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

2 years ago

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

regular attribute access 中,实例充当字典,属性的名称充当该字典查找值的键 .

instance(attribute) --> value

slots access 中,属性的名称充当字典,实例充当字典查找值的键 .

attribute(instance) --> value

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

attribute(value) --> instance

2 years ago

你有 - 基本上 - 没有用 __slots__ .

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

class Flyweight(object):

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

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

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

2 years ago

__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}

如果你看一下上面的例子,你可以看到 obj1obj2 有自己的 xy 属性,而python也为每个对象创建了一个 dict 属性( obj1obj2 ) .

假设我的 class Test 有几千个这样的对象?为每个对象创建一个附加属性 dict 会在我的代码中产生大量开销(内存,计算能力等) .

解决方案:使用__slots__

现在,在以下示例中,我的类 Test 包含 __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'

2 years ago

另一个有点模糊的 __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

2 years ago

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

下面是使用插槽和无插槽创建具有一百万个条目的对象树的比较 . 作为对树使用普通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