限制类属性的扩展

我们知道Python中,我们很容易为一个类的实例增加属性或者方法。
比如

class Student(object):
    pass

s = Student()
s.name = "lightky"
print(s.name)  #为实例s增加了属性name

def set_id(self, id):
    self.id = id

from types import MethodType

s.set_id = MethodType(set_id, s)
s.set_id(201300000001)
print(s.id)   # 为s增加了set_id方法,从而可以输出id属性


试想,我们如果不希望我们的类被任意增加属性或者方法咋办。这时候可以通过slots变量来限制该class实例能添加的属性。

class Student(object):
    __slots__ = ("name", "id")

s = Student()
s.age = 25  #执行出错

节省空间

当使用slots时, 类实例的dict就会不存在,Python将会为slots列表里的属性分配空间,而不使用dict.
当我们使用大量这个类的实例时,通过该方法能节省巨额的内存空间。

class Image(object):
    def __init__(self, id, caption, url):
        self.id = id
        self.caption = caption
        self.url = url 

img_no_slot = Image(1, "test", "http://lightky.com/img/123.png")
dir(img_no_slot)
'''
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'caption', 'id', 'url']
'''
class Image1(object):
    __slots__=["id", "caption", "url"]
    def __init__(self, id, caption, url):
        self.id = id
        self.caption = caption
        self.url = url 

img_slot = Image1(1, "test", "http://lightky.com/img/123.png")
dir(img_slot)
'''
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'caption', 'id', 'url']
'''

#当存在__slots__时,类的实例中没有__dict__属性

附 Python语言手册3.4.2.4 __slots__

默认情况下,旧式类和新式类都具有一个存储属性的字典。这对于具有非常多的实例属性的对象非常浪费空间。当创建大量的实例时,空间的消耗可能变得很严重。
By default, instances of both old and new-style classes have a dictionary for attribute storage. This wastes space for objects having very few instance variables. The space consumption can become acute when creating large numbers of instances.

在新式类的定义中,该默认行为可以通过定义__slots__覆盖。__slots__声明接受一个实例变量的序列并且在每个实例中只保留为每个变量保存一个值的空间。空间被省下来了是因为不会为每个实例创建__dict__。
The default can be overridden by defining __slots__ in a new-style class definition. The __slots__ declaration takes a sequence of instance variables and reserves just enough space in each instance to hold a value for each variable. Space is saved because __dict__ is not created for each instance.

__slots__

  • 这个类变量可以赋值为一个字符串、可迭代对象或者由实例使用的变量名组成的字符串序列。如果在新式类中定义,__slots__将保留空间给声明的变量且防止为每个实例自动创建__dict__和__weakref__。
    This class variable can be assigned a string, iterable, or sequence of strings with variable names used by instances. If defined in a new-style class, __slots__ reserves space for the declared variables and prevents the automatic creation of __dict__ and __weakref__ for each instance.

使用__slots__的注意事项

Notes on using __slots__

  • 如果继承自一个没有__slots__的类,该类的__dict__属性将始终可以访问,所以在子类中定义__slots__毫无意义。
    When inheriting from a class without __slots__, the __dict__ attribute of that class will always be accessible, so a __slots__ definition in the subclass is meaningless.

  • 没有__dict__变量,实例不可以给不在__slots__中定义的新变量赋值。尝试给没有列出的变量名赋值将引发AttributeError。如果需要动态地给新的变量赋值,那么可以在__slots__的声明的字符串序列中增加'__dict__'。
    Without a __dict__ variable, instances cannot be assigned new variables not listed in the __slots__ definition. Attempts to assign to an unlisted variable name raises AttributeError. If dynamic assignment of new variables is desired, then add '__dict__' to the sequence of strings in the __slots__ declaration.
    2.3版中的变化:在此之前,添加'__dict__'到__slots__声明中不会使得给没有在实例变量名称序列中列出的新属性赋值。
    Changed in version 2.3: Previously, adding '__dict__' to the __slots__ declaration would not enable the assignment of new attributes not specifically listed in the sequence of instance variable names.

  • 因为每个实例都没有__weakref__变量,定义__slots__ 的类不支持对其实例的弱引用。如果需要支持弱引用,可以在__slots__声明的字符串序列中增加'__weakref__'。
    Without a __weakref__ variable for each instance, classes defining __slots__ do not support weak references to its instances. If weak reference support is needed, then add '__weakref__' to the sequence of strings in the __slots__ declaration.
    2.3版中的变化:在此之前,添加'__weakref__'到 __slots__声明中并不会支持弱引用。
    Changed in version 2.3: Previously, adding '__weakref__' to the __slots__ declaration would not enable support for weak references.

  • __slots__在类级别上实现,通过为每个变量名创建描述器(Implementing Descriptors)。因此,类属性不可以用于设置__slots__定义的实例变量的默认值;否则,该类属性将覆盖描述器的赋值。
    __slots__ are implemented at the class level by creating descriptors (Implementing Descriptors) for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment.

  • __slots__定义的动作只限于它所定义的类。因此,子类将具有__dict__,除非它们也定义了__slots__(必须只能包含额外的slots名称)。
    The action of a __slots__ declaration is limited to the class where it is defined. As a result, subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).

  • 如果类定义了一个在基类中定义了的slot,基类slot定义的实例变量将不可访问(除非直接从基类获取它的描述器)。这致使程序的含义无法定义。在未来,可能会增加一个检查来防止这个行为。
    If a class defines a slot also defined in a base class, the instance variable defined by the base class slot is inaccessible (except by retrieving its descriptor directly from the base class). This renders the meaning of the program undefined. In the future, a check may be added to prevent this.

  • 非空的__slots__对于从“可变长度”的内建类型例如long、str和tuple继承的类不能工作。
    Nonempty __slots__ does not work for classes derived from “variable-length” built-in types such as long, str and tuple.

  • 非字符串形式的可迭代类型可以赋值给__slots__。映射也可以使用;然而,在未来,可能对每个键对应的值赋予特殊的含义。
    Any non-string iterable may be assigned to __slots__. Mappings may also be used; however, in the future, special meaning may be assigned to the values corresponding to each key.

  • __class__赋值只有在两个雷具有相同的__slots__是才工作。
    __class__ assignment works only if both classes have the same __slots__.
    2.6版中的变化:在此之前,__class__赋值将抛出一个错误如果新式类或旧式类具有__slots__。
    Changed in version 2.6: Previously, __class__ assignment raised an error if either new or old class had __slots__.