摘要:在本教程中,你将学习关于Python中的__slots__以及如何使用它来提高类的效率。
Python __slots__ 的简介
下面定义了一个Point2D类,它有两个属性,即x和y坐标:
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point2D({self.x},{self.y})'
每个Point2D类的实例都有自己的__dict__属性,用于存储实例属性。例如:
point = Point2D(0, 0)
print(point.__dict__)
默认情况下,Python使用字典来管理实例属性。字典允许你在运行时动态地向实例添加更多属性。然而,它也会带来一定的内存开销。如果Point2D类有很多对象,就会有很大的内存开销。
为了避免内存开销,Python引入了槽(slots)。如果一个类只包含固定(或预定义)的实例属性,你可以使用槽(slots)来指示Python使用更紧凑的数据结构,而不是字典。
例如,如果Point2D类只有两个实例属性,你可以像这样在槽(slots)中指定属性:
class Point2D:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point2D({self.x},{self.y})'
在这个例子中,你将一个可迭代对象(元组)赋给了包含类中要使用的属性名称的槽(slots)。
通过这样做,Python将不再使用__dict__来存储类的实例。下面的代码会导致AttributeError错误:
point = Point2D(0, 0)
print(point.__dict__)
错误信息如下:
AttributeError: 'Point2D' object has no attribute '__dict__'
相反,你会在类的实例中看到__slots__。例如:
point = Point2D(0, 0)
print(point.__slots__)
输出结果为:
('x', 'y')
此外,你不能在运行时动态地向实例添加更多属性。以下代码会导致错误:
point.z = 0
错误信息如下:
AttributeError: 'Point2D' object has no attribute 'z'
然而,你可以将类属性添加到类中:
Point2D.color = 'black'
pprint(Point2D.__dict__)
输出结果为:
mappingproxy({'__doc__': None,
'__init__': <function Point2D.__init__ at 0x000001BBBA841310>,
'__module__': '__main__',
'__repr__': <function Point2D
.__repr__ at 0x000001BBBA8413A0>,
'__slots__': ('x', 'y'),
'color': 'black',
'x': <member 'x' of 'Point2D' objects>,
'y': <member 'y' of 'Point2D' objects>})
这段代码有效的原因是因为 Python 将槽(slots)应用于类的实例,而不是应用于类本身。
Python的__slots__和单继承
让我们在继承的情况下探讨槽(slots)的用法。
基类使用了槽(slots),但子类没有使用
下面的代码定义了一个基类 Point2D 和一个继承自 Point2D 类的子类 Point3D:
class Point2D:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point2D({self.x},{self.y})'
class Point3D(Point2D):
def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
if __name__ == '__main__':
point = Point3D(10, 20, 30)
print(point.__dict__)
输出结果:
{'z': 30}
Point3D 类没有定义槽(slots),因此其实例具有 __dict__ 属性。在这种情况下,子类 Point3D 会使用其基类的槽(slots)(如果可用),并且还会使用实例字典。
如果希望 Point3D 类也使用槽(slots),可以通过以下方式定义额外的属性:
class Point3D(Point2D):
__slots__ = ('z',)
def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
请注意,不需要在子类的 __slots__ 中重复指定已经在基类的 __slots__ 中定义的属性。
现在,Point3D 类将使用槽来存储所有属性,包括 x、y 和 z。
基类不使用槽(__slots__),子类使用槽(__slots__)
下面的例子定义了一个基类 Shape,它没有使用槽(__slots__),而子类 Point2D 使用了槽(__slots__):
class Shape:
pass
class Point2D(Shape):
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
if __name__ == '__main__':
# 使用槽和字典来存储实例属性
point = Point2D(10, 10)
print(point.__slots__)
print(point.__dict__)
# 可以在运行时添加属性
point.color = 'black'
print(point.__dict__)
输出结果:
('x', 'y')
{'color': 'black'}
在这种情况下,Point2D 类的实例同时使用了 __slots__ 和字典来存储实例属性。
总结
Python 使用字典来存储类的实例属性。这使得您可以在运行时动态地向实例添加更多属性,但也会增加内存开销。
如果类具有预定义的实例属性,请在类中定义 __slots__,以指示 Python 不要使用字典来存储实例属性。使用 __slots__ 可以优化内存,特别是当类有大量对象时。