Usage of __slots__?
在python中,
In Python, what is the purpose of
__slots__ and what are the cases one should avoid this?
最后暴雪来句:
《特殊的属性允许你
好。
空间的冰从储蓄
好。
快速caveats
小的忠告,你应该只读DECLARE城堡一小时(特别是在一个树的传承。例如:
好。
1 2 3 4 5 6 7 8 | class Base: __slots__ = 'foo', 'bar' class Right(Base): __slots__ = 'baz', class Wrong(Base): __slots__ = 'foo', 'bar', 'baz' # redundant foo and bar |
Python对象不让你得到这个错误的信息可能会这样),否则没有问题,但你的目标宣言,将更多的空间比内地的IP,否则,他们应该。
好。
1 2 3 | >>> from sys import getsizeof >>> getsizeof(Right()), getsizeof(Wrong()) (64, 80) |
《biggest多传承"冰"的父母多类与非空槽"不能被联合。
好。
两个accommodate这个限制,遵循最佳实践的因素:选择一个或所有的一切但他们父母的abstraction级分别为混凝土,混凝土等级和你的新的采矿inherit将从现在的abstraction(S)的空槽(就像抽象类的标准库的图书馆)。
好。
看下面的传承型在线多个实例。
好。 要求:
-
"有两个属性实际上是在
__slots__ 可存储在两个插槽,而不是__dict__ A,A类必须从object inherit。好。
-
两个目标是创造一
__dict__ ,你必须从inheritobject 和所有类的DECLARE和传承必须__slots__ 没有他们可以拥有一'__dict__' 录入。好。
有很多的细节,如果你希望继续阅读。
好。 为什么使用
《典范(Guido van Rossum Python,事实上,他创造了美国
好。
它是两个重要的琐碎的demonstrate measurably阿姨访问:
好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
鸭
好。
1 2 3 4 | >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.2846834529991611 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.3664822799983085 |
《冰缝隙存取几乎30%的阿姨在Python 3.5在Ubuntu。
好。
1 2 | >>> 0.3664822799983085 / 0.2846834529991611 1.2873325658284342 |
在Python中的Windows 2在线测量的信息约有15%的禁食。
好。 为什么使用
另一个目的是
好。
我自己的贡献,美国的文件明确,这背后的原因:
好。
The space saved over using
__dict__ can be significant.Ok.
SQLAlchemy的属性很多记忆两个
好。
这两个验证,采用康达学院分布在Python 2.7 Ubuntu Linux操作系统,与
好。
在一审级的对比与
好。
Python的64位的存储器,在illustrate消费的字节在Python 2.7和3.6,
好。
1 2 3 4 5 6 7 8 9 | 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 |
所以,在大学spite小词典在Python 3,我们看到如何nicely
好。
我只是完全性票据,票据,有一个一小时的成本每槽类的命名空间(64字节的Python 72字节2和3在Python中使用数据槽,因为descriptors状特性,称为"成员"。
好。
1 2 3 4 5 6 | >>> Foo.foo <member 'foo' of 'Foo' objects> >>> type(Foo.foo) <class 'member_descriptor'> >>> getsizeof(Foo.foo) 72 |
大学
两个拒绝"创造一
好。
1 2 | class Base(object): __slots__ = () |
现在:
好。
1 2 3 4 5 6 | >>> 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' |
这是另一个类或subclass
好。
1 2 | class Child(Base): __slots__ = ('a',) |
现在:鸭
好。
1 2 | c = Child() c.a = 'a' |
但:
好。
1 2 3 4 5 | >>> 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' |
为了在子类化时隙对象时允许创建
1 2 3 4 5 6 7 | class SlottedWithDict(Child): __slots__ = ('__dict__', 'b') swd = SlottedWithDict() swd.a = 'a' swd.b = 'b' swd.c = 'c' |
和好的。
1 2 | >>> swd.__dict__ {'c': 'c'} |
或者你甚至不需要在你的子类中声明
1 2 3 4 | class NoSlots(Child): pass ns = NoSlots() ns.a = 'a' ns.b = 'b' |
还有:好的。
1 2 | >>> ns.__dict__ {'b': 'b'} |
但是,
1 2 3 4 5 | class BaseA(object): __slots__ = ('a',) class BaseB(object): __slots__ = ('b',) |
因为从具有两个非空槽的父类创建子类失败:好的。
1 2 3 4 5 6 | >>> 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 |
如果遇到此问题,您可以从父代中删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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! |
将
1 2 | class Foo(object): __slots__ = 'bar', 'baz', '__dict__' |
现在:好的。
1 2 | >>> foo = Foo() >>> foo.boink = 'boink' |
因此,在槽中使用EDOCX1[1]时,我们会失去一些尺寸优势,因为动态分配的好处是,我们仍然可以为我们期望的名称提供槽。好的。
当您从一个没有时隙的对象继承时,当您使用
避免使用
如果需要该功能,也可以将
名为duple的builtin使不可变的实例非常轻(本质上是tuples的大小),但要获得好处,如果您将它们子类化,则需要自己执行:好的。
1 2 3 4 | from collections import namedtuple class MyNT(namedtuple('MyNT', 'bar baz')): """MyNT is an immutable and lightweight object""" __slots__ = () |
用途:好的。
1 2 3 4 5 | >>> nt = MyNT('bar', 'baz') >>> nt.bar 'bar' >>> nt.baz 'baz' |
并且试图分配一个意外的属性会引发一个
1 2 3 4 | >>> nt.quux = 'quux' Traceback (most recent call last): File"<stdin>", line 1, in <module> AttributeError: 'MyNT' object has no attribute 'quux' |
您可以通过不使用
即使多个父项的非空插槽相同,也不能一起使用:好的。
1 2 3 4 5 6 7 8 9 10 | 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 |
在父级中使用空的
1 2 3 4 5 | class Foo(object): __slots__ = () class Bar(object): __slots__ = () class Baz(Foo, Bar): __slots__ = ('foo', 'bar') b = Baz() b.foo, b.bar = 'foo', 'bar' |
你不需要有插槽-所以如果你添加它们,然后再删除它们,就不会造成任何问题。好的。
这里有一个缺陷:如果您正在编写mixin或使用抽象的基类,而这些类并不打算被实例化,那么在这些父类中使用空的
为了演示,首先,让我们用在多重继承下要使用的代码创建一个类。好的。
1 2 3 4 5 6 7 | 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)})' |
我们可以通过继承和声明预期的槽来直接使用上面的内容:好的。
1 2 | class Foo(AbstractBase): __slots__ = 'a', 'b' |
但我们不在乎,这是一个微不足道的单一继承,我们需要另一个我们可能也继承自的类,可能具有嘈杂的属性:好的。
1 2 3 4 5 6 7 8 9 10 | 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 |
现在如果两个基地都有非空的插槽,我们就不能做下面的。(事实上,如果我们愿意的话,我们可以给
1 2 | class Concretion(AbstractBase, AbstractBaseC): __slots__ = 'a b _c'.split() |
现在我们可以通过多重继承从这两方面获得功能,并且仍然可以拒绝
1 2 3 4 5 6 7 8 9 10 | >>> 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之类的可变长度内置项进行子类化,并且想要向它们添加属性,就不要使用它们。
- 如果您坚持通过实例变量的类属性提供默认值,请避免使用它们。
您可以从其他
当前的顶级答案引用了过时的信息,并且在某些重要方面很容易被忽略。好的。不要"在实例化许多对象时只使用
我引述:好的。
"You would want to use
__slots__ if you are going to instantiate a lot (hundreds, thousands) of objects of the same class."Ok.
例如,来自
为什么?好的。
如果用户想要拒绝
确实,许多Python用户并不是为了可重用性而写的,但是当您这样做时,拥有拒绝不必要空间使用的选项是很有价值的。好的。
当酸洗有槽物体时,您可能会发现其投诉带有误导性的
1 2 | >>> pickle.loads(pickle.dumps(f)) TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled |
这实际上是错误的。此消息来自最旧的协议,这是默认协议。您可以使用
1 2 | >>> pickle.loads(pickle.dumps(f, -1)) <__main__.Foo object at 0x1129C770> |
在Python 2.7中:好的。
1 2 | >>> pickle.loads(pickle.dumps(f, 2)) <__main__.Foo object at 0x1129C770> |
在Python 3.6中好的。
1 2 | >>> pickle.loads(pickle.dumps(f, 4)) <__main__.Foo object at 0x1129C770> |
所以我会记住这一点,因为这是一个已解决的问题。好的。对(截至2016年10月2日)接受答案的批评
第一段是半简短的解释,半预测。这是唯一真正回答这个问题的部分好的。
The proper use of
__slots__ is to save space in objects. Instead of having a dynamic dict that allows adding attributes to objects at anytime, there is a static structure which does not allow additions after creation. This saves the overhead of one dict for every object that uses slotsOk.
下半部分是一厢情愿的想法,而且毫无意义:好的。
While this is sometimes a useful optimization, it would be completely unnecessary if the Python interpreter was dynamic enough so that it would only require the dict when there actually were additions to the object.
Ok.
python实际上做了类似的事情,只在访问时创建
第二段过于简单化,遗漏了避免
They change the behavior of the objects that have slots in a way that can be abused by control freaks and static typing weenies.
Ok.
然后,它将继续讨论用python实现这一反常目标的其他方法,而不是与
第三段更是一厢情愿。总之,这主要是离谱的内容,回答者甚至没有作者和贡献弹药的批评者的网站。好的。内存使用证据
创建一些普通对象和槽对象:好的。
1 2 | >>> class Foo(object): pass >>> class Bar(object): __slots__ = () |
举例说明一百万个:好的。
1 2 | >>> foos = [Foo() for f in xrange(1000000)] >>> bars = [Bar() for b in xrange(1000000)] |
用
1 2 3 4 5 6 7 8 | >>> 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 ... |
访问常规对象及其
1 2 3 4 5 6 7 8 9 10 11 | >>> 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中统一类型和类的历史是一致的。好的。
If you subclass a built-in type, extra space is automatically added to the instances to accomodate
__dict__ and__weakrefs__ . (The__dict__ is not initialized until you use it though, so you shouldn't worry about the space occupied by an empty dictionary for each instance you create.) If you don't need this extra space, you can add the phrase"__slots__ = [] " to your class.Ok.
好啊。
引用雅各布·哈伦的话:
The proper use of
__slots__ is to save space in objects. Instead of having
a dynamic dict that allows adding attributes to objects at anytime,
there is a static structure which does not allow additions after creation.
[This use of__slots__ eliminates the overhead of one dict for every object.] While this is sometimes a useful optimization, it would be completely
unnecessary if the Python interpreter was dynamic enough so that it would
only require the dict when there actually were additions to the object.Unfortunately there is a side effect to slots. They change the behavior of
the objects that have slots in a way that can be abused by control freaks
and static typing weenies. This is bad, because the control freaks should
be abusing the metaclasses and the static typing weenies should be abusing
decorators, since in Python, there should be only one obvious way of doing something.Making CPython smart enough to handle saving space without
__slots__ is a major
undertaking, which is probably why it is not on the list of changes for P3k (yet).
如果要实例化同一类的许多(几十万)对象,您将希望使用
强烈建议不要使用
每个python对象都有一个
但是,当使用
因此,如果想要一个C样式的结构而不是一个完整的类,您可以使用
除其他答案外,这里还有一个使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | >>> 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'] |
因此,要实现
时隙对于库调用非常有用,可以在进行函数调用时消除"命名方法调度"。这在swig文档中提到。对于希望减少函数开销的高性能库来说,使用槽的通常调用函数要快得多。
现在这可能与作战问题没有直接关系。与在对象上使用slots语法相比,它更多地与构建扩展相关。但它确实有助于完成插槽的使用以及背后的一些推理。
类实例的属性有3个属性:实例、属性名称和属性值。
在常规属性访问中,实例充当字典,属性名称充当字典中查找值的键。
实例(属性)->值
在_uu slots_uuu access中,属性的名称充当字典,实例充当字典中查找值的键。
属性(实例)->值
在flyweight模式中,属性的名称充当字典,值充当字典中查找实例的键。
属性(值)->实例
默认情况下,对代理对象的任何属性访问都将为您提供来自代理对象的结果。如果需要在代理对象上添加属性,可以使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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 |
如果我的类中没有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)创建了
假设我的类测试有数千个这样的对象?为每个对象创建一个额外的属性
在下面的示例中,我的类测试包含
1 2 3 4 5 6 7 8 9 10 11 12 | 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' |
你基本上没有使用
在您认为可能需要
1 2 3 4 5 6 7 | class Flyweight(object): def get(self, theData, index): return theData[index] def set(self, theData, index, value): theData[index]= value |
类包装器没有属性-它只提供对基础数据起作用的方法。这些方法可以简化为类方法。实际上,它可以简化为只对底层数据数组进行操作的函数。
最初的问题是关于一般用例,而不仅仅是关于内存。因此,这里应该提到,在实例化大量对象时(例如,将大型文档解析为对象或从数据库中解析时),您还可以获得更好的性能。
这里比较了使用插槽和不使用插槽创建具有一百万个条目的对象树。作为参考,在对树使用普通dict时的性能(OSX上的py2.7.10):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ********** 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):
1 2 3 4 5 6 7 8 9 10 11 | 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) |
测试代码,详细模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 |