Python的目的是什么__slots__
——尤其是关于我什么时候想用它,什么时候不想用?
11 回答
__slots__
在 Python 中,应该避免这种情况的目的和情况是什么?
TLDR:
特殊属性__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 不会反对(它可能应该),否则问题可能不会出现,但是你的对象会占用比它们原本应该占用的更多空间。蟒蛇 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
这是因为 Base 的槽描述符有一个与 Wrong 分开的槽。这通常不应该出现,但它可以:
>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
最大的警告是多重继承 - 不能组合多个“具有非空槽的父类”。
为了适应这种限制,请遵循最佳实践:除一个或所有父抽象之外,它们的具体类和您的新具体类将共同继承的所有抽象 - 给抽象空槽(就像抽象基类中的标准库)。
有关示例,请参见下面的多重继承部分。
要求:
要将名为 in
__slots__
的属性实际存储在插槽而不是 a__dict__
中,类必须继承自object
(在 Python 3 中是自动的,但在 Python 2 中必须是显式的)。为了防止创建 a
__dict__
,您必须继承自object
,并且继承中的所有类都必须声明__slots__
,并且它们都不能有'__dict__'
条目。
如果您想继续阅读,这里有很多细节。
为什么使用__slots__
:更快的属性访问。
Python 的创建者 Guido van Rossum表示,他实际上是__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__
是减少每个对象实例占用的内存空间。
使用时节省的空间
__dict__
可能很大。
SQLAlchemy 将大量内存节省归功于__slots__
.
为了验证这一点,在 Ubuntu Linux 上使用 Python 2.7 的 Anaconda 发行版,带有guppy.hpy
(又名 heapy)和sys.getsizeof
,没有声明的类实例的大小__slots__
是 64 字节。这不包括__dict__
. 再次感谢 Python 的惰性求值,在__dict__
被引用之前显然不会被调用,但是没有数据的类通常是无用的。当调用存在时,该__dict__
属性至少额外增加 280 个字节。
相比之下,__slots__
声明为()
(无数据)的类实例只有 16 个字节,总共 56 个字节,其中一个项目在插槽中,64 个字节有两个。
对于 64 位 Python,我说明了 Python 2.7 和 3.6 中的内存消耗(以字节为单位),对于 dict 在 3.6 中增长的每个点(除了 0、1 和 2 属性)(没有定义槽)__slots__
:__dict__
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__
实例的扩展性如何以节省我们的内存,这是您想要使用__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__
:
要拒绝 a 的创建__dict__
,您必须子类化object
。Python 3 中的所有子类object
,但在 Python 2 中,您必须明确:
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__
在你的子类中声明,你仍然会使用父类的插槽,但不限制 a 的创建__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__'
in slot 时,我们会失去一些大小优势,因为动态分配的好处是仍然有我们所期望的名称的 slot。
当你从一个没有开槽的对象继承时,你会得到相同的语义__slots__
——名称__slots__
指向开槽的值,而任何其他值都放在实例的__dict__
.
避免__slots__
因为您希望能够即时添加属性实际上不是一个好的理由 -如果需要,只需添加"__dict__"
到您的属性中。__slots__
如果您需要该功能,您可以类似__weakref__
地显式添加。__slots__
子类化命名元组时设置为空元组:
内置的 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'
您可以__dict__
通过离开来允许创建__slots__ = ()
,但不能将非空__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__
似乎提供了最大的灵活性,允许子级选择阻止或允许(通过添加'__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或使用不打算实例化的抽象基类__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__
文档的其余部分(3.7 开发文档是最新的)中梳理出进一步的警告,我最近对此做出了重要贡献。
对其他答案的批评
当前的最佳答案引用了过时的信息,并且非常随意,并且在某些重要方面未达到目标。
不要“仅__slots__
在实例化大量对象时使用”
我引用:
__slots__
“如果你要实例化很多(成百上千)同一个类的对象,你会想要使用它。”
例如,来自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 的开销
后半段是一厢情愿,而且离题:
虽然这有时是一个有用的优化,但如果 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__ = []
”添加到你的班。
正确使用
__slots__
是为了节省对象的空间。与允许随时向对象添加属性的动态字典不同,有一个不允许在创建后添加的静态结构。[这种使用__slots__
消除了每个对象使用一个 dict 的开销。] 虽然这有时是一种有用的优化,但如果 Python 解释器足够动态,以至于它只需要 dict 实际添加到目的。不幸的是,插槽有副作用。它们以一种可能被控制狂和静态打字小鬼滥用的方式改变了具有插槽的对象的行为。这很糟糕,因为控制狂应该滥用元类,而静态类型的小伙伴们应该滥用装饰器,因为在 Python 中,应该只有一种明显的方式来做某事。
使 CPython 足够聪明以处理节省空间
__slots__
是一项重大任务,这可能是它不在 P3k 更改列表中的原因(目前)。
__slots__
如果您要实例化同一类的很多(数百、数千)个对象,您会想要使用。__slots__
仅作为内存优化工具存在。
强烈建议不要使用它__slots__
来约束属性创建。
酸洗对象__slots__
不适用于默认(最旧的)泡菜协议;有必要指定更高版本。
python的一些其他自省功能也可能受到不利影响。
每个 python 对象都有一个__dict__
属性,它是一个包含所有其他属性的字典。例如,当您键入self.attr
python 时,实际上是在做self.__dict__['attr']
. 正如您可以想象的那样,使用字典来存储属性需要一些额外的空间和时间来访问它。
但是,当您使用 时__slots__
,为该类创建的任何对象都没有__dict__
属性。相反,所有属性访问都是通过指针直接完成的。
因此,如果想要一个 C 风格的结构而不是一个完整的类,您可以使用它__slots__
来压缩对象的大小并减少属性访问时间。一个很好的例子是包含属性 x 和 y 的 Point 类。如果你打算有很多点,你可以尝试使用__slots__
以节省一些内存。
除了其他答案,这里是一个使用示例__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__
,它只需要额外的一行(如果还没有的话,让你的类成为一个新样式的类)。通过这种方式,您可以将这些类的内存占用减少 5 倍,但代价是必须编写自定义 pickle 代码(如果有必要)。
插槽对于库调用非常有用,可以在进行函数调用时消除“命名方法调度”。这在 SWIG文档中有所提及。对于希望减少使用插槽的常用函数的函数开销的高性能库,速度要快得多。
现在这可能与 OPs 问题没有直接关系。它更多地与构建扩展有关,而不是与在对象上使用槽语法有关。但它确实有助于完整了解插槽的使用情况以及它们背后的一些推理。
类实例的属性具有 3 个属性:实例、属性名称和属性值。
在常规属性访问中,实例充当字典,属性名称充当该字典中查找值的键。
实例(属性)--> 值
在__slots__ access中,属性的名称充当字典,实例充当字典中查找值的键。
属性(实例)-> 值
在flyweight 模式中,属性的名称充当字典,值充当查找实例的字典中的键。
属性(值)-> 实例
一个非常简单的__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属性,python 还dict
为每个对象(obj1和obj2)创建了一个属性。
假设如果我的类Test有数千个这样的对象?为每个对象创建一个附加属性dict
会在我的代码中导致大量开销(内存、计算能力等)。
解决方案:使用__slots__
现在在下面的示例中,我的类Test包含__slots__
属性。现在我不能向我的对象添加新属性(除了 attribute 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'
另一个有点晦涩的用法__slots__
是从 ProxyTypes 包(以前是 PEAK 项目的一部分)向对象代理添加属性。它ObjectWrapper
允许您代理另一个对象,但拦截与代理对象的所有交互。它不是很常用(并且不支持 Python 3),但我们已经使用它来实现一个线程安全的阻塞包装器,该包装器围绕基于 tornado 的异步实现,该实现通过 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
你有 - 基本上 - 没有用 for __slots__
。
当您认为您可能需要时__slots__
,您实际上想要使用轻量级或轻量级设计模式。这些是您不再想使用纯 Python 对象的情况。相反,您需要一个类似 Python 对象的包装器来围绕数组、结构或 numpy 数组。
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
类包装器没有属性——它只提供作用于底层数据的方法。这些方法可以简化为类方法。实际上,它可以简化为仅对底层数据数组进行操作的函数。
最初的问题是关于一般用例,而不仅仅是关于内存。所以这里应该提到的是,当实例化大量对象时,您也会获得更好的性能——这很有趣,例如在将大型文档解析为对象或从数据库中解析时。
这是使用插槽和不使用插槽创建具有一百万个条目的对象树的比较。作为对树使用普通字典时的性能参考(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)
测试代码,详细模式:
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