__slots__的用法?
Python中__slots__
的目的是什么 – 尤其是关于什么时候使用它,什么时候不使用它。
在Python中,
__slots__
的目的是什么,以及应该避免这种情况的是什么?
TLDR:
特殊属性__slots__
允许您显式指定您希望对象实例具有哪些实例属性,并具有预期的结果:
- 更快的属性访问。
- 内存空间节省 。
空间节省是从
- 将值引用存储在槽中而不是
__dict__
。 - 否定
__dict__
和__weakref__
创build如果父类拒绝他们,你声明__slots__
。
最大的缺陷是多重inheritance – 多个“非空槽的父类”不能组合。 为了适应这个限制,请遵循最佳实践:分解所有的,除了一个或所有父母的抽象,它们的具体类和新的具体类将共同inheritance – 提供抽象空的插槽(就像抽象基类在标准库)。
要求:
-
要使
__slots__
属性实际存储在槽中而不是__dict__
,则类必须从object
inheritance。 -
为了防止创build一个
__dict__
,你必须inheritanceobject
并且inheritance中的所有类必须声明__slots__
并且它们都不能有'__dict__'
条目。
如果你想继续阅读,有很多细节。
为什么使用__slots__
:更快的属性访问。
Python的创build者Guido van Rossum 指出 ,他实际上创build了__slots__
来加速属性访问。
certificate显着更快速的访问是微不足道的:
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__
另一个目的是减less每个对象实例占用的内存空间。
我自己对文件的贡献清楚地说明了背后的原因 :
使用
__dict__
保存的空间可能很大。
SQLAlchemy为__slots__
节省了大量内存。
为了validation这一点,在Ubuntu Linux上使用Python 2.7的Anaconda发行版,使用guppy.hpy
(aka heapy)和sys.getsizeof
,没有声明__slots__
的类实例的大小是64字节。 这不包括__dict__
。 再次感谢Python的懒惰评价, __dict__
显然不被调用直到它被引用,但没有数据的类通常是无用的。 当被调用时, __dict__
属性至less为280个字节。
相比之下, __slots__
声明为()
(无数据)的类实例只有16个字节,总共56个字节,其中一个项目在插槽中,64个包含两个。
我testing了特定的字典实现的大小,通过列举字母字符变成了一个字典,在第六个字符上升到了1048,22到3352,然后是85到12568(把这么多的属性放在一个类中是不切实际的,违反了单一责任原则。)
attrs __slots__ no slots declared + __dict__ none 16 64 (+ 280 if __dict__ referenced) one 56 64 + 280 two 64 64 + 280 six 96 64 + 1048 22 224 64 + 3352
所以我们可以看到__slots__
为了节省我们的内存是多么的好,这就是你想使用__slots__
的原因。
__slots__
演示:
要拒绝__dict__
的创build,您必须inheritanceobject
:
class Base(object): __slots__ = ()
现在:
>>> b = Base() >>> ba = 'a' Traceback (most recent call last): File "<pyshell#38>", line 1, in <module> ba = 'a' AttributeError: 'Base' object has no attribute 'a'
或者子类另一个定义__slots__
类
class Child(Base): __slots__ = ('a',)
现在:
>>> c = Child() >>> ca = 'a' >>> cb = 'b' Traceback (most recent call last): File "<pyshell#42>", line 1, in <module> cb = 'b' AttributeError: 'Child' object has no attribute 'b'
为了允许__dict__
创build带槽对象的子类时创build,只需要将__dict__
添加到__slots__
(注意槽是有序的,而且不应该重复已经在父类中的槽):
class SlottedWithDict(Child): __slots__ = ('__dict__', 'b') >>> swd = SlottedWithDict() >>> swd.a = 'a' >>> swd.b = 'b' >>> swd.c = 'c' >>> swd.__dict__ {'c': 'c'}
或者你甚至不需要在你的子类中声明__slots__
,你仍然会使用来自父类的插槽,但不会限制创build__dict__
:
>>> class NoSlots(Child): pass >>> ns = NoSlots() >>> ns.a = 'a' >>> ns.b = 'b' >>> ns.__dict__ {'b': 'b'}
但是, __slots__
可能会导致多重inheritance的问题:
>>> 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__
,并把它放回到有很多实例的地方。
>>> class BaseA(object): __slots__ = () >>> class BaseB(object): __slots__ = () >>> class Child(BaseA, BaseB): __slots__ = ('a', 'b') >>> c = Child >>> ca = 'a' >>> cb = 'b' >>> cc = 'c' >>> c.__dict__ <dictproxy object at 0x10C944B0> >>> c.__dict__['c'] 'c'
将'__dict__'
添加到__slots__
以获得dynamic分配:
class Foo(object): __slots__ = 'bar', 'baz', '__dict__'
现在:
>>> foo = Foo() >>> foo.boink = 'boink'
所以在槽中使用'__dict__'
,我们会失去一些优势,因为我们有了dynamic赋值的优势,而且我们所期望的名字还有插槽。
当你从一个没有分割的对象inheritance时,当你使用__slots__
时,你可以得到相同的语义 – 在__slots__
名字指向开槽值,而任何其他的值被放在实例的__dict__
。
避免__slots__
因为你想能够在飞行中添加属性实际上不是一个很好的理由 – 只要将"__dict__"
添加到__slots__
如果需要的话)。
如果需要该function,可以同样将__weakref__
添加到__slots__
。
子类化一个namedtuple时设置为空元组:
被命名的内build函数使得不可变的实例非常轻量级(本质上是元组的大小),但为了获得好处,如果你inheritance它们,你需要自己去做:
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__
的创build:
>>> nt.quux = 'quux' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'MyNT' object has no attribute 'quux'
您可以通过closures__slots__ = ()
来允许__dict__
创build,但不能将非空的__slots__
与元组的子类一起使用。
最大的警告:多重inheritance
即使多个父母的非空插槽相同,也不能一起使用:
>>> class Foo(object): __slots__ = 'foo', 'bar' >>> class Bar(object): __slots__ = 'foo', 'bar' # alas, would work if empty, ie () >>> 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__
似乎提供了最大的灵活性, 允许孩子select防止或允许 (通过添加'__dict__'
来获得dynamic分配,参见上面的部分) 创build一个__dict__
:
>>> class Foo(object): __slots__ = () >>> class Bar(object): __slots__ = () >>> class Baz(Foo, Bar): __slots__ = ('foo', 'bar') >>> b = Baz() >>> b.foo, b.bar = 'foo', 'bar'
你不必拥有插槽 – 所以如果你添加它们,并在以后删除,它不应该引起任何问题。
如果你正在编写mixin或者使用抽象基类 (这些基类不打算被实例化),那么父类中的空__slots__
似乎是在子类的灵活性方面最好的方法。
其他情况下避免插槽:
- 避免他们,当你想执行
__class__
分配另一类没有他们(你不能添加他们),除非插槽布局是相同的。 (我非常有兴趣了解谁在做这个,为什么。) - 避免它们,如果你想要inheritance可变长度的内build像long,tuple或str,并且你想添加属性给它们。
- 如果您坚持通过实例variables的类属性提供默认值,请避免使用它们。
- 在多重inheritance的情况下避免使用父类 – 您可以将它们重新插入一个有很多实例的子项(请参阅上一节中的build议)。
您可以从其余的__slots__
文档中找出进一步的注意事项(3.7开发文档是最新的) ,我最近做出了重大贡献。
批评其他答案
目前的最佳答案引用了过时的信息,并且是非常手动的,并且在一些重要方面错过了标记。
不要“仅在实例化大量对象时使用__slots__
”
我引用:
“如果你要实例化大量(数百,数千)同一类的对象,你会想要使用
__slots__
”
抽象基类,例如,从collections
模块中,没有实例化,但__slots__
为他们声明。
为什么?
如果用户希望拒绝创build__dict__
或__weakref__
,那么这些东西不能在父类中提供。
__slots__
有助于创build接口或mixin时的可重用性。
确实,很多Python用户并不是为了重用而编写的,但是,如果您有select拒绝不必要的空间使用的情况,那么这是非常有价值的。
__slots__
不会破坏酸洗
酸洗一个开槽的物体时,可能会发现它带有误导性的TypeError
:
>>> pickle.loads(pickle.dumps(f)) TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
这实际上是不正确的。 此消息来自最早的协议,这是默认协议。 您可以使用-1
参数select最新的协议。 在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__
是为了节省对象的空间。 而不是有一个dynamic的字典,允许随时添加属性的对象,有一个静态结构,不允许添加后创build。 这为使用插槽的每个对象节省了一个字典的开销
下半场是一厢情愿的想法,
虽然这有时是一个有用的优化,但是如果Python解释器足够dynamic,那么只有在实际添加对象时才需要字典,这将是完全没有必要的。
Python实际上做了类似的事情,只是在访问时创build了__dict__
,但是创build大量没有数据的对象是非常荒谬的。
第二段过分简化和错过了实际的理由,以避免__slots__
。 以下是不是真正的原因,以避免插槽(由于实际原因,请参阅我上面的答案的其余部分):
它们以一种可被控制怪物和静态打字机滥用的方式来改变具有插槽的对象的行为。
然后继续讨论用Python完成这个逆向目标的其他方法,而不是讨论与__slots__
有关的任何事情。
第三款更是一厢情愿。 大多数情况下,回答者甚至不会为网站的评论者撰写和贡献弹药。
内存使用证据
创build一些普通对象和开槽对象:
>>> 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 2.2的Unifyingtypes和类的历史一致
如果您创build了一个内置types的子类,那么将会自动向这些实例添加额外的空间来容纳
__dict__
和__weakrefs__
。 (__dict__
被初始化,直到你使用它,所以你不应该担心你创build的每个实例的空字典占用的空间。)如果你不需要这个额外的空间,你可以添加短语“__slots__ = []
“给你的class级。
引用Jacob Hallen :
正确使用
__slots__
是为了节省对象的空间。 而不是有一个dynamic的字典,允许随时添加属性的对象,有一个静态结构,不允许添加后创build。 [__slots__
使用消除了每个对象的一个字典的开销]。虽然这有时是一个有用的优化,但是如果Python解释器足够dynamic的话,这将是完全没有必要的,所以只有在实际上添加了物体。不幸的是,插槽有一个副作用。 它们以一种可被控制怪物和静态打字机滥用的方式来改变具有插槽的对象的行为。 这是不好的,因为控制怪胎应该滥用元类,而静态types的小怪应该在滥用装饰器,因为在Python中,应该只有一个明显的做法。
使CPython足够聪明以处理节省空间而不需要
__slots__
是一项重大的任务,这可能是为什么它不在P3k(尚未)的变化列表中。
如果你要实例化很多(数百,数千)同一类的对象,你会希望使用__slots__
。 __slots__
只作为一个内存优化工具而存在。
使用__slots__
来限制属性的创build是非常令人沮丧的,一般情况下,你要避免使用__slots__
,因为它会破坏pickle,还有一些其他的python内省function。
每个python对象都有一个__dict__
属性,它是一个包含所有其他属性的字典。 例如,当你inputself.attr
python实际上是做self.__dict__['attr']
。 正如你可以想象使用字典来存储属性需要一些额外的空间和时间来访问它。
但是,当您使用__slots__
,为该类创build的任何对象将不会有__dict__
属性。 相反,所有的属性访问都是通过指针直接完成的。
所以如果想要一个C风格的结构而不是一个完整的类,你可以使用__slots__
来压缩对象的大小和减less属性访问时间。 一个很好的例子是一个包含属性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__
,它只需要一个额外的行(如果它还没有成为新的类)。 通过这种方式,您可以将这些类的内存占用减less5倍 ,而不必编写自定义的pickle代码(如果需要的话)。
在进行函数调用时,插槽对库调用非常有用,可以消除“命名方法调度”。 这在SWIG 文档中提到。 对于想要减less通常使用插槽的函数开销的高性能库,速度要快得多。
现在这可能不是与OP的问题直接相关的。 它与构build扩展相比,更多地关系到在对象上使用插槽语法。 但它确实帮助完成插槽的使用情况以及背后的一些推理。
类实例的一个属性有3个属性:实例,属性的名称和属性的值。
在常规属性访问中 ,实例充当字典,属性名称充当字典查找值中的键。
实例(属性) – >值
在__slots__访问中 ,属性的名称充当字典,实例充当字典查找值中的键。
属性(实例) – >值
在轻量级模式中 ,属性的名称作为字典,值作为查找实例的字典中的键。
属性(值) – >实例
__slots__
另一个有些模糊的用法是将属性添加到ProxyTypes包(以前属于PEAK项目的一部分)的对象代理中。 它的ObjectWrapper
允许你代理另一个对象,但拦截与代理对象的所有交互。 它不是很常用(也没有Python 3的支持),但我们用它来实现一个基于龙卷风的asynchronous实现的线程安全阻塞包装,通过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
你基本上没有用__slots__
。
当你觉得你可能需要__slots__
,你实际上想要使用轻量级或者权重devise模式。 这些情况下,你不再想要使用纯粹的Python对象。 相反,你需要在数组,结构或numpy数组周围使用类似于Python的包装器。
class Flyweight(object): def get(self, theData, index): return theData[index] def set(self, theData, index, value): theData[index]= value
类封装器没有属性 – 它只是提供了对底层数据起作用的方法。 方法可以简化为类方法。 事实上,它可以简化为仅仅在底层数据上运行的函数。
__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也为每个对象( obj1和obj2 )创build了一个dict
属性。
假设如果我的类Test有成千上万个这样的对象? 为每个对象创build一个额外的属性dict
会在我的代码中造成大量开销(内存,计算能力等)。
解决scheme:使用__slots__
现在在下面的例子中我的类Test包含__slots__
属性。 现在我不能添加新的属性到我的对象(除了属性x
)和python不会创build一个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'
原来的问题不仅是关于记忆的一般用例。 所以在这里应该提到的是,当实例化大量对象时,你也会获得更好的性能 – 例如,在将大型文档parsing为对象或从数据库中parsing时,这些都是有趣的。
下面是使用插槽和无插槽创build具有一百万个条目的对象树的比较。 作为一个参考也使用普通字典的树木(OS2上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
testingclass级(身份证,从插槽appart):
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)
testing代码,详细模式:
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