14个回答

1738
在Python中,__slots__的作用是什么?有哪些情况应该避免使用它?
TLDR:
特殊属性__slots__允许您明确声明您期望对象实例具有哪些实例属性,并获得预期结果:
1.更快的属性访问。
2.内存中的空间节省。
空间节省来自于:
1.将值引用存储在插槽中,而不是__dict__中。
2.如果父类拒绝了它们并且您声明了__slots__,则拒绝__dict____weakref__的创建。
快速警告:

需要注意的是,您在继承树中只应声明特定的插槽一次。例如:

class Base:
    __slots__ = 'foo', 'bar'

class Right(Base):
    __slots__ = 'baz', 

class Wrong(Base):
    __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar

当你犯了这个错误时,Python不会报错(虽然它可能应该),问题可能不会表现出来,但是你的对象占用的空间将比它们本应该占用的空间更多。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'

最大的注意事项是多重继承 - 不能组合具有非空插槽的多个“父类”。
为了适应这个限制,请遵循最佳实践:将所有但一个或所有父类的抽象内容分解出来,它们的具体类和你的新具体类将从中继承 - 给予抽象内容空插槽(就像标准库中的抽象基类)。
请参见下面的多重继承部分以获取示例。
要求:
- 要使命名为`__slots__`的属性实际存储在插槽中而不是`__dict__`中,类必须继承自`object`(在Python 3中自动完成,但在Python 2中必须显式声明)。 - 要防止创建`__dict__`,您必须继承自`object`,并且继承中的所有类都必须声明`__slots__`,并且它们都不能有`'__dict__'`条目。
如果您希望继续阅读,则有很多详细信息。
为什么使用`__slots__`:更快的属性访问。
Python的创始人Guido van Rossum指出,他创建__slots__是为了更快的属性访问。
很容易证明使用__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分发版,使用(又名heapy)和,没有声明<__slots__>的类实例的大小,且没有其他东西,是64字节。这不包括<__dict__>。感谢Python再次进行懒惰评估,<__dict__>显然只有在被引用时才会存在,但没有数据的类通常是无用的。当被引用时,<__dict__>属性还需要额外的280个字节。
相比之下,声明为<()>(没有数据)的<__slots__>的类实例仅占用16个字节,如果<__slots__>中有一个项目,则总共占用56个字节,如果有两个,则占用64个字节。
对于64位Python,我说明了Python 2.7和3.6中<__slots__>和<__dict__>(未定义slots)的内存消耗,每个点都是3.6中字典增长的地方(除了0、1和2个属性)。
       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中的字典较小,我们仍然可以看到__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__演示:

如果想要禁止创建__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'

为了在子类化slotted对象时允许__dict__创建,只需将'__dict__'添加到__slots__中(请注意,slots是有序的,并且不应重复父类中已经存在的slots):
class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'

并且

>>> swd.__dict__
{'c': 'c'}

或者您甚至不需要在子类中声明__slots__,仍然可以使用父类的slots,但不会限制创建__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__,或者如果你能控制父级,给它们一个空的 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!

__slots__中添加'__dict__'以实现动态赋值:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'

现在:

>>> foo = Foo()
>>> foo.boink = 'boink'

所以,当我们在 slots 中使用 '__dict__' 时,我们会失去一些大小优势,但好处是具有动态赋值能力,并且仍然拥有我们期望的 slots 名称。

当你从一个没有 slots 的对象继承时,如果你使用了 '__slots__',你会得到相同类型的语义 - 在 '__slots__' 中的名称指向 slotted 值,而其他任何值都会被放入实例的 '__dict__' 中。

避免使用 '__slots__' 是因为你想要能够动态添加属性,这实际上不是一个好理由 - 如果需要,只需将 "__dict__" 添加到你的 '__slots__' 中即可。

如果需要该功能,同样可以明确地将 '__weakref__' 添加到 '__slots__' 中。

在子类化命名元组时设置为空元组:

命名元组内置使得不可变实例非常轻量级(本质上就是元组的大小),但如果你对它们进行子类化,则需要自己完成:

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'

你可以通过省略 __slots__ = () 来允许 __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'

避免使用slots的其他情况:

  • 除非slot布局相同,否则在想要使用另一个没有它们(且无法添加它们)的类进行__class__分配时,应避免使用它们。(我非常有兴趣知道谁在做这个以及为什么。)
  • 如果您想对长整型、元组或字符串等可变长度内置对象进行子类化,并且想要向它们添加属性,则应避免使用它们。
  • 如果您坚持通过类属性为实例变量提供默认值,则应避免使用它们。

您可以从其余的__slots__文档(3.7 dev文档是最新的)中进一步了解注意事项,我最近对此作出了重大贡献。

对其他答案的批评

目前的顶级答案引用了过时的信息,在某些重要方面含糊不清且错失机会。

不要“只在实例化大量对象时使用__slots__

我引用:

如果您将实例化大量(数百、数千)相同类的对象,则需要使用__slots__

例如,从collections模块导入的抽象基本类不会被实例化,但为它们声明了__slots__

为什么?

如果用户希望拒绝创建__dict____weakref__,则这些内容不能在父类中提供。

__slots__有助于在创建接口或mixin时实现可重用性。

确实,许多Python用户并不是为了可重用性而编写代码,但是当您需要时,拒绝不必要的空间使用选项非常有价值。

__slots__不会破坏pickle功能

在pickle slotted对象时,您可能会发现它会报错,并显示具有误导性的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__ 的正确使用方法是为了在对象中节省空间。不再使用允许在任何时候向对象添加属性的动态字典,而是使用静态结构,在创建后不允许添加。这样可以避免每个使用 slots 的对象都需要一个字典的开销。

后半部分是一种愿望式的想法,离题了:

虽然这有时是一种有用的优化,但如果 Python 解释器足够动态,只有在实际添加对象时才需要字典,那么这将是完全不必要的。

Python 实际上做了类似的事情,只有在访问时才会创建 __dict__,但是创建大量没有数据的对象相当荒谬。

第二段过于简化,忽略了避免使用__slots__的实际原因。以下不是避免使用slots的真正原因(有关实际原因,请参见上面我回答的其他内容):

它们会以某种方式改变具有slots的对象的行为,这可能会被控制狂和静态类型迷所滥用。

然后继续讨论使用Python实现该反常目标的其他方法,而没有讨论任何与__slots__有关的内容。

第三段更多的是一种愿望式的思考。总体来说,这些内容大都离题,回答者甚至没有撰写,这为该网站的批评者提供了弹药。

内存使用证据

创建一些普通对象和slotted对象:

>>> 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的历史一致,源自在Python 2.2中统一类型和类

如果您对内置类型进行子类化,则会自动向实例添加额外空间以容纳__dict____weakrefs__。(__dict__在使用前不会初始化,因此您不必担心每个创建的实例占用的空间为空字典。)如果您不需要此额外空间,则可以将短语"__slots__ = []"添加到您的类中。


165
这个答案应该成为Python官方文档关于__slots__的一部分。真的!谢谢! - NightElfik
61
信不信由你,大约一年前我为Python文档中的__slots__做出了贡献:https://github.com/python/cpython/pull/1819/files。 - Russia Must Remove Putin
6
非常详细的回答。我有一个问题:是否应该将slots作为默认选项使用,除非使用中遇到了其中的某些注意事项,或者只有在知道会遇到速度/内存问题时才考虑使用slots?换句话说,您是否应该鼓励新手从一开始就学习并使用它们? - freethebees
6
@pepoluan 不需要在 __slots__ 中列出方法名,但感谢您的问题!槽声明会在命名空间(即 __dict__)中创建一个描述符对象,就像方法定义一样。 - Russia Must Remove Putin
4
@ greatvovan,感谢您提醒我注意此事,我已在两个位置更新了文字,以明确这一点。如果您认为我漏掉了其他地方或其他问题,请告诉我。非常感谢。 - Russia Must Remove Putin
显示剩余17条评论

279

引用Jacob Hallen的话:

__slots__ 的正确使用是为了节省对象的空间。与随时允许向对象添加属性的动态字典不同,它是一个静态结构,在创建后不允许添加。[这种使用 __slots__ 可以消除每个对象一个字典的开销。] 虽然有时候这是一个有用的优化,但如果 Python 解释器足够动态,只有在对象实际需要添加属性时才需要字典,那么这将是完全不必要的。

不幸的是,__slots__ 会产生副作用。它们会改变具有插槽的对象的行为方式,这可能会被控制狂人和静态类型迷所滥用。这很糟糕,因为控制狂人应该滥用元类,而静态类型迷应该滥用装饰器,因为在 Python 中,应该只有一种明显的方法来做某件事情。

使 CPython 足够聪明,可以在没有 __slots__ 的情况下处理节省空间,这是一个重大的任务,可能是为什么它还没有列入 P3k 更改列表的原因之一。


95
我希望你能对“静态类型”/修饰符的问题进行一些阐述,但不要使用贬义词。引用第三方是无益的。__slots__与静态类型不涉及同样的问题。例如,在C++中,限制并非针对成员变量的声明,而是针对将意外类型(由编译器强制执行)分配给该变量。我并不支持使用__slots__,只是对这个话题感兴趣。谢谢! - hiwaylon
在Python中,应该只有一种明显的方法来做某件事情。那么使用元类防止全局变量(大写变量被命名为常量)的一个明显方法是什么? - dbow

144
如果你需要实例化大量(数百、数千)相同类的对象,可以使用__slots__。这是一种优化内存的工具。
强烈不建议使用__slots__来限制属性创建。
如果对象中有__slots__,则使用默认pickle协议(最老版本)无法将其序列化;需要指定较新的版本。
Python的其他反射特性也可能受到负面影响。

16
我会演示如何在我的回答中对一个带有槽的对象进行pickling,并且回答你回答中的第一部分。 - Russia Must Remove Putin
7
我理解你的观点,但是插槽(slots)也提供更快的属性访问速度(正如其他人所说)。这种情况下,为了获得更好的性能,你不需要“实例化很多(数百、数千)个相同类的对象”,而是需要大量访问同一实例的同一(插槽)属性。(如果我理解有误请纠正我。) - Rotareti
7
为什么“强烈不建议”使用slots来限制动态属性的创建?最近我在寻找一种约束动态属性创建的方法。我找到了一些东西,但没有提及slots。现在我了解了slots后,似乎正是我之前正在寻找的东西。使用slots来防止在运行时添加属性有什么问题? - 463035818_is_not_a_number
1
@idclev463035818 我认为这没有任何问题。 - Ekrem Dinçel

75

每个Python对象都有一个__dict__属性,其中包含所有其他属性。例如,当您键入self.attr时,Python实际上执行的是self.__dict__['attr']。可以想象,使用字典来存储属性需要一些额外的空间和时间来访问它。

然而,当您使用__slots__时,为该类创建的任何对象都不会具有__dict__属性。相反,所有属性访问都是通过指针直接完成的。

因此,如果要使用C样式结构而不是完整的类,则可以使用__slots__来压缩对象的大小并减少属性访问时间。一个很好的例子是一个Point类,其中包含属性x和y。 如果您将拥有大量点,则可以尝试使用__slots__来节省一些内存。


11
一个定义了__slots__的类的实例,不像C语言风格的结构体。这种类有一个映射属性名和索引的类级别字典,否则以下操作将无法实现: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1) 我认为这个答案需要澄清(如果您想的话,我可以做到)。此外,我不确定instance.__hidden_attributes[instance.__class__[attrname]]是否比instance.__dict__[attrname]更快。 - tzot

31

除了其他答案外,这里是使用__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__,只需要多加一行代码(如果你的类还不是新式类,那么也需要将其变成新式类)。这样一来,你就可以将这些类的内存占用减少五倍,但代价是在必要时需要编写自定义pickle代码。

