Python3 构造函数与析构函数详解及默认参数陷阱

落日邮差
落日邮差
发布时间:2026-10-13 | 4人浏览过
原创

本文深入解析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构造函数和析构函数的解析及默认参数陷阱的分享到此结束,希望能为读者提供参考,欢迎持续关注本站获取更多技术内容

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表