python-__slots__性能优化

__slots__介绍

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

class Student(object):
    pass

然后,尝试给实例绑定一个属性:

>>> s = Student()
>>> s.name = 'Jone' # 动态给实例绑定一个属性
>>> print(s.name)
Jone

但是,给一个实例绑定的方法,对另一个实例是不起作用的:

>>> s2 = Student() # 创建新的实例
>>> s2.set_age(25) # 尝试调用方法
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'

为了给所有实例都绑定方法,可以给class绑定方法:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

给class绑定方法后,所有实例均可调用:

>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

使用__slots__

但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加nameage属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

然后,我们试试:

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 30# 绑定属性'age'
>>> s.score = 88# 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999

减少内存额外消耗 / 提升属性访问速度 

使用用 __slots__ 前,Python 无法在对象创建时,直接分配?个固定量的内存来保存所有的属性。因此,对于许多“小”类对象而言,使用 dict 维护实例将额外存储许多数据,占用大量不必要的内存,特别是创建成千上万的实例时。

使用 __slots__ 后,Python 内部的 __new__ 方法将不再创建一个 dict 来保存实例属性,而是用一个固定大小的数组来取代,从而节省大量空间。事实上,Python 内置的 dict 本质是一个哈希表 hashtable,是一种用空间换时间的数据结构。为了解决冲突问题,当 dict 使用量超过2/3 时,Python 会根据情况进行 2-4 倍的扩容。由此又佐证了取消 __dict__ 的使用可大幅减少实例的空间消耗。通常,使用 __slots__ 能降低 40%~50% 的内存占用率,即牺牲了一定的灵活性来保证性能。这也是 __slots__ 关键字的设计初衷,但现在很多人都用错了地方。

>>> class Point_new_son(Point_new):    
     pass
>>> g = Point_new_son()
>>> g.z
Traceback (most recent call last):  
File "<pyshell#39>", line 1, in <module>    
    g.z
AttributeError: 'Point_new_son' object has no attribute 'z'
>>> g.z = 2

注意,__slots__ 定义的属性仅对当前类实例起作用,而对继承的子类无效,如上例所示。除非子类中也定义 __slots__ ,从而令子类实例允许定义的属性为自身的 __slots__ 加上父类的 __slots__ 。

实例说明

安装 ipython_memory_usage 模块:

pip install ipython_memory_usage

然后打开 Jupyter Notebook,新建一个 py3 主文件,导入模块并启动内存观测功能:

自此,每个 ceil 执行完都将按上述格式返回运行时间与内存占用情况,以便于观察。

接着,另新建一个 without_slots.py 模块,存放一个未使用 __slots__ 关键字的示例:

在主文件中导入 without_slots.py 模块,并随之显示运行情况:

接着,另新建一个 with_slots.py 模块,存放一个使用了 __slots__ 关键字的示例:

在主文件中导入 with_slots.py 模块,并随之显示运行情况:

对比可见,使用 __slots__ 关键字减轻了将近 60% 的内存负担,内存占用率几乎至少有 40%~50% 的降低。


参考:

  • https://www.liaoxuefeng.com/wiki/1016959663602400/1017501655757856
  • https://www.codenong.com/cs105810661/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值