python面向对象-学习笔记(七、对象的生命周期)

发布时间 2023-11-04 16:32:28作者: 阿慢2010

概念

  • 生命周期
    • 一个对象,从诞生到消亡的过程
    • 一个对象被创建时,会在内存中分配相应的内存空间进行存储
    • 当对象不再使用,为了节约内存,就会把这个对象释放掉
  • 涉及问题
    • 如何监听一个对象的生命过程?
    • python如何掌握一个对象的生命?
  • 监听对象的生命周期
    • __new__方法
      • 当我们创建一个对象时,用于给这个对象分配内存的方法
      • 通过拦截这个方法,可以修改对象的创建过程。
        • 比如:单例设计模式
    • __init__方法
    • __del__方法
__new__方法
class Person:
    def __new__(cls, *args, **kwargs):
        print("新建了一个对象,被拦截了")

    # def __init__(self):
    #     print("初始化方法")
    #     self.name = "sz"
    #
    # def __del__(self):
    #     print("对象被释放了")
    #
    pass


p = Person()
# del p
print(p)
# print(p.name)

覆盖了__new__方法
image
未覆盖__new__方法
image

__init__和__del__方法
class Person:
    # def __new__(cls, *args, **kwargs):
    #     print("新建了一个对象,被拦截了")

    def __init__(self):
        print("初始化方法")
        self.name = "sz"

    def __del__(self):
        print("对象被释放了")

    pass


p = Person()
# 调用del会释放对象
# del p
print(p)
print(p.name)

image

小案例:统计对象的个数
# Person,打印一下,当前这个时刻,由Person类,产生的实例,有多少个
# 创建了一个实例,计数+1,删除一个实例,计数-1
# 方案1:使用全局变量personCount
# 方案2:使用类属性personCount
# 方案3:使用类隐藏属性__personCount

# personCount = 0


class Person:
    __personCount = 0

    def __init__(self):
        # global personCount
        print("计数:+1")
        Person.__personCount += 1

    def __del__(self):
        # global personCount
        print("计数:-1")
        self.__class__.__personCount -= 1

    # @staticmethod
    @classmethod
    def log(cls):
        print("当前人的个数: ", cls.__personCount)


Person.personCount = 100
# personCount = 100
p = Person()
p1 = Person()
Person.log()
del p
Person.log()

image

  • 内存管理机制
    • 存储方面
      • 1.在python中万物皆对象
        • 不存在基本数据类型,全部是对象
      • 2.所有对象都会在内存中开辟一块空间进行存储
        • 会根据不同的类型和内容,开辟不同大小的内存储存空间
        • 返回该空间的地址给外界接收("引用"),用于后续对这个对象的操作
          • 通过id()函数查看内存地址(十进制)
          • 通过hex()函数十六进制
      • 3.对于整数和短小的字符,python会进行缓存;不会创建多个相同的对象
      • 4.容器对象,存储的其他对象,仅仅是其他对象的引用,并不是其他对象本身
    • 垃圾回收方面
      • 引用计数器
        • 概念
          • 一个对象,会记录自身被引用的个数
          • 每增加一个引用,这个对象的引用计数会自动+1
          • 每减少一个引用,这个对象的引用计数会自动-1
        • 举例
          • 引用计数+1的场景
            • 对象被创建
            • 对象被引用
            • 对象被作为参数传入到函数中
            • 对象作为一个元素存储在容器中
          • 引用计数-1的场景
            • 对象的别名被显示销毁
            • 对象的别名被赋予新的对象
            • 一个对象离开它的作用域
              • 一个函数执行完毕时
              • 内部的局部变量关联的对象
            • 对象所在的容器被销毁,或从容器中删除对象
        • 查看引用计数
      • 垃圾回收
        • 主要作用
          • 从经历过“引用计数器机制”仍未被释放的对象中,找到“循环引用”,干掉相关对象
        • 底层机制(了解&难)
          • 怎样找到“循环引用”?
            • 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)
              • 垃圾回收器中,新增对象个数-消亡对象个数,达到一定的阈值,才会触发垃圾检测
        • 垃圾回收时机(掌握&简单)
          • 自动回收
            • 触发条件
              • 开启垃圾回收机制(默认开启)并且 达到垃圾回收的阈值
          • 手工回收
      • 特殊场景
    • 测量对象的引用个数
