Python深入理解__slots__
文章目录
- 1 功能
- 2 优点
- 2.1 节省内存
- 2.2 更快的属性访问速度
- 3 原理
- 4 使用注意点
- 4.1 赋值
- 4.2 向__slots__中添加__dict__
- 4.3 类属性赋值的限制
- 4.4 __slots__在继承中的问题
- 4.4.1 父类有,子类无
- 4.4.2 父类无,子类有
- 4.4.3 父类有,子类有
- 4.4.4 多父类继承
1 功能
__slots__是python类的魔法属性,可接收一个iterable对象作为属性。定义后,该类实例只能创建__slots__中声明的属性,否则报错。
class Test(object):__slots__ = ['a']if __name__ == '__main__':t = Test()t.a = 1Test.c = 3 # 类属性仍然可以自由添加print(t.c) # 输出:3t.b = 2 # AttributeError: 'Test' object has no attribute 'b'
从上面的例子能看出__slots__的具体功能,它的作用就是用来约束类实例的属性,不允许类实例调用方向实例随意添加属性。
2 优点
2.1 节省内存
python的类在没有定义__slots__时,实例的属性管理其实依赖字典, 也就是魔法属性__dict__,它其实就是个存放实例所有属性及对应值的字典。需要注意的是,定义了__slots__的类实例不再拥有__dict__属性。
在python中字典的内存分配规则是,先预分配一块内存区,当元素添加到一定阈值时进行扩容再分配一块比较大的内存区,由此可见__dict__存储属性会预留比较大的空间,因此会存在比较大的内存浪费。
__slots__的做法就是在创建实例之初就按照__slots__中声明的属性分配定长内存,实际就是定长列表,因此会更加节省内存。
实例说明:
from memory_profiler import profileclass TestA(object):__slots__ = ['a', 'b', 'c']def __init__(self, a, b, c):self.a = aself.b = bself.c = cclass TestB(object):def __init__(self, a, b, c):self.a = aself.b = bself.c = c@profile
def test():temp = [TestA(i, i+1, i+2) for i in range(10000)]del temptemp = [TestB(i, i+1, i+2) for i in range(10000)]del tempif __name__ == '__main__':test()
可以看到定义了__slots__的类实例会更加节省内存。
总结一下实际使用中应该使用__slots__的场景:需要大量创建固定属性的实例时。
2.2 更快的属性访问速度
使用__slots__访问属性,实际节省了一次哈希的过程,因此属性访问速度会更快一些。
实例说明:
from line_profiler import LineProfilerslots = ['a{}'.format(i) for i in range(10000)]class TestA(object):__slots__ = slotsdef __init__(self):for i in slots:self.__setattr__(i, i[1:])class TestB(object):def __init__(self):for i in slots:self.__setattr__(i, i[1:])def test():a = TestA()b = TestB()for i in range(10000):tmp = a.a6666for i in range(10000):tmp = b.a6666if __name__ == '__main__':profiler = LineProfiler()profiler(test)()profiler.print_stats()
3 原理
理解原理的最好方法当然是阅读源码,这里本人就不献丑了,找到一篇大佬的博客对于__slots__源码讲解的还蛮清晰,真正理解了原理,很多关于__slots__的问题也就迎刃而解了。
这里给出一个python版的__slots__实现,用以说明原理:
'Rough approximation of how slots work'class Member(object):'Descriptor implementing slot lookup'def __init__(self, i):self.i = idef __get__(self, obj, type=None):return obj._slotvalues[self.i]def __set__(self, obj, value):obj._slotvalues[self.i] = valueclass Type(type):'Metaclass that detects and implements _slots_'def __new__(self, name, bases, namespace):slots = namespace.get('_slots_')if slots:for i, slot in enumerate(slots):namespace[slot] = Member(i)original_init = namespace.get('__init__') def __init__(self, *args, **kwds):'Create _slotvalues list and call the original __init__' self._slotvalues = [None] * len(slots)if original_init is not None:original_init(self, *args, **kwds)namespace['__init__'] = __init__return type.__new__(self, name, bases, namespace)
从python实现中,我们可以看到,定义__slots__之后,类会为__slots__中每个属性创建一个Member
实例用来记录属性对应值在_slotvalues
中的偏移量,类实例会创建一个定长列表_slotvalues
用来存储对应的属性值。因为使用定长列表和偏移量所以也就更省内存和访问时间。
4 使用注意点
4.1 赋值
__slots__接收可迭代对象赋值,一般使用list或者tuple即可。注意:
- dict赋值只会取keys();
- str赋值只会有一个属性就是赋值的字符串。
从源码中可以知道,__slots__中声明的属性可以重复(然而除浪费空间之外没什么卵用)除了__dict__和__weakref__之外(是的,__slots__之中还可以包含这两个属性)。
4.2 向__slots__中添加__dict__
在__slots__中添加__dict__之后可以恢复动态添加属性功能,然而既生瑜何生亮,从来没这么用过。
class Test(object):__slots__ = ['a', '__dict__']if __name__ == '__main__':t = Test()t.a = 'a't.b = 'b'print(t.__dict__) # 输出:{'b': 'b'}
4.3 类属性赋值的限制
从__slots__原理的介绍中,我们知道__slots__之中声明的类属性其实是member_descriptor实例,类实例在取值时其实时对于实例属性_slotsvalue
做列表寻址。因此如果对类属性重新赋值,会破坏寻址过程,影响实例属性取值。
实例说明:
class Test1(object):__slots__ = ['a']class Test2(object):passif __name__ == '__main__':x = Test1()x.a = 'a'Test1.a = 1print(x.a) # 输出:1y = Test2()y.a = 'a'Test2.a = 1print(y.a) # 输出:a
4.4 __slots__在继承中的问题
在继承中使用__slots__是比较混乱的,情况根据父类子类有没有定义__slots__分为以下几种情况:
4.4.1 父类有,子类无
子类实例继承父类__slots__中的属性,同时也会自动创建__dict__用来动态拓展属性。
class Parent(object):__slots__ = ['x']class Child(Parent):passc = Child()
c.x, c.y = 1, 2
print(c.__slots__) # 输出:['x']
print(c.__dict__) # 输出:{'y': 2}
4.4.2 父类无,子类有
子类继承父类__dict__可动态拓展属性,自身__slots__中属性不变。
class Parent(object):passclass Child(Parent):__slots__ = ['x']c = Child()
c.x, c.y = 1, 2
print(c.__slots__) # 输出:['x']
print(c.__dict__) # 输出:{y: 2}
4.4.3 父类有,子类有
子类__slots__会覆盖父类__slots__,子类仍可访问父类slots中有但自己没有的属性。
class Parent(object):__slots__ = ['x']class Child(Parent):__slots__ = ['y']c = Child()
print(c.__slots__) # 输出:['y']
c.x, c.y = 1, 2
print(c.x, c.y) # 输出:1 2
4.4.4 多父类继承
若只有一个父类有非空__slots__,其他父类无或__slots__为空,则情况同上面单继承类似;
若多个父类有非空__slots__,则会报错。
class Parent(object):__slots__ = ['x']class ParentA(object):__slots__ = ['y']class Child(Parent, ParentA): # 报错:TypeError: multiple bases have instance lay-out conflictpass
推荐阅读:
- HOW __SLOTS__ ARE IMPLEMENTED
- python slots源码分析
Python深入理解__slots__相关推荐
- Python字典理解
本文翻译自:Python Dictionary Comprehension Is it possible to create a dictionary comprehension in Python ...
- python基础——使用__slots__
python基础--使用__slots__ 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: cla ...
- python中的列表理解_掌握『Python列表理解』需要知道的9件事,你知道了吗?
越来越多的人开始学习Python,它已经成为最流行的编程语言之一,这几乎发生在所有领域.比如网络开发.科学计算,当然还有人工智能. 无论想用python干什么,都绕不开学习Python的数据结构.变量 ...
- 我对python的理解_python高级函数以及我对python的理解
常见的高级函数:lambda.map.reduce.filter.list comprehension lambda 匿名函数,限制一个表达式 m = lambda x,y:x+y # 5 m(2,3 ...
- python self理解_Python列表理解
python self理解 In our previous tutorial we learned about Python Sort List. In this tutorial we will l ...
- Python 代码理解 polygon.py
Python 代码理解 polygon.py 1.运行和阅读代码 该代码的主要功能为通过调用不同的包,设置不同的线段数.线段长度和角度等条件,进行不同图形的绘制. 经过环境配置和代码阅读后,初步运行代 ...
- Python优化之__slots__
目录 类中的__dict__ 掀起字典的盖头来 开始主题:__slots__ 为什么__slots__可以 更详细一点的使用方法 后续计划 类中的__dict__ 每个python类都是隐形的继承自o ...
- python yield理解_对Python中Yield的理解
看到下面这段程序的时候,有点不明白这个yield到底是个啥东西,看了网上很多的博客,大致理解了yield的含义,所以记录下来. 要说yield首先要说python中的生成器,那么什么是生成器? 假设有 ...
- python类的__slots__属性、__del__属性、上下文(__enter__和__exit__)、
常规情况下,类的属性字典是共享的,而实例的字典是独立的.如果一个类的属性较少,但是拥有很多的实例,这些实例的属性字典会占用较多的内存空间.对这样的类来说,为了节省内存空间,可以使用__slots__类 ...
最新文章
- **Java有哪些悲观锁的实现_Redis 分布式锁的正确实现方式(Java版)
- 第三轮316工程知识竞赛简讯_【资讯】第五届日本文化知识竞赛决赛
- linux之tr命令使用和总结
- [VSCode] 设置 pylint 以解决 Module ‘torch‘ has no ‘xxx‘ member
- ecshop根目录调用_ecshop调用指定商品分类下的商品
- Velocity - 单例还是非单例
- automak 和 autoconf 介绍
- tp5实现126邮件服务
- FTP服务器是什么意思
- 深度学习数学基础 讲义_深度学习入门部分的讲义3
- windows7计算机不显示u盘,win7插上u盘不显示盘符怎么办|win7 u盘识别不显示盘符的解决方法...
- DAMA数据管理知识体系指南之数据安全管理
- kso经验积累 -- c#发送邮件
- 《Python编程 从入门到实践》第八章 ——函数习题
- 网络软件测试的IP地址,如何快速查出网内空闲IP地址
- Cesium,mars3d,3dtiles 3d地图显示
- 5月编程排行榜出炉,最佳编程语言是谁?
- coffeescript 汉字转拼音代码
- 【Python计量】DID模型构建
- 微信高音质speex格式转为mp3详细教程