Python教程

022-Python对象的生命周期和周期方法

本文主要是介绍022-Python对象的生命周期和周期方法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

话不多说,上代码,看结果。


print('1、概念#############################################')
# 生命周期
# 指的是一个对象, 从诞生到消亡的过程
# 当一个对象被创建时, 会在内存中分配相应的内存空间进行存储
# 当这个对象不再使用, 为了节约内存, 就会把这个对象释放

# 涉及问题
# 如何监听一个对象的生命过程?
# Python是如何掌控一个对象的生命?

# 监听对象生命周期
# __new__方法 创建一个对象时, 用于给这个对象分配内存的方法
# 通过拦截这个方法, 可以修改对象的创建过程  比如:单例设计模式
# __init__方法
# 每个对象实例化的时候,都会自动执行这个方法 可以在这个方法里面,初始化一些实例属性
# __del__方法
# 当对象被释放的时候调用这个方法  可用于在这个方法中清理资源


class A:
    def __init__(self):
        print('init')
        self.aa = 12

    def __del__(self):
        print('del')


a = A()
print(a)
print(a.aa)
del a
print('class B#####')


class B:
    def __new__(cls, *args, **kwargs):
        print('拦截')

    def __init__(self):
        print('init')
        self.bb = 12

    def __del__(self):
        print('del')


b = B()
print(b)

# print(b.bb)  # AttributeError: 'NoneType' object has no attribute 'bb'

# 内存管理机制
# 存储方面
# 1. 在Python中万物皆对象
# 不存在基本数据类型  0,  1.2,  True, False, "abc"  这些全都是对象
# 2. 所有对象, 都会在内存中开辟一块空间进行存储
# 会根据不同的类型以及内容, 开辟不同的空间大小进行存储
# 返回该空间的地址给外界接收(称为"引用"), 用于后续对这个对象的操作 可通过id()函数获取内存地址(10进制) 通过hex()函数可以查看对应的16进制地址
# 3. 对于整数和短小的字符, Python会进行缓存; 不会创建多个相同对象  此时, 被多次赋值, 只会有多份引用
# 4. 容器对象, 存储的其他对象, 仅仅是其他对象的引用, 并不是其他对象本身
# 比如字典, 列表, 元组这些"容器对象"  全局变量是由一个大字典进行引用  global()查看

print('class C#####')


class C:
    pass


c1 = C()
c2 = C()
print(id(c1), id(c2))

c3 = 12
c4 = 12
print(id(c3), id(c4))
# 垃圾回收方面
# 引用计数器
# 概念
# 一个对象, 会记录着自身被引用的个数
# 每增加一个引用, 这个对象的引用计数会自动+1
# 每减少一个引用, 这个对象的引用计数会自动-1
# 举例
# 引用计数+1场景
# 对象被创建  p1 = Person()
# 对象被引用  p2 = p1
# 对象被作为参数,传入到一个函数中 log(p1)  这里注意会+2, 因为内部有两个属性引用着这个参数
# 对象作为一个元素,存储在容器中 l = [p1]
# 引用计数-1场景
# 对象的别名被显式销毁  del p1
# 对象的别名被赋予新的对象  p1 = 123
# 一个对象离开它的作用域  一个函数执行完毕时 内部的局部变量关联的对象, 它的引用计数就会-1
# 对象所在的容器被销毁,或从容器中删除对象
# 查看引用计数
# import sys   sys.getrefcount(对象)  注意会大一
print('class D########')
import sys


class D:
    pass


d1 = D()
d3 = D()
print(sys.getrefcount(d1))  # 1
d2 = d1
print(sys.getrefcount(d1))  # 2
del d2
print(sys.getrefcount(d1))  # 1

print('def E########')


def E(obj):
    print(sys.getrefcount(obj))  # 2


e1 = E(d1)
e2 = [d1]
print(sys.getrefcount(d1))  # 3