对象在内存中的储存
class Person:
    pass


p = Person()
print(p)
print(id(p))
print(hex(id(p)))

p2 = Person()

print(id(p), id(p2))

num1 = 2
num2 = 2
# 对于整数和短小的字符,python会进行缓存,不会创建多个相同的对象
print(id(num1), id(num2))

str1 = 'A'
str2 = 'A'
print(id(str1), id(str2))

image

引用计数器
# 引用技术器+1 的情况
import sys


class Person:
    pass


# 1.对象被创建
p1 = Person()
# 2.对像作为函数参数
print(sys.getrefcount(p1))

# 对像作为函数参数
# def log(obj):
#     print(sys.getrefcount(obj))
#
#
# log(p1)
#
# # dir()可以打印对象所有属性
# # getattr() 获取所有的属性的值
# for attr in dir(log):
#     print(attr, getattr(log, attr))

# 3.对象被引用
p2 = p1
print(sys.getrefcount(p2))

# del p2
# print(sys.getrefcount(p1))
# del p1
# print(sys.getrefcount(p1))

# 4.对象作为一个元素,存储在容器中
l = [p1]

print(sys.getrefcount(p1))


image

引用计数器-特殊场景-循环引用问题
# 内存管理机制:引用计数器机制,垃圾回收机制
# 当一个对象,如果被引用+1,删除一个引用:-1,0:被自动释放
# 循环引用

# objgraph.count() 可以查看,垃圾回收器,跟踪的对象个数
import objgraph


class Person:
    pass


class Dog:
    pass


p = Person()
d = Dog()

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

p.pet = d
d.master = p

del p
del d

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

image

垃圾回收机制-分代回收(自动回收)
# 1.默认一个对象被创建出来后,属于0代
# 2.如果经历过这一代“垃圾回收”后,依然存活,则划分到下一代
# 3.“垃圾回收”周期顺序为:
# ---- 0代“垃圾回收”一定次数,会触发0代和1代回收
# ---- 1代“垃圾回收”一定次数,会触发0代,1代和2代回收
# 垃圾回收当中,新增对象个数-消亡的对象个数,达到一定的阈值,才会触发,垃圾检测

# 自动回收-1.开启垃圾回收机制(默认开启);2.达到垃圾回收阈值(新增对象个数-消亡的对象个数,达到一定的阈值)

import gc

# 第一个是阈值(新增对象个数-消亡的对象个数),第二个次数(0代“垃圾回收”一定次数),第三个次数(1代“垃圾回收”一定次数)
print(gc.get_threshold())
# 可以设置垃圾回收的阈值
# gc.set_threshold(500, 5, 5)
# 判断垃圾回收机制是否开启状态
print(gc.isenabled())
# 关闭垃圾回收机制
gc.disable()
print(gc.isenabled())
# 开启垃圾回收机制
gc.enable()
print(gc.isenabled())

image

垃圾回收机制-分代回收(手动回收)
import objgraph
import gc
# 弱引用
import weakref


class Person:
    # 在python2.x中会导致无法回收“循环引用”的对象
    def __del__(self):
        print("Person对象,被释放了")

    pass


class Dog:
    def __del__(self):
        print("Dog对象,被释放了")

    pass


p = Person()
d = Dog()

# 循环引用,计数器机制无法释放;需要垃圾回收机制进行释放
# 3.多个循环引用的破除,弱字典
# p.pets = weakref.WeakKeyDictionary({"dog": d1, "cat": c1})
p.pet = d
# # 1.弱引用,导致无法形成“循环引用”
# # d.master = weakref.ref(p)
d.master = p
# 2.破除“循环引用”
# p.pet = None

del p
del d
# 垃圾回收,释放“循环引用”导致的内存
gc.collect()

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

image


学习链接: 【Python】零基础入门 面向对象编程(强烈推荐)