ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python面向对象:__slots__限制实例属性的内存优化

Python面向对象:__slots__限制实例属性的内存优化 Python面向对象__slots__限制实例属性的内存优化一、开篇当实例属性太多时Python的每个实例都有一个__dict__字典来存储属性——这很灵活但也很重。当你需要创建成千上万个实例时比如游戏中的粒子、金融数据中的K线每个实例的字典开销就成了瓶颈。⌨️__slots__就是解决方案# 没有__slots__——每个实例带着一个__dict__字典classPointNormal:def__init__(self,x,y):self.xx self.yy# 使用__slots__——实例不再有__dict__内存大幅减少classPointSlots:__slots__[x,y]# 声明允许的属性def__init__(self,x,y):self.xx self.yy# 测试importsys p1PointNormal(1,2)p2PointSlots(1,2)print(f普通Point:{sys.getsizeof(p1)}字节 __dict__:{sys.getsizeof(p1.__dict__)}字节)print(fSlots Point:{sys.getsizeof(p2)}字节)# slots版本通常节省50%以上的内存# 创建100万个实例对比# 普通版约 100万 × (56 104) ≈ 160MB# Slots版约 100万 × 48 ≈ 48MB# 差距巨大__slots__是Python的内存优化利器。它不仅减少内存占用还能防止意外添加属性、略微提升属性访问速度。二、__slots__的基本使用2.1 声明和使用classUser:定义用户类——只允许特定的属性__slots__[username,email,age,_password]def__init__(self,username,email,age0):self.usernameusername self.emailemail self.ageage self._passwordNonedefset_password(self,password):self._passwordpassword# ✅ 可以使用声明的属性userUser(zhangsan,zstest.com,25)print(user.username)# zhangsanuser.age26print(user.age)# 26# ❌ 不能添加未声明的属性# user.phone 13800138000 # AttributeError!# User object has no attribute phone# ❌ 没有__dict__了# print(user.__dict__) # AttributeError!# ⚠️ __slots__只限制实例属性——类属性不受影响User.site_name测试站点print(User.site_name)# 测试站点2.2 __slots__不会自动创建属性# ⚠️ 常见误解__slots__不会自动创建属性# 你仍然需要在__init__中初始化classBadExample:__slots__[name,age]# 没有__init__# obj BadExample()# print(obj.name) # AttributeError! __slots__中的属性不会被自动初始化classGoodExample:__slots__[name,age]def__init__(self,name,age):self.namename# 必须手动初始化self.ageage objGoodExample(张三,25)print(obj.name)# 张三# __slots__只是声明了允许哪些属性# 不会自动给你创建默认值2.3 包含__dict__的__slots__# 如果你把__dict__加到__slots__中# 实例又可以有动态属性了——但失去了内存优势classFlexible:__slots__[name,__dict__]# 允许动态属性def__init__(self,name):self.namename objFlexible(测试)obj.extra动态添加# ✅ 可以——因为__dict__在slots中print(obj.extra)# 动态添加# 通常不推荐这样做——如果保留动态属性用slots的意义就不大了三、__slots__在继承中的行为3.1 父类有slots子类呢classParent:__slots__[x,y]def__init__(self,x,y):self.xx self.yyclassChild(Parent):子类没有定义__slots__——会自动获得__dict__passcChild(1,2)c.z3# ✅ 可以——子类有__dict__print(c.z)# 3print(c.__dict__)# {z: 3}# 子类如果要保持slots优势——必须也定义__slots__classChildSlots(Parent):__slots__[z]# 子类自己的slotsdef__init__(self,x,y,z):super().__init__(x,y)self.zz csChildSlots(1,2,3)# cs.w 4 # ❌ AttributeError —— 没有__dict__print(cs.x,cs.y,cs.z)# 1 2 33.2 多重继承中的__slots__# 当多个父类都定义了__slots__时# 子类继承所有父类的slotsclassA:__slots__[a]classB:__slots__[b]classC(A,B):__slots__[c]cC()c.a1# 来自A的slotsc.b2# 来自B的slotsc.c3# 来自C的slotsprint(c.a,c.b,c.c)# 1 2 3# ⚠️ 如果某个父类没有定义__slots__有__dict__# 子类的slots就失效了——子类会有__dict__四、__slots__的其他效果4.1 阻止动态属性# __slots__的一个副作用——防止意外添加属性# 这在某些场景下是好事防止拼写错误classConfig:__slots__[host,port,debug]def__init__(self):self.hostlocalhostself.port8080self.debugFalseconfigConfig()# 如果你不小心拼错了属性名——立即报错# config.debuh True # AttributeError!# 而不是默默地给实例添加一个新属性这是普通类的行为4.2 性能提升importtimeclassPointNormal:def__init__(self,x,y):self.xx self.yyclassPointSlots:__slots__[x,y]def__init__(self,x,y):self.xx self.yy# 属性访问速度对比n10_000_000pnPointNormal(1,2)psPointSlots(1,2)starttime.perf_counter()for_inrange(n):_pn.xpn.yprint(f普通类:{time.perf_counter()-start:.3f}秒)starttime.perf_counter()for_inrange(n):_ps.xps.yprint(fSlots类:{time.perf_counter()-start:.3f}秒)# Slots通常快10-20%五、实践何时使用__slots__# ✅ 适合使用__slots__的场景# 1. 大量实例百万级——内存优化显著# 2. 数据类/值对象只存储少量数据# 3. 需要防止意外添加属性# ✅ 典型示例游戏中的粒子classParticle:__slots__[x,y,vx,vy,life,color]def__init__(self,x,y,vx,vy,life,color):self.x,self.yx,y self.vx,self.vyvx,vy self.lifelife self.colorcolor# ❌ 不适合使用__slots__的场景# 1. 需要动态添加属性# 2. 需要序列化某些库依赖__dict__# 3. 多继承复杂的情况# 4. 实例数量少几百个以内不值得# 对于大多数应用来说——用dataclass就够了# Python 3.10的dataclass支持slotsTrue参数fromdataclassesimportdataclassdataclass(slotsTrue)classPoint:x:floaty:float# 这等价于手动定义__slots__但更简洁六、总结__slots__是Python的内存优化开关。用好了大幅节省内存并提升性能但它也是把双刃剑——牺牲了动态属性的灵活性。核心要点__slots__禁止创建__dict__——这是内存节省的来源只限实例属性——类属性不受影响不会自动初始化属性——仍需要在__init__中赋值子类需单独定义__slots__——否则会获得__dict__大量实例万级以上再考虑——普通场景用dataclass(slotsTrue)✅一句话__slots__适合值的容器——大量小对象、固定属性集、讲究内存效率的场景。
返回列表