我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈
入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈
虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈
PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈
Oracle数据库教程:👉👉 Oracle数据库文章合集 👈👈
优 质 资 源 下 载 :👉👉 资源下载合集 👈👈
类&补充_生命周期
- 生命周期
- 概念
- 涉及问题
- 监听对象生命周期
- `__new__` 方法
- `__init__` 方法
- `__del__` 方法
- 生命周期案例
- 内存管理机制
- 存储方面
- 垃圾回收方面
- 引用计数机制
- 垃圾回收机制
- 主要作用
- 底层实现原理(了解/难)
- 垃圾回收时机(掌握&简单)
- 循环引用解决办法
生命周期
概念
- 生命周期指的是一个对象从诞生到消亡的过程
- 当一个对象被创建时,会在内存中分配相应的内存空间进行存储
- 当这个对象不在使用,为了节约内存,就会把这个对象释放
涉及问题
- 如何监听一个对象的生命过程?
- Python是如何掌控一个对象的生命?
监听对象生命周期
__new__
方法
- 当我们创建一个实例对象时,用于给这个实例对象分配内存的方法
- 通过拦截这个方法,可以修改实例对象的创建过程。例如:单例设计模式
# 对比示例 class Person1(object): pass p1 = Person1() print(p1) # 输出:<__main__.Person object at 0x000001F672F8F5E0> # __new__方法示例 class Person2(object): def __new__(cls, *args, **kwargs): print('新建一个实例对象,被我拦截了') p2 = Person2() print(p2) # 输出:新建一个实例对象,被我拦截了 None
__init__
方法
- 当实例对象被创建完成之后,会自动调用这个方法,并且将这个对象传递给
self
- 在这个方法中可以给这个创建好的对象增加一个额外的信息(如:属性)
- 通过
__new__
方法拦截实例对象创建操作后,该方法也无法执行class Person(object): def __init__(self): print('对象初始化') self.name = '失心疯' p = Person() print(p.name) # 实例对象创建被拦截后,该方法也无法执行 class Person(object): def __new__(cls, *args, **kwargs): print('新建实例对象,被拦截了') def __init__(self): print('对象初始化') self.name = '失心疯' p = Person() print(p.name) # 输出结果 # Traceback (most recent call last): # File "E:\StudyCode\Python\13-面向对象\18-生命周期.py", line 28, in <module> # print(p.name) # AttributeError: 'NoneType' object has no attribute 'name' # 新建实例对象,被拦截了
__del__
方法
- 当对象被释放时,会自动调用这方法(如:通过del方法删除对象)
class Person(object): # def __new__(cls, *args, **kwargs): # print('新建实例对象,被拦截了') def __init__(self): print('对象初始化') self.name = '失心疯' def __del__(self): print('对象被释放了') p = Person() print(p.name) del p
生命周期案例
- 要求:记录一下,当前这个时刻由Person类产生的实例对象有多少个
- 实现:创建一个实例对象,计数+1,删除一个实例对象,计数-1
personCount = 0 class Person(object): def __init__(self): global personCount personCount += 1 # print('创建实例对象 计数+1') def __del__(self): global personCount personCount -= 1 # print('释放实例对象 计数-1') p1 = Person() p2 = Person() print('当前实例个数为:', personCount) # 当前实例个数为: 2 del p1 print('当前实例个数为:', personCount) # 当前实例个数为: 1
- 这一段实现代码中有几个问题
- 1、每次输出统计数量的时候都要重复写上
print('当前实例个数为:', personCount)
这一句,代码冗余 - 2、用于计数的
personCount
变量是一个全局变量,在外部可以随意改变 - 3、在类内部使用
personCount
变量时,都需要使用global
关键字指定变量是全局变量
- 1、每次输出统计数量的时候都要重复写上
- 解决方法:
- 1、将重复性的操作封装成一个方法,每次要用的时候直接调用方法
- 2、将全局变量定义成类属性(不定义成实例属性,是因为实例属性是分别记录各个实例对象的属性)
- 示例代码优化
class Person(object): personCount = 0 def __init__(self): Person.personCount += 1 # print('创建实例对象 计数+1') def __del__(self): self.__class__.personCount -= 1 # print('释放实例对象 计数-1') @staticmethod # 静态方法 def count(): print('当前实例个数为:', Person.personCount) p1 = Person() p2 = Person() Person.count() # 当前实例个数为: 2 Person.personCount = 100 del p1 Person.count() # 当前实例个数为: 99
- 优化后的代码中依然有一些问题
- 1、这个类属性在外界通过
类名.属性
的方式依然可以对这个属性进行修改
- 1、这个类属性在外界通过
- 解决方法
- 将这个类属性改成私有属性,外界就无法对这个属性进行修改了
- 示例代码优化
class Person(object): __personCount = 0 def __init__(self): Person.__personCount += 1 # print('创建实例对象 计数+1') def __del__(self): self.__class__.__personCount -= 1 # print('释放实例对象 计数-1') @staticmethod # 静态方法 def count(): print('当前实例个数为:', Person.__personCount) p1 = Person() p2 = Person() Person.count() # 当前实例个数为: 2 del p1 Person.count() # 当前实例个数为: 1
- 在这一段代码中,依然可以进行优化
- 在这个类中的静态方法
def count()
中用到了这个类,那么我们在定义这个方法的时候,就可以通过参数来接收一个类 - 那么,就可以通过定义类方法来实现这个操作
class Person(object): __personCount = 0 def __init__(self): Person.__personCount += 1 # print('创建实例对象 计数+1') def __del__(self): self.__class__.__personCount -= 1 # print('释放实例对象 计数-1') @classmethod # 类方法 def count(cls): print('当前实例个数为:', cls.__personCount) p1 = Person() p2 = Person() Person.count() # 当前实例个数为: 2 del p1 Person.count() # 当前实例个数为: 1
内存管理机制
- 在Python中,内存管理机制是引用计数机制和垃圾回收机制两套机制并行的
- 垃圾回收机制解决循环引用的问题,但是引用计数机制效率更高
存储方面
- 1、在Python中万物皆对象
- 其他部分语言中存在基本数据类型和对象
- Python不存在基本数据类型,int、float、bool、str等都是对象
- 2、所有对象都会在内存中开辟一块空间进行存储
- 会根据不同的类型一级内容,开辟不同的空间大小进行存储
- 返回该控件的地址给外界接收(称为“引用”)没用与后续对这个对象的操作
- 通过
id(对象)
函数可以获取指定对象的内存地址(10进制) - 通过
hex(10进制内存地址)
函数可以查看对应的16进制地址
- 3、对于整数和短小的字符,Python会进行缓存处理,不会创建多个相同的对象
- 4、容器对象存储的其他对象,仅仅是存储的其他对象的引用,并不是存储的其他对象本身
垃圾回收方面
引用计数机制
-
概念
- 一个对象会记录者自身被引用的个数
- 每增加一个引用,这个对象的引用计数就会自动+1
- 每减少一个引用,这个对象的引用计数就会自动-1
- 一个对象会记录者自身被引用的个数
-
查看引用计数
- 可以通过
sys
模块中的getrefcount(对象)
方法获取到指定对象的引用数 - 调用这个函数的时候,这个对象作为参数被传递的时候就是被引用,所以统计出来的引用数会比实际的引用数大1
import sys class Person(object): pass p1 = Person() # 引用个数:1 print(sys.getrefcount(p1)) # 2,获取到的引用个数比实际的大1 p2 = p1 # 引用个数:2 print(sys.getrefcount(p1)) # 3 del p2 # 引用个数:1 print(sys.getrefcount(p1)) # 2 del p1 # p1对象被删除,后续就无法再通过p1进行查询操作 print(sys.getrefcount(p1)) # 报错:NameError: name 'p1' is not defined
- 可以通过
-
引用计数发生改变的场景举例
-
引用计数+1场景
- 对象被创建时
p1 = Person()
- 对象被引用时
p2 = p1
- 对象被作为参数传入到一个函数中时
log(p1)
,引用计数会+2- 对象作为参数传入一个函数时,在函数内部会被两个属性引用(
__globals__
、func_globals
)
- 对象作为参数传入一个函数时,在函数内部会被两个属性引用(
- 对象作为一个元素存储到容器中时
l = [p1]
- 对象被创建时
-
引用计数-1场景
- 对象的别名被现实销毁
del p1
- 对象的别名被赋予新的对象
p1 = 123
- 一个对象离开它的作用域
- 一个函数执行完毕时,内部的局部变量关联的对象,它的引用会被释放
- 对象所在的容器被销毁或者从容器中删除对象
- 对象的别名被现实销毁
-
特殊场景-循环引用问题
-
内存管理机制:当一个对象 被引用,引用计数+1,删除引用,引用计数-1;引用计数为0时,对象被自动释放
-
循环引用:
class Person(object): pass class Dog(object): pass p = Person() d = Dog() p.pet = d d.master = p del p del d
* 通过 p = Person() 实例化对象的时候,p 引用了 Person 的内存地址, Person 对象的引用计数+1,此时 Person 对象的引用计数=1 * 通过 d = Dog() 实例化对象的时候, d 引用了 Dog 的内存地址, Dog 对象的引用计数+1,此时 Dog 对象的引用计数=1 * 通过 p.pet = d 添加属性赋值, Person 对象的 pet 属性引用了 Dog 的内存地址, Dog 对象的引用计数再+1,此时 Dog 对象的引用计数=2 * 通过 d.master = p 添加属性赋值, Dog 对象的 master 属性引用了 Person 的内存地址, Person 对象的引用计数再+1,此时 Person 对象的引用计数=2 * 通过 del p 语句删除p实例对象,释放 Person 对象的一个引用, Person 对象的引用计数-1,此时 Person 对象的引用计数=1 * 通过 del d 语句删除d实例对象,释放 Dog 对象的一个引用, Dog 对象的引用计数-1,此时 Dog 对象的引用计数=1 * 此时已经没有变量引用 Person和Dog对象了,这两个对象就没办法被使用了,但是他们的引用计数依然保存着1,就不会被释放掉,就造成了内存泄漏 * 这两个对象之间属于互相引用的关系,就成了循环引用,导致两个对象的引用计数都是1,所以不能被引用计数机制释放
-
由于实例对象被删除之后,无法使用
sys.getrefcount
获取对象的引用计数,需要通过第三方库objgraph
的count
方法来查看垃圾回收器,跟踪的对象个数- 安装第三方库
objgraph
pip install objgraph
- 使用第三方库跟踪对象个数
# 没有被循环引用的情况 import objgraph class Person(object): pass class Dog(object): pass p = Person() d = Dog() print(objgraph.count('Person')) # 追踪关于Person这个类的相关对象个数:1(也就是由Person类创建的实例对象个数) print(objgraph.count('Dog')) # 追踪关于Dog这个类的相关对象个数:1(也就是由Dog类创建的实例对象个数) del p del d print(objgraph.count('Person')) # p实例对象被删除,Person类相关的对象个数:0 print(objgraph.count('Dog')) # d实例对象被删除,Dog类相关的对象个数:0 #================================================== # 被循环引用之后的情况 import objgraph class Person(object): pass class Dog(object): pass p = Person() d = Dog() print(objgraph.count('Person')) # 追踪关于Person这个类的相关对象个数:1(也就是由Person类创建的实例对象个数) print(objgraph.count('Dog')) # 追踪关于Dog这个类的相关对象个数:1(也就是由Dog类创建的实例对象个数) p.pet = d d.master = p del p del d print(objgraph.count('Person')) # p实例对象被删除,但是被Dog类的master属性引用,Person类相关的对象个数:1 print(objgraph.count('Dog')) # d实例对象被删除,但是被Person类的pet属性引用,Dog类相关的对象个数:1
- 安装第三方库
垃圾回收机制
- 垃圾回收机制:是属于引用计数机制的一个补充机制(一般对象完全可以通过引用计数机制进行释放,但是如果对象被循环引用,就无法通过引用计数机制进行释放,所以就产生了垃圾回收机制)
主要作用
- 从经历过“引用计数机制”仍未被释放的对象中,找到“循环引用”并且干掉相关对象
底层实现原理(了解/难)
-
怎样找到“循环引用”?
- 1、收集所有的“容器对象”,通过一个双向链表进行记录引用
- 容器对象:可以引用其他对象的对象(列表、元组、字典、自定义类对象等可以添加其他对象的东西)
- 非容器对象:不可以引用其他对象的对象(int、float、bool、str等不能添加其他对象的对象)<非容器对象不能引用其他对象,所以就不会出现循环引用的情况>
- 双向链表:
- 2、针对每一个“容器对象”,通过一个变量
gc_refs
来记录当前对应的引用计数 - 3、针对每个“容器对象”,找到他引用的“容器对象”,并将这个引用的“容器对象”的引用计数-1
- 4、经过步骤3之后,如果一个“容器对象”的引用计数为0,就代表这个“容器对象”可以被回收了,肯定是“循环引用”导致他活到现在的
- 1、收集所有的“容器对象”,通过一个双向链表进行记录引用
-
如何提升查找“循环引用”的性能
-
上面这种垃圾回收的检测机制需要把所有记录在双向链表中的“容器对象”进行检测一遍,是非常的耗费性能。
-
基于这个问题,Python就提出了一个假设:命越大,越长寿
- 假设一个对象经过10次检测都没有被释放掉,那就认定这个对象一定很长寿,就减少对这个对象的“检测频率”
-
基于这种假设,就设计了一套机制:分代回收
-
分代回收
- 1、默认一个对象被创建出来之后,属于0代
- 2、如果经历过这一代“垃圾回收”之后,依然没有被释放,则划分到下一代
- 3、“垃圾回收”的周期
- 0代“垃圾回收”一定次数,会触发0代和1代回收
- 1代“垃圾回收”一定次数,会触发0代、1代和2代回收
-
垃圾回收器当中,新增的对象个数-消亡的对象个数,达到一定的阈值时,才会触发垃圾检测
-
个人理解:当出现循环引用对象(无法被释放的对象)个数达到一定阈值时候才会触发垃圾检测
-
查看和设置相关参数
-
语法
import gc gc.get_threshold() # 查看触发机制的阈值 # 输出 (700, 10, 10) # 700 触发垃圾检测的阈值 # 10 触发0代和1代回收的阈值 # 10 触发0代、1代和2代回收的阈值 gc.set_threshold(触发垃圾检测的阈值,触发0代和1代回收的阈值,触发0代、1代和2代的阈值) # 设置触发阈值
-
示例代码
import gc print(gc.get_threshold()) # 输出:(700, 10, 10) # 700 垃圾回收器中, 新增对象个数 - 消亡对象个数 = 700,触发垃圾检测 # 10 每经过10次【0代垃圾回收】检测,就触发一次【0代和1代回收】(前面10次仅执行0代回收) # 10 每经过10次【0代和1代垃圾回收】检测,就触发一次【0代、1代和2代回收】 # 10次【0代回收】执行1次【0代和1代回收】,10次【0代和1代回收】执行1次【0代、1代和2代回收】 gc.set_threshold(1000, 50, 20) # 1000 设置垃圾回收器中, 新增对象个数 - 消亡对象个数 = 1000时,才触发垃圾检测 # 50 设置每经过50次【0代垃圾回收】检测,就触发一次【0代和1代回收】 # 20 设置每经过20次【0代和1代垃圾回收】检测,就触发一次【0代、1代和2代回收】 print(gc.get_threshold()) # 输出:(1000, 50, 20)
垃圾回收时机(掌握&简单)
- 自动回收
- 触发条件:开启垃圾回收机制并且达到了垃圾回收的阈值
- 阈值:垃圾回收器中,新增的对象个数 与 释放的对象个数 之差
- 垃圾回收机制开启和关闭设置
import gc gc.enable() # 开启垃圾回收机制(默认开启) gc.disable() # 关闭垃圾回收机制 gc.isenabled() # 判定垃圾回收机制是否开启状态
- 垃圾回收阈值获取和设置
import gc gc.get_threshold() # 获取自动回收阈值 gc.set)threshold() # 设置自动回收阈值
- 手动回收
- 语法
import gc gc.collect(garbage=None) # 运行垃圾收集器 # garbage:可以是一个整数,指定要收集哪一代,默认运行完整收集(0代、1代、2代) # 无论垃圾回收机制是否开启,都可以运行
- 示例(循环引用问题解决)
# 默认情况,出现循环引用后无法被引用计数机制释放,未达到阈值的情况下也不会被垃圾回收机制释放 import objgraph class Person(object): pass class Dog(object): pass p = Person() d = Dog() p.pet = d d.master = p del p del d print(objgraph.count('Person')) # 输出:1 print(objgraph.count('Dog')) # 输出:1 #=============通过手动开启垃圾回收机制进行释放========================= import objgraph import gc class Person(object): pass class Dog(object): pass p = Person() d = Dog() p.pet = d d.master = p del p del d gc.collect() # 手动开启垃圾回收机制 print(objgraph.count('Person')) # 输出:0 print(objgraph.count('Dog')) # 输出:0
循环引用解决办法
- 可到达引用:可以直接访问真实对象的引用(p 和 d)
- 不可到达引用:需要通过可到达引用间接的访问真实对象(pet 和 master)
- 不能直接通过pet和master访问真实对象,必须通过p.pet和d.master间接访问,删除p和d之后,就无法通过pet和master访问到真实对象了
- 强引用:会影响对象的引用计数的引用
- 弱引用:与强引用相对,弱引用并不会影响对象的引用计数,也就是说其不影响对象是否被回收的判定(需要使用weakref模块)
- 基础类型int、list、dict、tuple、str不支持弱引用,对其执行弱引用会报错
- 可以通过
__weakrefoffset__
查看类型是否支持弱引用,该变量表示弱引用指针相对对象起始地址的偏移量,>0表示支持弱引用 - weakref模块
- 方法
weakref.ref(obj) # 给指定对象创建一个弱引用 weakref.WeakKeyDictionary(dict) # 给指定字典的key创建一个弱引用 weakref.WeakValueDictionary(dict) # 给指定字典的value创建一个弱引用
- 通过使用弱引用解决循环引用不能自动释放问题
import objgraph import weakref class Person(object): pass class Dog(object): pass p = Person() d = Dog() p.pet = d d.master = weakref.ref(p) # 通过weakref.ref进行弱引用 del p del d print(objgraph.count('Person')) # 输出:0 print(objgraph.count('Dog')) # 输出:0
- 当一个对象引用多个对象时,可以使用字典的方式
import objgraph import weakref class Person(object): pass class Dog(object): pass class Cat(object): pass p = Person() d = Dog() c = Cat() # p.pet = {'Dog': d, 'Cat': c} p.pet = weakref.WeakValueDictionary({'Dog': d, 'Cat': c}) d.master = p del p del d del c print(objgraph.count('Person')) # 输出:0 print(objgraph.count('Dog')) # 输出:0 print(objgraph.count('Cat')) # 输出:0
- 在释放对象之前打断循环引用
import objgraph class Person(object): pass class Dog(object): pass p = Person() d = Dog() p.pet = d d.master = p p.pet = None del p del d print(objgraph.count('Person')) # 输出:0 print(objgraph.count('Dog')) # 输出:0