Python3 构造函数与析构函数详解及默认参数陷阱
本文深入解析Python3中魔法方法__init__与__del__的执行机制,阐明析构函数仅在引用计数归零时由垃圾回收器调用。重点剖析使用列表或字典作为类成员默认参数导致的共享地址问题,并提供通过显式初始化避免数据污染的解决方案。
核心要点概述
Python魔法方法与内存管理机制
魔法方法是指那些被双下划线(__)包围的特殊方法
这类方法会在特定场景下由解释器自动触发调用
在实例化对象的过程中,构造函数必然会被执行
对于析构函数__del__,执行del语句删除对象引用时,并不保证立即触发该函数
只有当对象的引用计数降为0时,系统才会调用析构函数以回收相关资源
析构函数的调用时机取决于Python的垃圾回收机制。当对象不再被任何变量引用时,垃圾回收器会自动回收其占用的资源并执行析构逻辑
#coding=utf-8
'''
魔法方法,被__双下划线所包围
在适当的时候自动被调用
'''
#构造init、析构del
class Rectangle:
def __init__(self,x,y):
self.x = x
self.y = y
print('构造')
'''
del析构函数,并不是在del a对象的时候就会调用该析构函数
只有当该对象的引用计数为0时才会调用析构函数,回收资源
析构函数被python的垃圾回收器销毁的时候调用。当某一个对象没有被引用时,垃圾回收器自动回收资源,调用析构函数
'''
def __del__(self):
print('析构')
def getPeri(self):
return (self.x + self.y)*2
def getArea(self):
return self.x * self.y
if __name__ == '__main__':
rect = Rectangle(3,4)
# a = rect.getArea()
# b = rect.getPeri()
# print(a,b)
rect1 = rect
del rect1
# del rect
while 1:
pass
默认参数引发的共享引用陷阱
补充说明:Python类成员变量在使用默认值进行初始化时需警惕潜在问题
补充说明:Python类成员变量在使用默认值进行初始化时需警惕潜在问题
涉及技术栈:python2.7 python 3.6
基于以下典型场景展开分析
定义类A,其中包含成员变量l(列表类型)和d(字典类型)
在类A的构造函数__init__中,利用缺省参数对成员变量l和d进行初始化
具体实现代码示例如下
class A:
def __init__(self, l=["name"], d={"key1": "test"}):
self.l = l
self.d = d
在主逻辑中创建多个A类的实例,并在构造时依赖构造函数提供的缺省值
if __name__ == "__main__":
a1 = A()
a2 = A()
print (id(a1.l), id(a1.d))
print (id(a2.l), id(a2.d))
运行上述代码后的输出结果如下
python2.7
(56305416L, 56376040L)
(56305416L, 56376040L)
python3.6
2036953558112
2036953558112
结果显示,使用相同缺省值初始化的两个A类实例,其成员变量l和d指向了内存中的同一地址
假设在主逻辑中分别对实例a1和a2进行操作
if __name__ == "__main__":
a1 = A()
a2 = A()
# print (id(a1.l), id(a1.d))
# print (id(a2.l), id(a2.d))
a1.l.extend(["a", "b", "C", "Xa"])
a1.d["key"] = "value"
print ("a1", a1.l, a1.d)
print ("a2", a2.l, a2.d)
此时的程序输出结果如下
a1 ['name', 'a', 'b', 'C', 'Xa'] {'key1': 'test', 'key': 'value'} a2 ['name', 'a', 'b', 'C', 'Xa'] {'key1': 'test', 'key': 'value'}
尽管仅修改了a1,但a2的成员变量也随之发生了改变
该问题在实际开发中常见于使用wxGrid组件的场景
class MyGrid(wx.grid.Grid):
def __init__(self, parent, col_titles=["a", "b", "c"], data=[["1", "2", "3"]]):
wx.grid.Grid__init__(self, parent=parent)
self.col_titls = col_titles
self.data = data
...
def AppendData(self, rows=[], clear=Flase):
self.data.extend(rows)
msg = wx.grid.GridTableMessage(self,
wx.grid.GRIDTABLE_NOTIFY_ROWS_DELETED,
0,
len(rows))
self.ProcessTableMessage(msg)
class MyFrame(wx.Frame):
def __init(self, parent, title=""):
wx.Frame.__init__(self, parent=parent, title=title)
self.grid1 = MyGrid(self)
self.grid2 = MyGrid(self)
...
def onGridAddCallback(rows, force=False):
if isinstance(rows, list) and len(rows) > 0:
self.grid1.AppendData(rows, force)
当更新grid1的内容时,由于共享引用,grid2的成员变量data也会同步变更,从而引发异常
推荐的解决方案:避免直接使用缺省值初始化指针类型或可变类型的成员变量(如list, dict等)
class MyFrame(wx.Frame):
def __init(self, parent, title=""):
wx.Frame.__init__(self, parent=parent, title=title)
self.grid1 = MyGrid(self, col_titles=["a", "b", "c"], data=[["1", "2", "3"]])
self.grid2 = MyGrid(self, col_titles=["a", "b", "c"], data=[["1", "2", "3"]])
...
以上关于Python3构造函数和析构函数的解析及默认参数陷阱的分享到此结束,希望能为读者提供参考,欢迎持续关注本站获取更多技术内容
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









