Python中__slots__
的目的是什么 - 特别是关于何时使用它,何时不使用?
答案 0 :(得分:751)
在Python中,
__slots__
的目的是什么?应该避免这种情况的原因是什么?
特殊属性__slots__
允许您明确说明您希望对象实例具有哪些实例属性,并具有预期结果:
节省空间来自
__dict__
。__dict__
,则拒绝__weakref__
和__slots__
创建。小警告,您应该只在继承树中声明一次特定的插槽。例如:
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 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__
作业时,请避免使用它们,除非插槽布局相同。 (我非常有兴趣了解这是谁以及为什么这样做。)您可以从__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>
所以我会记住这一点,因为这是一个已解决的问题。
第一段是半短的解释,半预测。这是实际回答问题的唯一部分
正确使用
__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