python---构造函数、析构函数
文章目录
- 构造函数
- 构造函数的作用
- 基本语法和示例
- 一个简单的 Person 类
- 带默认参数的构造函数
- 与 __new__ 方法的区别
- 析构函数
- 析构函数的定义
- 3. 析构函数何时被调用?
- 重要注意事项和陷阱
- a. 调用时机的不确定性
- b. 异常处理
- c. 模块全局变量
- d. 与 try...finally 和 with 语句的对比
构造函数
在 Python 中,构造函数指的是一个特殊的实例方法,名为 :
__init__ #(注意,前后都是两个下划线)。
构造函数的作用
1、初始化新创建的对象:当一个类的实例被创建后(即内存空间被分配之后),init 方法会立即被自动调用。
2、为对象的属性设置初始值:这是它最主要的工作。你可以通过参数将初始值传递给 init 方法,然后将其赋给对象的属性(即 self.attribute)。
基本语法和示例
class ClassName:def __init__(self, param1, param2, ...):# 初始化代码self.attribute1 = param1self.attribute2 = param2# ... 其他初始化操作
self:这是第一个且必不可少的参数。它代表当前对象的实例(也就是新创建的那个对象本身)。通过 self,你可以在类内部访问该实例的属性和方法。在调用方法时,你不需要手动传递这个参数,Python 会自动处理。
其他参数:你可以根据需要定义任意数量的其他参数。在创建对象时,必须提供这些参数(除非它们有默认值)。
一个简单的 Person 类
class Person:# 构造函数,接受 name 和 age 两个参数def __init__(self, name, age):print("一个Person对象被创建了!")self.name = name # 将参数name的值赋给实例的name属性self.age = age # 将参数age的值赋给实例的age属性def introduce(self):print(f"大家好,我叫{self.name},今年{self.age}岁。")# 创建对象实例
# 这会自动调用 __init__ 方法,并将 "Alice" 和 30 传递给它
person1 = Person("Alice", 30)# 访问对象的属性
print(person1.name) # 输出: Alice
print(person1.age) # 输出: 30# 调用对象的方法
person1.introduce() # 输出: 大家好,我叫Alice,今年30岁。
带默认参数的构造函数
参数设置默认值,这使得它们在创建对象时成为可选的。
使用默认参数:
class Person:# age 参数有一个默认值 18def __init__(self, name, age=18):self.name = nameself.age = age# 不提供age,使用默认值
person_young = Person("Bob")
print(person_young.name, person_young.age) # 输出: Bob 18# 提供age,覆盖默认值
person_old = Person("Charlie", 65)
print(person_old.name, person_old.age) # 输出: Charlie 65
与 new 方法的区别
需要特别注意的是,init 并不是真正意义上的“构造”函数。在 Python 中,真正负责创建(分配内存)对象的是另一个特殊方法 new。
new:这是一个类方法(尽管不用 @classmethod 装饰器),它负责创建并返回一个新的对象实例。它是真正意义上的“构造函数”。
init:它是在 new 完成创建对象之后被调用的,负责初始化这个新创建的对象(给对象的属性赋值)。
在绝大多数情况下,你不需要重写 new 方法,使用默认的即可。init 才是你用来初始化对象的地方。
执行顺序: new -> init
析构函数
析构函数(Destructor)是面向对象编程中的一个特殊方法,它的作用与构造函数相反。当一个对象被销毁(例如,其生命周期结束,被从内存中释放)时,析构函数会被自动调用,用于执行一些清理工作,
例如:
1、关闭该对象打开的文件
2、断开网络连接
3、释放非托管资源(如通过 ctypes 调用的 C 库分配的内存)
4、保存最终状态等
在 Python 中,析构函数的方法名是固定的:
__del__
析构函数的定义
在类中定义一个名为 del 的方法即可。这个方法不需要任何参数(除了必须的 self),并且没有返回值。
语法:
class MyClass:def __init__(self, name):# 构造函数,初始化对象self.name = nameprint(f"对象 {self.name} 被创建了")def __del__(self):# 析构函数,对象销毁时调用print(f"对象 {self.name} 即将被销毁")
3. 析构函数何时被调用?
析构函数的调用是由 Python 的垃圾回收机制(Garbage Collection, GC) 触发的。具体来说,在以下情况下 del 可能会被调用:
1、引用计数降为 0:这是 CPython 解释器主要的垃圾回收机制。当一个对象没有任何变量引用它时,它的引用计数会变为 0,解释器会立即回收它并调用 del。
def create_obj():obj = MyClass(“局部对象") # 对象在函数内创建# 函数结束时,局部变量 obj 失效,引用计数降为 0create_obj() # 函数调用结束后,会输出“对象 局部对象 即将被销毁”
2、使用 del 语句:del 语句会删除一个变量名(即减少对象的引用计数),如果引用计数因此变为 0,则会触发析构。
obj = MyClass(“测试对象")
del obj # 立即输出“对象 测试对象 即将被销毁”
3、程序正常退出时:程序运行结束后,所有对象都会被销毁,它们的 del 方法也会被调用。
重要注意事项和陷阱
a. 调用时机的不确定性
虽然引用计数为 0 时会立即调用 del,但 Python 还有一种更高级的垃圾回收器来处理循环引用。
循环引用:两个或多个对象相互引用,导致它们的引用计数永远不为 0。
class A:def __init__(self):self.b = Noneclass B:def __init__(self):self.a = Nonea = A()
b = B()
a.b = b # a 引用 b
b.a = a # b 引用 a,形成循环引用del a
del b # 即使删除了变量 a 和 b,对象 A 和 B 的引用计数仍为 1(它们相互引用)
对于这种循环引用,引用计数机制无法回收它们。此时,分代垃圾回收器(Generational GC) 会间歇性地运行,检测并清理这些循环引用。这意味着 del 的调用时机变得不确定,你可能无法预测它何时会被执行,甚至可能根本不会被执行(比如解释器异常退出时)。
b. 异常处理
在 del 方法执行过程中发生的异常不会被捕获,它们会被输出到 sys.stderr,但不会中断程序的执行(如果程序还在运行的话)。
c. 模块全局变量
对于模块级别的全局变量,它们的 del 方法可能在解释器关闭时才被调用。此时,一些该对象所依赖的其他模块或全局变量可能已经被清理或设置为 None(例如 sys.stdout)。如果在 del 中尝试使用这些已被清理的资源,可能会导致异常。
不推荐写法:
class BadExample:def __del__(self):# 在解释器退出时,sys.stdout 可能已经不可用了print("Destructor called")
d. 与 try…finally 和 with 语句的对比
非常重要:del 不应该用于管理关键资源(如文件、锁、网络连接)的清理!
Python 提供了更可靠、更可预测的上下文管理器(with 语句)和 try…finally 块来确保资源被正确释放。
使用 with 语句(推荐):
with open('file.txt', 'r') as f:data = f.read()
# 离开 with 块后,文件 f 会自动、立即地被关闭,无需等待垃圾回收
依赖 del(不推荐):
class FileHandler:def __init__(self, filename):self.file = open(filename, 'r')def __del__(self):self.file.close() # 不可靠!文件可能很久以后才关闭,或者根本不关闭。# 使用这个类
handler = FileHandler('file.txt')
data = handler.file.read()
# 文件关闭的时机不确定