在Python中__slots__的目的是什么——特别是当我想要使用它时,什么时候不使用它?
当前回答
插槽对于库调用非常有用,可以在进行函数调用时消除“命名方法分派”。SWIG文档中提到了这一点。对于想要减少常用调用函数的函数开销的高性能库来说,使用插槽要快得多。
这可能和OPs问题没有直接关系。它更多地与构建扩展有关,而不是与在对象上使用插槽语法有关。但它确实有助于完善插槽的使用情况以及它们背后的一些原因。
其他回答
本质上,你没有使用__slots__。
当你认为你可能需要__slots__时,你实际上想要使用轻量级或Flyweight设计模式。在这些情况下,您不再希望使用纯Python对象。相反,您需要一个Python类对象的包装器来包装数组、结构体或numpy数组。
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= 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).
如果你要实例化很多(成百上千)同一个类的对象,你会想要使用__slots__。__slots__仅作为内存优化工具存在。
强烈建议使用__slots__来约束属性创建。
使用__slots__ pickle对象将无法使用默认的(最古老的)pickle协议;有必要指定一个更高的版本。
python的其他一些自省特性也可能受到不利影响。
最初的问题是关于一般用例,而不仅仅是关于内存。 因此,这里应该提到的是,当实例化大量对象时,您也会获得更好的性能——有趣的是,当将大型文档解析为对象或从数据库中解析时。
下面是使用插槽和不使用插槽创建具有一百万个条目的对象树的比较。作为对树使用普通字典时的性能参考(OSX上的Py2.7.10):
********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict
测试类(标识,除了槽):
class Element(object):
__slots__ = ['_typ', 'id', 'parent', 'childs']
def __init__(self, typ, id, parent=None):
self._typ = typ
self.id = id
self.childs = []
if parent:
self.parent = parent
parent.childs.append(self)
class ElementNoSlots(object): (same, w/o slots)
Testcode,详细模式:
na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
print '*' * 10, 'RUN', i, '*' * 10
# tree with slot and no slot:
for cls in Element, ElementNoSlots:
t1 = time.time()
root = cls('root', 'root')
for i in xrange(na):
ela = cls(typ='a', id=i, parent=root)
for j in xrange(nb):
elb = cls(typ='b', id=(i, j), parent=ela)
for k in xrange(nc):
elc = cls(typ='c', id=(i, j, k), parent=elb)
to = time.time() - t1
print to, cls
del root
# ref: tree with dicts only:
t1 = time.time()
droot = {'childs': []}
for i in xrange(na):
ela = {'typ': 'a', id: i, 'childs': []}
droot['childs'].append(ela)
for j in xrange(nb):
elb = {'typ': 'b', id: (i, j), 'childs': []}
ela['childs'].append(elb)
for k in xrange(nc):
elc = {'typ': 'c', id: (i, j, k), 'childs': []}
elb['childs'].append(elc)
td = time.time() - t1
print td, 'dict'
del droot
除了在这里的其他答案中描述的无数优点-内存意识的紧凑实例,比更易变的__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