引言
在该系列的上一篇文章中,我们留下了一个问题,Python中一切皆对象,Python对象底层是对dict的弱封装,那么为什么有些内置类找不到__dict__属性呢?似乎,我们前面所介绍的自相矛盾了……
通过今天这篇文章,希望能够自圆其说,并学会基于这种特性,进行实例对象的内存优化。
内置类型对象没有__dict__属性
首先我们通过代码,来验证下内置类型有没有__dict__属性:
# AttributeError: 'str' object has no attribute '__dict__'
print('abc'.__dict__)
# AttributeError: 'int' object has no attribute '__dict__'
print((10).__dict__)
# AttributeError: 'list' object has no attribute '__dict__'
print([].__dict__)
# AttributeError: 'tuple' object has no attribute '__dict__'
print((1, 2).__dict__)
# AttributeError: 'dict' object has no attribute '__dict__'
print({'name': 'zs'}.__dict__)
对int、str、list、tuple、list等实例对象,访问其__dict__属性,都会报AttributeError的错误,提示对象并没有__dict__的属性。
其实,内置类型的对象,之所以没有__dict__属性,主要是由于它们通常是使用C语言实现的,而且,为了使用性能,有自己特定的内存布局和属性的管理方式。
那么,我们自定义的类型,是否也可以进行内存布局的优化,而不是使用dict方式进行存储呢?
答案是肯定的,这就是我们这篇文章,重点要介绍的Python的特殊属性__slots__。
灵活与性能的Trade-Off
在Python中,我们可以通过__slots__属性来进行自定义类型的内存优化与性能提升。当使用__slots__进行属性的限定之后,该类的实例将不再以默认的字典(dict)方式来存储实例数据,转而采用一种基于数组的更加紧凑的数据结构。
因此,当有场景需要创建大量的实例对象时,可以尝试使用__slots__来显著减少内存占用和程序执行时间。
以实际代码来看__slost__的用法及效果:
import sys
class DaGongRen:
def __init__(self, name, age):
self.name = name
self.age = age
class SlotDaGongRen:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
dgr = DaGongRen('张三', 18)
sdgr = SlotDaGongRen('张三', 18)
# 通过sys.getsizeof()函数,获取一个对象的字节数
print(sys.getsizeof(dgr))
print(sys.getsizeof(sdgr))
# 试着看一下sdgr的__dict__,会发现没了……
print(sdgr.__dict__)
代码中,我们使用了sys内置模块的getsizeof()方法,来查看一个对象占用内存的字节数,这里放一下该函数的定义:
执行结果:
从执行结果,可以看出,使用__slot__限定属性之后,内存确实降低了8个字节,具体为什么分别是56和48,是由具体的实现决定的,可以不用在意,我们只需要注意到这个比较优势即可。
此外,使用__slost__之后,__dict__不见了,毕竟换了一种基于数组的实例对象的数据存储方式,不再使用字典了。
而没有了__dict__属性,一个最直接的影响时,不能再动态给实例对象添加属性了。此外,需要注意的是,如果一个类继承自有__slots__属性的类时,如果不声明__slots__属性,又会出现__dict__属性,这时,内存的节省可能就不太有意义了。
直接看代码:
class DaGongRen:
def __init__(self, name, age):
self.name = name
self.age = age
class SlotDaGongRen:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
class Programmer(SlotDaGongRen):
def __init__(self, name, age, language):
super().__init__(name, age)
self.language = language
if __name__ == '__main__':
dgr = DaGongRen('张三', 18)
sdgr = SlotDaGongRen('张三', 18)
coder = Programmer('光头', 18, 'Python')
coder.gender = '女'
print(coder.__dict__)
# 给dgr实例对象添加一个gender属性
dgr.gender = '女'
print(dgr.gender)
# 试着给sdgr实例对象添加gender属性
sdgr.gender = '女'
print(sdgr.gender)
执行结果:
总结
稍微总结一下__slots__属性的特性及需要注意的点:
1、定义__slots__属性后,会将该类的实例对象的数据将不再使用默认的基于字典的方式存储,而采用基于数组的更加紧凑的方式进行存储,内存的占用和执行时间将得到大大消减。
2、一但使用了__slots__属性,将会失去一些动态语言的灵活性,比如不能动态添加新的属性了,所以,具体使用时,需要进行权衡取舍。
3、当在继承中使用__slots__时,需要尤其注意。如果继承自使用__slots__的基类,那么子类也需要定义__slots__来存储自己的属性(即使它不会添加任何属性,也应该如此)。否则,子类的运行速度将更慢,占用的内存也更多,比完全不使用__slots__时还要更糟。
4、使用__slots__将会破坏期望实例具有底层__dict__属性的代码。
5、可以在__slots__属性中,加入__dict__从而让类的实例对象具有__dict__,但是,这样就失去了节省内存的意义。
6、如果没有把__weakref__加入到__slots__中,那么该类的实例对象将不能作为弱引用的目标。
感谢您的拨冗阅读,如果这篇文章对您学习Python有所帮助,欢迎点赞收藏。