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() # Нет ошибок
附加'__dict__'到__slots__以获得动态分配:
class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
class Foo(object):
__slots__ = 'foo', 'bar'
class Bar(object):
__slots__ = 'foo', 'bar' # увы, будет работать, если пусто, т.е. ()
>>> 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
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)})'
TLDR:
特殊属性
__slots__
允许您明确指定您期望从对象实例中获得哪些实例属性,并获得预期结果:节省空间是因为
引用保存到插槽的值,而不是
__dict__
禁止创建,
__dict__
如果__weakref__
父类禁止它们并且您声明__slots__
.简短的警告
在继承树中,您只需要声明一次特定的插槽。
例如:
Python 不介意你错了(可能应该),否则问题可能不会出现,但你的对象会占用更多的空间。
蟒蛇 3.8:
这是因为槽描述符
Base
有一个与Wrong
.最大的警告涉及多重继承 - 不能组合多个“具有非空槽的父类”。
要解决此限制,请遵循以下指南:提取除一个或所有父抽象之外的所有抽象,它们的具体类将从其继承,以及您的新类 - 为抽象提供空槽(就像标准库中的抽象基类一样)
请参阅下面有关多重继承的部分中的示例。
要求:
为了将名为 in 的属性
__slots__
存储在插槽中而不是存储在 中__dict__
,该类必须继承自object
.为了防止创建
__dict__
,你必须从object
w 继承,并且继承中的所有类都必须声明__slots__
,并且它们都不能有__dict__
。如果您想继续阅读,还有更多详细信息。
为什么使用
__slots__
:更快的属性访问。Python 的创建者 Guido van Rossum声称他实际上创建它是
__slots__
为了更快地访问属性。演示更快的访问是一项微不足道的任务:
和
在 Ubuntu 上的 Python 3.5 中,使用插槽的访问速度几乎快了 30%。
我在 Windows 上的 Python 2 中对其进行了测量,结果证明它快了大约 15%。
为什么使用
__slots__
:节省内存另一个目标
__slots__
是减少对象的每个实例占用的内存量。我自己对文档的贡献清楚地说明了这样做的原因
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 中递增的每个点的内存消耗(以字节为单位
__slots__
)__dict__
(属性 0、1 和 2 除外):因此,尽管
__dict__
在 Python 3 中更小,但我们看到它__slots__
跨实例扩展以节省内存的能力如何,这就是使用__slots__
.只是为了我的笔记的完整性,请注意,在 Python 2 中,类名称空间中的每个插槽的一次性成本为 64 字节,在 Python 3 中为 72 字节,因为插槽使用数据描述符,例如称为“成员”的属性。
演示
__slots__
:为了防止创建__dict__
,您必须子类化对象:现在
或者创建另一个定义的类的子类
__slots__
现在:
但:
要允许
__dict__
在子类化时创建具有槽的对象,只需附加__dict__
到__slots__
(注意槽是有序的,您不应该重复已经在父类中的槽):和
或者你甚至不需要
__slots__
在你的子类中声明,你仍然会使用来自父母的插槽,但不要限制创建__dict__
:和:
但是,
__slots__
它可能会导致多重继承问题。从具有两个非空槽的父类创建子类失败:
如果遇到此问题,您可以简单地
__slots__
从父级中删除,或者如果您可以控制父级,则给它们空槽或重构抽象:附加
'__dict__'
到__slots__
以获得动态分配:现在
因此,使用
'__dict__'
in slot 时,我们失去了一些大小优势,因为我们拥有动态分配的好处,并且仍然有我们期望的名称的插槽。当你从一个没有槽的对象继承时,你在使用它时会得到相同的语义
__slots__
——槽中的名称__slots__
指向放置在槽中的值,而任何其他值都放置在__dict__
实例中。避免
__slots__
,因为您希望能够即时添加属性,这并不是一个很好的理由 -如果需要,只需添加'__dict__'
到您的.__slots__
__weakref__
如果您需要__slots__
此功能,您可以显式添加。子类化命名元组时设置一个空元组:
内置类
namedtuple
创建了非常轻量级的不可变实例(本质上是元组的大小),但是如果您将它们子类化,您需要自己来获得好处:用法
并且尝试分配意外属性会引发错误
AttributeError
,因为我们已阻止创建__dict__
:__dict__
您可以通过禁用来启用创建__slots__ = ()
,但不能将非空__slots__
与元组子类型一起使用。最大的警告:多重继承
即使多个父级的非空槽相同,它们也不能一起使用:
在父级上使用 empty
__slots__
似乎提供了最大的灵活性,允许子级选择是阻止还是允许(添加'__dict__'
以获取动态分配,参见上面的部分)创建__dict__
:您不必有插槽 - 因此,如果您稍后添加它们并删除它们,它应该不会导致任何问题。
总结一下:如果您正在编译mixins或使用不打算实例化的抽象基类
__slots__
,那么 这些父类中的空似乎是子类灵活性的最佳方式。为了演示,首先让我们用一个我们想在多重继承中使用的类来创建代码。
我们可以通过继承和声明预期的插槽直接使用上述内容:
但我们不在乎,这是微不足道的单继承,我们需要另一个我们也可以继承的类,也许有一个嘈杂的属性:
现在,如果两个基地都有非空插槽,我们将无法执行以下操作。(事实上,如果我们愿意,我们可以给出非
AbstractBase
空槽a
并将b
它们从下面的声明中排除——留下它们是错误的):现在我们通过多重继承获得了两者的功能,我们仍然可以防止和的实例
__dict__
化__weakref__
:避免插槽的其他情况:
如果您想
__class__
与另一个没有它们的类(并且您不能添加它们)进行分配,请避免使用它们,除非插槽布局相同。(我很想知道谁这样做以及为什么这样做。)Избегайте их, если вы хотите создать подкласс встроенных функций переменной длины, таких как
long
,tuple
илиstr
, и хотите добавить к ним атрибуты.Избегайте их, если вы настаиваете на предоставлении значений по умолчанию через атрибуты класса для переменных экземпляра.
Возможно, вам удастся выявить дополнительные предостережения из остальной части документации
__slots__
(наиболее актуальной является документация разработчика версии 3.7), в которую я недавно внес значительный вклад.перевод ответа от участника @AaronHall