__slots__
的作用是阻止在实例化类时为实例分配dict,默认情况下每个类都会有一个dict,通过__dict__
访问,这个dict维护了这个实例的所有属性。
代码:
# coding:utf-8
class Base(object):
val = 1
def __init__(self):
pass
class BaseSlots(object):
val = 1
__slots__ = ('y',)
def __init__(self):
pass
class BaseSlots2(object):
val = 1
y = 2
__slots__ = ('y',)
def __init__(self):
pass
b1 = Base()
print 'b1.__dict__ is ', b1.__dict__ # b1.__dict__ is {}
b1.x = 1
print 'bi.x = 1, b1.__dict__ is ', b1.__dict__ # bi.x = 1, b1.__dict__ is {'x': 1}
b2 = BaseSlots()
print 'b2.__dict__ is ', b2.__dict__ # AttributeError: 'BaseSlots' object has no attribute '__dict__'
b2.x = 1 # AttributeError: 'BaseSlots2' object has no attribute 'x'
b2.y = 3
print 'b2.__dict__ is ', b2.__dict__ # AttributeError: 'BaseSlots' object has no attribute '__dict__'
b3 = BaseSlots2()
print 'b3.__dict__ is ', b3.__dict__ # AttributeError: 'BaseSlots2' object has no attribute '__dict__'
b3.x = 1 # AttributeError: 'BaseSlots2' object has no attribute 'x'
b3.y = 3 # 'BaseSlots2' object attribute 'y' is read-only
print 'b3.__dict__ is ', b3.__dict__ # AttributeError: 'BaseSlots2' object has no attribute '__dict__'
输出
# Base()输出
b1.__dict__ is {}
bi.x = 1, b1.__dict__ is {'x': 1}
# BaseSlots()输出
b2.__dict__ is
Traceback (most recent call last): File "test04.py", line 34, in <module> print 'b2.__dict__ is ', b2.__dict__ AttributeError: 'BaseSlots' object has no attribute '__dict__' Traceback (most recent call last): File "test04.py", line 35, in <module> b2.x = 1 AttributeError: 'BaseSlots' object has no attribute 'x' b2.__dict__ is Traceback (most recent call last): File "C:/Users/fred1/PycharmProjects/test/test04.py", line 37, in <module> print 'b2.__dict__ is ', b2.__dict__ AttributeError: 'BaseSlots' object has no attribute '__dict__' # BaseSlots2输出 File "test04.py", line 40, in <module> print 'b3.__dict__ is ', b3.__dict__ AttributeError: 'BaseSlots2' object has no attribute '__dict__' Traceback (most recent call last): File "test04.py", line 41, in <module> b3.x = 1 AttributeError: 'BaseSlots2' object has no attribute 'x' Traceback (most recent call last): File "test04.py", line 42, in <module> b3.y = 3 AttributeError: 'BaseSlots2' object attribute 'y' is read-only Traceback (most recent call last): File "test04.py", line 43, in <module> print 'b3.__dict__ is ', b3.__dict__ AttributeError: 'BaseSlots2' object has no attribute '__dict__'
可见:实例的 __dict__
只保持实例的变量,对于类的属性是不保存的,类的属性包括变量和函数。由于每次实例化一个类都要分配一个新的dict,因此存在空间的浪费,因此有了__slots__
,当定义了__slots__
后,__slots__
中定义的变量变成了类的描述符,相当于java,c++中的成员变量声明,类的实例只能拥有这些个变量,而不在有__dict__
,因此也就不能在增加新的变量。
Python 是一门动态语言,可以在运行过程中,修改对象的属性和增删方法。任何类的实例对象包含一个字典__dict__
, Python通过这个字典将任意属性绑定到对象上。有时候我们只想使用固定的对象,而不想任意绑定对象,这时候我们可以定义一个属性名称集合,只有在这个集合里的名称才可以绑定。__slots__
就是完成这个功能的。
使用__slots__
的主要原因是当你只需要用预定义一系列属性的简单对象,并且不需要携带dict方法时来节省空间。PS:仅在你有大量实例的时候使用。
# coding:utf-8
import sys
import pympler.asizeof as sf
# Pympler is a development tool to measure, monitor and analyze the memory behavior of Python objects in a running Python application.
class Slots(object):
pass
class WithSlots(object):
__slots__ = ('a', 'b', 'c')
pass
n = Slots()
n.a, n.b, n.c = 1, 2, 3
w = WithSlots()
w.a, w.b, w.c = 1, 2, 3
print sys.getsizeof(n) # 32
print sys.getsizeof(w) # 36
print sf.asizeof(n) # 296
print sf.asizeof(w) # 136
# test in Python 2.7.10
__slots__
允许子类重复继承
# coding:utf-8
import sys
import pympler.asizeof as sf
class A(object):
__slots__ = 'a'
pass
class AB(A):
__slots__ = 'b'
pass
ab = AB()
ab.a = ab.b = 23
class ABC(A):
__slots__ = 'a', 'b' # 允许重复继承
pass
abc = ABC()
abc.a = abc.b = 23
print sf.asizeof(ab) # 88
print sf.asizeof(abc) # 96
# test in Python 2.7.10
若子类没有__slots__
,父类的__slots__
对子类无效。
>>> class A(object): __slots__ = 'a'
...
>>> a = A()
>>> a.b = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'b'
>>> class B(A): pass
...
>>> b = B()
>>> b.b = 2
>>> b.b
2
>>>
__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. 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__
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. Changed in version 2.3: Previously, adding __weakref__
to the __slots__
declaration would not enable support for weak references.__slots__
are implemented at the class level by creating descriptors (3.4.2) 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__
declaration is limited to the class where it is defined. As a result, subclasses will have a __dict__
unless they also define __slots__
.__slots__
do not work for classes derived from “variable-length” built-in types such as long, str and tuple.__slots__
. Mappings may also be used; however, in the future, special meaning may be assigned to the values corresponding to each key.We’ve mentioned before how Oyster.com’s Python-based web servers cache huge amounts of static content in huge Python dicts (hash tables). Well, we recently saved over 2 GB in each of four 6 GB server processes with a single line of code — using __slots__
on our Image
class.
Here’s a screenshot of RAM usage before and after deploying this change on one of our servers:
We allocate about a million instances of a class like the following:
class Image(object):
def __init__(self, id, caption, url):
self.id = id
self.caption = caption
self.url = url
self._setup()
# ... other methods ...
By default Python uses a dict to store an object’s instance attributes. Which is usually fine, and it allows fully dynamic things like setting arbitrary new attributes at runtime.
However, for small classes that have a few fixed attributes known at “compile time”, the dict is a waste of RAM, and this makes a real difference when you’re creating a million of them. You can tell Python not to use a dict, and only allocate space for a fixed set of attributes, by settings __slots__
on the class to a fixed list of attribute names:
class Image(object):
__slots__ = ['id', 'caption', 'url']
def __init__(self, id, caption, url):
self.id = id
self.caption = caption
self.url = url
self._setup()
# ... other methods ...
Note that you can also use collections.namedtuple, which allows attribute access, but only takes the space of a tuple, so it’s similar to using __slots__
on a class. However, to me it always feels weird to inherit from a namedtuple class. Also, if you want a custom initializer you have to override __new__
rather than __init__
.
Warning: Don’t prematurely optimize and use this everywhere! It’s not great for code maintenance, and it really only saves you when you have thousands of instances.