# 内存管理机制 = 引用计数器机制 + 垃圾回收机制
# 当一个对象, 如果被引用 + 1, 删除一个引用 : -1 0: 被自动释放
# 循环引用 容易造成内存泄漏
# objgraph
# objgraph.count() 可以查看, 垃圾回收器, 跟踪的对象个数
print('class F########')
import objgraph


class F:
    pass


class G:
    pass


f = F()
g = G()
print(objgraph.count('F'), objgraph.count('G'))
print()
f.f1 = g
g.g1 = f

del f
del g
print(objgraph.count('F'), objgraph.count('G'))


# 垃圾回收
# 主要作用  从经历过"引用计数器机制"仍未被释放的对象中, 找到"循环引用", 干掉相关对象
# 底层机制(了解&难)

# 怎样找到"循环引用"?
# 1. 收集所有的"容器对象", 通过一个双向链表进行引用
# 容器对象  可以引用其他对象的对象 列表、元组、字典、自定义类对象...
# 非容器对象  不能引用其他对象的对象  数值、字符串、布尔...  注意: 针对于这些对象的内存, 有其他的管理机制
# 2. 针对于每一个"容器对象", 通过一个变量gc_refs来记录当前对应的引用计数
# 3. 对于每个"容器对象",找到它引用的"容器对象", 并将这个"容器对象"的引用计数 -1
# 4. 经过步骤3之后, 如果一个"容器对象"的引用计数为0, 就代表这玩意可以被回收了, 肯定是"循环引用"导致它活到现在的

# 如何提升查找"循环引用"的性能?
# 如果程序当中创建了很多个对象, 而针对于每一个对象都要参与"检测"过程; 则会非常的耗费性能
# 所以, 基于这个问题, 产生了一种假设:  越命大的对象, 越长寿 假设一个对象10次检测都没给它干掉, 那认定这个对象一定很长寿, 就减少这货的"检测频率"

# 基于这种假设, 设计了一套机制
# 分代回收
# 机制
# 1. 默认一个对象被创建出来后, 属于 0 代
# 2. 如果经历过这一代"垃圾回收"后, 依然存活, 则划分到下一代
# 3. "垃圾回收"的周期顺序为  0代"垃圾回收"一定次数, 会触发 0代和1代回收
# 1代"垃圾回收"一定次数, 会触发0代, 1代和2代回收
# 查看和设置相关参数  import gc  print(gc.get_threshold())  gc.set_threshold(700, 10, 5)
# 垃圾回收器当中, 新增的对象个数-消亡的对象个数 , 达到一定的阈值时, 才会触发, 垃圾检测

# 垃圾回收时机(掌握&简单)
# 1. 自动回收
# 触发条件
# 开启垃圾回收机制
# gc.enable()  开启垃圾回收机制(默认开启)
# gc.disable()  关闭垃圾回收机制
# gc.isenabled()   判定是否开启

# 达到了垃圾回收的阈值
# 垃圾回收器中, 新增的对象个数和释放的对象个数之差到达某个阈值
# 涉及方法
# gc.get_threshold()  获取自动回收阈值
# gc.set_threshold()  设置自动回收阈值
# 2. 手动回收
# 触发条件
# gc.collect()  执行一次垃圾回收(开关状态无效)

# 特殊场景
# 场景条件  Python2.x版本下, 循环引用, 并且有一个对象都实现了__del__方法
# 概念  两个对象互相引用对象, 谁的引用计数都是1
# 导致结果  无法释放, 进行内存回收
# 底层原因  无法判别先释放哪个对象, 调用哪一个del方法
# 解决思路
# 手动解除循环引用
# 方式1
# 预防  尽可能避免循环引用产生
# 实现  一方使用弱引用代替
# 方式2
# 治疗  在循环引用的产生的前提下
# 实现  当删除一个引用, 确定以后不再使用时, 手动清空对其他容器对象的引用

# 测量对象的引用个数
# 辅助工具  objgraph

结果如下图。
在这里插入图片描述
就先这样,遇到别的再补充。

这篇关于022-Python对象的生命周期和周期方法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!