在Python中__slots__的目的是什么——特别是当我想要使用它时,什么时候不使用它?
当前回答
类实例的属性有3个属性:实例、属性名和属性值。
在常规属性访问中,实例充当字典,属性名充当字典查找值中的键。
实例(属性)——>值
在__slots__访问中,属性的名称充当字典,实例充当字典查找值中的键。
属性(实例)——>值
在flyweight模式中,属性的名称充当字典,值充当查找实例的字典中的键。
属性(value)——>实例
其他回答
引用雅各布·海伦的话:
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对象都有一个__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
除了其他答案,__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__更好,以保持对对象的更多控制。
插槽对于库调用非常有用,可以在进行函数调用时消除“命名方法分派”。SWIG文档中提到了这一点。对于想要减少常用调用函数的函数开销的高性能库来说,使用插槽要快得多。
这可能和OPs问题没有直接关系。它更多地与构建扩展有关,而不是与在对象上使用插槽语法有关。但它确实有助于完善插槽的使用情况以及它们背后的一些原因。