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__'
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)})'
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
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
11 回答
类实例的属性有3个属性:实例,属性名称和属性值 .
在 regular attribute access 中,实例充当字典,属性的名称充当该字典查找值的键 .
instance(attribute) --> value
在 slots access 中,属性的名称充当字典,实例充当字典查找值的键 .
attribute(instance) --> value
在 flyweight pattern 中,属性的名称充当字典,该值充当查找实例的字典中的键 .
attribute(value) --> instance
TLDR:
特殊属性
__slots__
允许您显式声明您期望对象实例具有哪些实例属性,并具有预期结果:faster 属性访问 .
space savings 在记忆中 .
节省的空间来自
在槽中存储值引用而不是
__dict__
.如果父类拒绝它们并且声明
__slots__
,则拒绝创建__dict__
和__weakref__
.快速警告
小警告,您应该只在继承树中声明一次特定的插槽 . 例如:
当你弄错了(它可能应该)时,Python不会反对,否则问题可能无法表现出来,但是你的对象会占用比它们应该占用更多的空间 .
最重要的警告是多重继承 - 多个“非空插槽的父类”无法组合 .
为了适应这种限制,请遵循最佳实践:分解除了一个或所有父类的抽象,除了它们的具体类和新的具体类将共同继承 - 给抽象空槽(就像抽象基类一样)标准库) .
有关示例,请参阅下面有关多重继承的部分 .
要求:
要使
__slots__
中指定的属性实际存储在插槽而不是__dict__
中,类必须从object
继承 .为了防止创建
__dict__
,必须从object
继承,并且继承中的所有类必须声明__slots__
,并且它们都不能具有'__dict__'
条目 .如果你想继续阅读,有很多细节 .
为什么要使用__slots__:更快的属性访问 .
Python的创建者,Guido van Rossum,states,他实际上创建了
__slots__
以便更快地进行属性访问 .显示可衡量的显着更快访问是微不足道的:
和
在Ubuntu上,Python 3.5的插槽访问速度提高了近30% .
在Windows上的Python 2中,我测得它的速度提高了约15% .
为什么要使用__slots__:节省内存
__slots__
的另一个目的是减少每个对象实例占用的内存空间 .My own contribution to the documentation clearly states the reasons behind this:
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 3中有较小的dicts,但我们看到实例可以很好地保存内存,这也是您想要使用
__slots__
的一个主要原因 .为了完整我的笔记,请注意,Python 2中类的64字节命名空间中每个插槽的一次性成本,以及Python 3中的72字节,因为插槽使用称为“成员”的属性等数据描述符 .
__slots__的演示:
要拒绝创建
__dict__
,您必须子类object
:现在:
或者继承另一个定义
__slots__
的类现在:
但:
要在创建插槽对象的子类时允许创建
__dict__
,只需将'__dict__'
添加到__slots__
(请注意插槽是有序的,您不应重复已在父类中的插槽):和
或者你甚至不需要在你的子类中声明
__slots__
,你仍然会使用父母的插槽,但不是限制__dict__
的创建:和:
但是,
__slots__
可能会导致多重继承问题:因为从具有非空插槽的父级创建子类失败:
如果你遇到这个问题,你可以从父母那里删除
__slots__
,或者如果你有父母的控制权,给他们空插槽,或重构抽象:将'dict'添加到__slots__以获得动态分配:
现在:
因此,对于插槽中的
'__dict__'
,我们会失去一些规模优势,具有动态分配的优势,并且仍然有我们期望的名称的插槽 .当您从未插入的对象继承时,当您使用
__slots__
时,您将获得相同类型的语义 -__slots__
中的名称指向时隙值,而任何其他值都放在实例的_1311171中 .避免
__slots__
,因为你希望能够动态添加属性实际上不是一个好理由 - 如果需要,只需将"__dict__"
添加到__slots__
.如果需要该功能,可以类似地将
__weakref__
明确添加到__slots__
.在子类化namedtuple时设置为空元组:
内置的namedtuple使得不可变的实例非常轻量级(本质上是元组的大小)但是为了获得好处,如果你将它们子类化,你需要自己动手:
用法:
并且尝试分配意外的属性会引发
AttributeError
,因为我们阻止了__dict__
的创建:您可以通过不使用
__slots__ = ()
来允许__dict__
创建,但是您不能将非空__slots__
与元组的子类型一起使用 .最大警告:多重继承
即使多个父母的非空插槽相同,也不能一起使用:
在父级中使用空
__slots__
似乎提供了最大的灵活性, allowing the child to choose to prevent or allow (通过添加'__dict__'
来获取动态分配,请参阅上面的部分) the creation of a dict :您不必拥有插槽 - 因此,如果您添加它们,并在以后删除它们,它不应该导致任何问题 .
Going out on a limb here :如果您正在编写mixins或使用abstract base classes,这些不是要实例化的,那么这些父项中的空
__slots__
似乎是子类的灵活性方面的最佳方法 .为了演示,首先,让我们创建一个包含我们想要在多重继承下使用的代码的类
我们可以通过继承和声明预期的插槽直接使用上面的内容:
但我们并不关心这一点,那是微不足道的单一继承,我们需要另一个我们也可能继承的类,可能有一个嘈杂的属性:
现在如果两个基地都有非空位,我们就不能这样做了 . (事实上,如果我们想要,我们可以给出
AbstractBase
非空槽a和b,并将它们从下面的声明中删除 - 留下它们会是错误的):现在我们通过多重继承获得了两种功能,并且仍然可以拒绝
__dict__
和__weakref__
实例化:其他避免插槽的情况:
当你想用另一个没有添加它们的类执行
__class__
赋值时,避免使用它们,除非插槽布局相同 . (我非常有兴趣了解谁在做这个以及为什么 . )如果要将变量长度内置类(如long,tuple或str)子类化,并且想要为它们添加属性,请避免使用它们 .
如果您坚持通过类属性为实例变量提供默认值,请避免使用它们 .
你或许能够从我最近做出的重要贡献中删除其余部分的进一步警告 .
批评其他答案
目前最热门的答案引用了过时的信息,并且非常手工波浪,并且在某些重要方面错过了标记 .
不要“在实例化大量对象时仅使用__slots__”
我引用:
例如,来自
collections
模块的抽象基类未实例化,但为它们声明了__slots__
.为什么?
如果用户希望拒绝
__dict__
或__weakref__
创建,那么这些内容必须在父类中不可用 .__slots__
在创建接口或mixin时有助于可重用性 .确实,许多Python用户并没有为可重用性而写作,但是当你,可以选择拒绝不必要的空间使用是有 Value 的 .
__slots__不会破坏酸洗
当腌制一个开槽对象时,你可能会发现它有误导性的
TypeError
:这实际上是不正确的 . 此消息来自最早的协议,这是默认协议 . 您可以使用
-1
参数选择最新协议 . 在Python 2.7中,这将是2
(在2.3中引入),在3.6中它是4
.在Python 2.7中:
在Python 3.6中
所以我会记住这一点,因为这是一个已解决的问题 .
批评(直到2016年10月2日)接受了答复
第一段是半短的解释,半预测 . 这是实际回答问题的唯一部分
下半场是一厢情愿的想法,并且不合时宜:
Python实际上做了类似的事情,只是在访问时创建了
__dict__
,但是创建大量没有数据的对象是相当荒谬的 .第二段过分简化并错过了避免
__slots__
的实际理由 . 以下不是避免插槽的真正原因(出于实际原因,请参阅上面的其余答案 . ):然后继续讨论用Python实现这个反常目标的其他方法,而不是讨论与
__slots__
有关的任何事情 .第三段是更加一厢情愿的想法 . 这主要是一个非常重要的内容,回答者甚至没有创作并为该网站的批评者提供弹药 .
内存使用证据
创建一些普通对象和开槽对象:
实例化其中的一百万:
检查
guppy.hpy().heap()
:访问常规对象及其
__dict__
并再次检查:这符合Python的历史,从Unifying types and classes in Python 2.2
引用Jacob Hallen:
如果要实例化同一类的很多(数百,数千)个对象,您可能希望使用
__slots__
.__slots__
仅作为内存优化工具存在 .非常不鼓励使用
__slots__
来约束属性创建,并且通常你想要避免它,因为它打破了pickle,以及python的一些其他内省特性 .每个python对象都有一个
__dict__
atttribute,它是一个包含所有其他属性的字典 . 例如当你输入self.attr
python实际上是做self.__dict__['attr']
. 您可以想象使用字典存储属性需要一些额外的空间和时间来访问它 .但是,当您使用
__slots__
时,为该类创建的任何对象都不会具有__dict__
属性 . 相反,所有属性访问都是通过指针直接完成的 .因此,如果想要一个C风格的结构而不是一个完整的类,你可以使用
__slots__
来压缩对象的大小并减少属性访问时间 . 一个很好的例子是包含属性x和y的Point类 . 如果要获得很多分数,可以尝试使用__slots__
以节省一些内存 .除了其他答案,以下是使用
__slots__
的示例:因此,要实现
__slots__
,它只需要一个额外的行(并且如果它还没有,则使您的类成为新式类) . 这样你就可以_1111244_,代价是必须编写自定义pickle代码,如果有必要的话 .插槽对于库调用非常有用,可以在进行函数调用时消除"named method dispatch" . 这在SWIG documentation中提到 . 对于希望减少使用插槽的常用函数的函数开销的高性能库,要快得多 .
现在这可能与OPs问题没有直接关系 . 它与构建扩展相关,而不是在对象上使用 slots 语法 . 但它确实有助于完成插槽使用的图片以及它们背后的一些推理 .
你有 - 基本上 - 没有用
__slots__
.在您认为可能需要
__slots__
的时候,您实际上想要使用 Lightweight 或 Flyweight 设计模式 . 这些是您不再需要使用纯Python对象的情况 . 相反,您需要围绕数组,结构或numpy数组的类似Python对象的包装器 .类类包装器没有属性 - 它只提供了对底层数据起作用的方法 . 这些方法可以简化为类方法 . 实际上,它可以简化为仅在基础数据阵列上运行的函数 .
__slot__
属性的一个非常简单的例子 .问题:没有__slots__
如果我的类中没有
__slot__
属性,我可以向对象添加新属性 .如果你看一下上面的例子,你可以看到 obj1 和 obj2 有自己的 x 和 y 属性,而python也为每个对象创建了一个
dict
属性( obj1 和 obj2 ) .假设我的 class Test 有几千个这样的对象?为每个对象创建一个附加属性
dict
会在我的代码中产生大量开销(内存,计算能力等) .解决方案:使用__slots__
现在,在以下示例中,我的类 Test 包含
__slots__
属性 . 现在我无法向我的对象添加新属性(属性x
除外),而python不再创建dict
属性 . 这消除了每个对象的开销,如果您有许多对象,这可能会变得很重要 .另一个有点模糊的
__slots__
用法是从ProxyTypes包中添加属性到对象代理,以前是PEAK项目的一部分 . 它的ObjectWrapper
允许您代理另一个对象,但拦截与代理对象的所有交互 . 它并不常用(并且没有Python 3支持),但我们已经使用它来实现基于龙卷风的异步实现的线程安全阻塞包装器,它通过ioloop反弹对代理对象的所有访问,使用线程安全concurrent.Future
对象要同步并返回结果 .默认情况下,对代理对象的任何属性访问都将为您提供代理对象的结果 . 如果需要在代理对象上添加属性,可以使用
__slots__
.最初的问题是关于一般用例不仅仅是关于记忆 . 所以在这里应该提到的是,在实例化大量对象时你也会获得更好的性能 - 例如,有趣的是将大型文档解析为对象或从数据库解析时 .
下面是使用插槽和无插槽创建具有一百万个条目的对象树的比较 . 作为对树使用普通dicts时的性能参考(OSX上的Py2.7.10):
测试类(ident,appart from slots):
testcode,详细模式: