在Python中__slots__的目的是什么——特别是当我想要使用它时,什么时候不使用它?


当前回答

除了在这里的其他答案中描述的无数优点-内存意识的紧凑实例,比更易变的__dict__承载实例更不容易出错等等-我发现使用__slots__提供了更清晰的类声明,因为类的实例变量显式地公开。

为了解决__slots__声明的继承问题,我使用了这个元类:

import abc

class Slotted(abc.ABCMeta):
    
    """ A metaclass that ensures its classes, and all subclasses,
        will be slotted types.
    """
    
    def __new__(metacls, name, bases, attributes, **kwargs):
        """ Override for `abc.ABCMeta.__new__(…)` setting up a
            derived slotted class.
        """
        if '__slots__' not in attributes:
            attributes['__slots__'] = tuple()
        
        return super(Slotted, metacls).__new__(metacls, name, # type: ignore
                                                        bases,
                                                        attributes,
                                                      **kwargs)

…如果在继承塔中声明为基类的元类,则确保从该基类派生的所有内容都将正确继承__slots__属性,即使中间类没有声明任何属性。像这样:

# note no __slots__ declaration necessary with the metaclass:
class Base(metaclass=Slotted):
    pass

# class is properly slotted, no __dict__:
class Derived(Base):
    __slots__ = 'slot', 'another_slot'

# class is also properly slotted:
class FurtherDerived(Derived):
    pass

其他回答

除了其他答案,__slots__还通过将属性限制在预定义的列表中增加了一点排版安全性。这一直是JavaScript的一个问题,它还允许您向现有对象添加新属性,无论您是否有意。

下面是一个普通的unslot对象,它什么都不做,但是允许你添加属性:

class Unslotted:
    pass
test = Unslotted()
test.name = 'Fred'
test.Name = 'Wilma'

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

使用插槽,你可以限制它:

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

这一次,第二个属性(Name)是不允许的,因为它不在__slots__集合中。

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

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

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

所以如果你想要一个C风格的结构而不是一个完整的类,你可以使用__slots__来压缩对象的大小并减少属性访问时间。一个很好的例子是一个包含属性x和y的Point类。如果你要有很多点,你可以尝试使用__slots__来节省一些内存。

除了在这里的其他答案中描述的无数优点-内存意识的紧凑实例,比更易变的__dict__承载实例更不容易出错等等-我发现使用__slots__提供了更清晰的类声明,因为类的实例变量显式地公开。

为了解决__slots__声明的继承问题,我使用了这个元类:

import abc

class Slotted(abc.ABCMeta):
    
    """ A metaclass that ensures its classes, and all subclasses,
        will be slotted types.
    """
    
    def __new__(metacls, name, bases, attributes, **kwargs):
        """ Override for `abc.ABCMeta.__new__(…)` setting up a
            derived slotted class.
        """
        if '__slots__' not in attributes:
            attributes['__slots__'] = tuple()
        
        return super(Slotted, metacls).__new__(metacls, name, # type: ignore
                                                        bases,
                                                        attributes,
                                                      **kwargs)

…如果在继承塔中声明为基类的元类,则确保从该基类派生的所有内容都将正确继承__slots__属性,即使中间类没有声明任何属性。像这样:

# note no __slots__ declaration necessary with the metaclass:
class Base(metaclass=Slotted):
    pass

# class is properly slotted, no __dict__:
class Derived(Base):
    __slots__ = 'slot', 'another_slot'

# class is also properly slotted:
class FurtherDerived(Derived):
    pass

类实例的属性有3个属性:实例、属性名和属性值。

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

实例(属性)——>值

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

属性(实例)——>值

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

属性(value)——>实例

除了其他答案,这里还有一个使用__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__,它只需要额外的一行(并使您的类成为一个新样式的类,如果它还不是的话)。通过这种方式,您可以将这些类的内存占用减少5倍,代价是必须编写自定义pickle代码(如果需要的话)。