14

插槽对于使用库函数进行调用时消除了“命名方法分派”非常有用。这在 SWIG 的 文档 中提到过。对于希望减少常见函数的函数开销的高性能库来说,使用插槽要快得多。

现在这可能与原始问题没有直接关系。它更与构建扩展相关,而不是在对象上使用 __slots__ 语法。但它确实有助于完整地理解插槽的用法和背后的一些原因。


12

一个非常简单的__slot__属性的例子。

问题:没有使用__slots__

如果我的类中没有__slots__属性,我可以向我的对象添加新的属性。

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}
如果您看上面的例子,您可以看到obj1obj2有它们自己的xy属性,Python还为每个对象(obj1obj2)创建了一个dict属性。
假设如果我的类Test有成千上万个这样的对象?为每个对象创建一个额外的dict属性将会在我的代码中造成很多开销(内存、计算能力等)。
解决方案:使用__slots__ 现在在以下示例中,我的类Test包含__slots__属性。现在我不能向我的对象添加新属性(除了属性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'

嗨,你可以使用 class Test: __slots__= ("x", "__dict__"),它会起作用。 - Jan

11

类实例属性有三个属性:实例、属性名称和属性值。

常规属性访问中,实例充当字典,属性名称充当字典中查找值的键。

instance(attribute) --> value

__slots__访问中,属性名称充当字典,实例充当字典中查找值的键。

attribute(instance) --> value

享元模式中,属性名称充当字典,属性值充当字典中查找实例的键。

attribute(value) --> instance


1
这是一个很好的分享,不适合放在评论中,因为其中也提到了享元模式,但它并不是对问题本身的完整回答。特别是(仅针对问题的背景):为什么要使用享元模式,以及“应该避免使用哪些情况下的__slots__”? - Merlyn Morgan-Graham
@Merlyn Morgan-Graham,它作为一个提示,让你选择:常规访问、__slots__或者享元模式。 - Dmitry Rubanovich

6
除了其他答案之外,__slots__ 还通过限制属性列表来提供一些排版安全性。这在 JavaScript 中长期存在问题,因为它也允许您向现有对象添加新属性,无论您是否打算这样做。
以下是一个普通的未使用 slots 的对象,它什么都不做,但允许您添加属性:
class Unslotted:
    pass
test = Unslotted()
test.name = 'Fred'
test.Name = 'Wilma'

由于Python区分大小写,两个属性名称大小写相同但实际不同。如果你怀疑其中一个是拼写错误,那就太倒霉了。

使用slots,你可以限制这种情况的发生:

class Slotted:
    __slots__ = ('name')
    pass
test = Slotted()
test.name = 'Fred'      #   OK
test.Name = 'Wilma'     #   Error

这次,第二个属性(Name)被禁止,因为它不在__slots__集合中。

建议在可能的情况下尽可能使用__slots__,以保持对对象更多的控制。


6
自 Python 3.9 开始,可以使用 dict 通过 __slots__ 为属性添加描述。对于没有描述的属性,可以使用 None;即使给出了描述,私有变量也不会显示。
class Person:

    __slots__ = {
        "birthday":
            "A datetime.date object representing the person's birthday.",
        "name":
            "The first and last name.",
        "public_variable":
            None,
        "_private_variable":
            "Description",
    }


help(Person)
"""
Help on class Person in module __main__:

class Person(builtins.object)
 |  Data descriptors defined here:
 |
 |  birthday
 |      A datetime.date object representing the person's birthday.
 |
 |  name
 |      The first and last name.
 |
 |  public_variable
"""

刚刚在看有人是否提到了这个:https://docs.python.org/3/reference/datamodel.html#slots “如果使用字典来分配__slots__,则字典的键将用作槽名。字典的值可以用于提供每个属性的文档字符串,这将被inspect.getdoc()识别并显示在help()的输出中。” - Thingamabobs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接