百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

python散装笔记——111: 垃圾回收

itomcoil 2025-03-25 14:27 7 浏览

1: 原始对象的重用

一个有助于优化应用程序的有趣现象是,原始类型实际上在底层也是通过引用计数来管理的。我们来看看数字:对于所有介于 -5 和 256 之间的整数,Python 总是重用同一个对象:

>>> import sys
>>> sys.getrefcount(1)
1262
>>> a = 1
>>> b = 1
>>> sys.getrefcount(1)
1433

请注意引用计数增加了,这意味着当 ab 指向 1 这个原始类型时,它们引用的是同一个底层对象。然而,对于更大的数字,Python 实际上并不会重用底层对象:

>>> a = 999999999
>>> sys.getrefcount(999999999)
2
>>> b = 999999999
>>> sys.getrefcount(999999999)
2

由于将 999999999 分配给 ab 时引用计数没有变化,我们可以推断它们指向两个不同的底层对象,尽管它们都被分配了相同的原始类型。

2: del命令的效果

使用 del v 从作用域中移除变量名,或者使用 del v[item]del[i:j] 从集合中移除对象,或者使用 del v.name 移除属性,或者其他任何移除对对象引用的方式,并不会直接触发任何析构函数调用或释放任何内存。只有当对象的引用计数达到零时,对象才会被析构。

>>> import gc
>>> gc.disable() # 禁用垃圾回收器
>>> class Track:
    def __init__(self):
        print("Initialized")

    def __del__(self):
        print("Destructed")
        
>>> def bar():
    return Track()
  
>>> t = bar()
Initialized
>>> another_t = t # 分配另一个引用
>>> print("...")
...
>>> del t # 尚未析构——`another_t` 仍然引用它
>>> del another_t # 最后一个引用被移除,对象被析构
Destructed

3: 引用计数

Python 的大部分内存管理是通过引用计数来处理的。

每当一个对象被引用(例如被分配给一个变量)时,它的引用计数会自动增加。当它被取消引用(例如变量超出作用域)时,它的引用计数会自动减少。

当引用计数达到零时,对象会立即被销毁,内存也会立即被释放。因此,在大多数情况下,垃圾回收器甚至都不是必需的。

>>> import gc
>>> gc.disable() # 禁用垃圾回收器
>>> class Track:
    def __init__(self):
        print("Initialized")
    def __del__(self):
        print("Destructed")

>>> def foo():
    Track()
    # 由于不再有任何引用,立即被析构
    print("---")
    t = Track()
    # 变量被引用,因此尚未被析构
    print("---")
    # 当函数退出时,变量被析构
>>> foo()
Initialized
Destructed
---
Initialized
---
Destructed

为了进一步说明引用的概念:

>>> def bar():
  return Track()
>>> t = bar()
Initialized
>>> another_t = t # 分配另一个引用
>>> print("...")
...
>>> t = None # 尚未析构——`another_t` 仍然引用它
>>> another_t = None # 最后一个引用被移除,对象被析构
Destructed

4: 处理引用循环的垃圾回收器

垃圾回收器只有在存在引用循环时才是必需的。最简单的引用循环例子是 A 指向 B,而 B 指向 A,同时没有任何其他对象指向 A 或 B。A 和 B 都无法从程序的任何地方访问,因此可以安全地被析构,但由于它们的引用计数为 1,因此仅靠引用计数算法无法释放它们。

>>> import gc
>>> gc.disable() # 禁用垃圾回收器
>>> class Track:
    def __init__(self):
        print("Initialized")
    def __del__(self):
        print("Destructed")

>>> A = Track()
Initialized
>>> B = Track()
Initialized
>>> A.other = B
>>> B.other = A
>>> del A; del B # 由于引用循环,对象尚未被析构
>>> gc.collect() # 触发回收
Destructed
Destructed
20

引用循环可以任意长。如果 A 指向 B 指向 C 指向……指向 Z,而 Z 又指向 A,那么从 A 到 Z 都不会被回收,直到垃圾回收阶段:

>>> objs = [Track() for _ in range(10)]
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
Initialized
>>> for i in range(len(objs)-1):
... objs[i].other = objs[i + 1]
...
>>> objs[-1].other = objs[0] # 完成循环
>>> del objs # 现在没有任何对象引用 `objs`——但仍未被析构
>>> gc.collect()
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
Destructed
20

5: 强制释放对象

在 Python 2 和 3 中,即使对象的引用计数不为 0,你也可以强制释放对象。

两个版本都使用 ctypes 模块来实现。

警告:这样做会使你的 Python 环境变得不稳定,容易在没有堆栈跟踪的情况下崩溃!使用这种方法还可能引入安全问题(虽然不太可能)。只有当你确定你永远不会再引用某个对象时,才应该释放它。

>>> import ctypes
>>> deallocated = 12345
>>> ctypes.pythonapi._Py_Dealloc(ctypes.py_object(deallocated))
1440518144

运行后,对刚刚释放的对象的任何引用都将导致 Python 产生未定义行为或崩溃——而且不会显示堆栈跟踪。垃圾回收器没有移除那个对象,可能有其原因……

如果你释放了 None,你会收到一条特殊的消息——在崩溃之前显示“致命的 Python 错误:释放 None”。

111.6 节:查看对象的引用计数

>>> import sys
>>> a = object()
>>> sys.getrefcount(a)
2
>>> b = a
>>> sys.getrefcount(a)
3
>>> del b
>>> sys.getrefcount(a)
2

111.7: 不要等待垃圾回收来清理

垃圾回收会清理内存这一事实并不意味着你应该等待垃圾回收周期来清理。

特别是你不应该等待垃圾回收来关闭文件句柄、数据库连接和打开的网络连接。

例如:

在下面的代码中,你假设文件将在下一个垃圾回收周期中被关闭,如果 f 是对文件的最后一个引用。

>>> f = open("test.txt")
>>> del f

一种更明确的清理方式是调用 f.close()。你可以更优雅地实现这一点,即使用 with 语句,也称为上下文管理器:

>>> with open("test.txt") as f:
... pass
... # 对 `f` 进行一些操作
>>> # 现在 `f` 对象仍然存在,但它已经被关闭了

with 语句允许你将代码缩进到打开的文件下。这使得代码更明确,更容易看出文件保持打开的时间有多长。即使在 with 块中引发异常,它也会始终关闭文件。

8: 管理垃圾回收

有两种方法可以影响何时执行内存清理。一种是影响自动进程的执行频率,另一种是手动触发清理。

可以通过调整回收阈值来操纵垃圾回收器,这些阈值会影响回收器运行的频率。Python 使用基于代的内存管理系统。新对象被保存在最新的代中——generation0,并且每次在回收中存活下来后,对象都会被提升到更老的代中。

达到最后一代——generation2 后,它们不再被提升。

可以使用以下代码片段更改阈值:

import gc
gc.set_threshold(1000, 100, 10) # 这些值仅用于演示目的

第一个参数表示收集 generation0 的阈值。每当分配的内存数量超过释放的内存数量 1000 时,垃圾回收器就会被调用。

为了避免每次运行时都清理较老的代以优化进程,清理较老代的频率可以通过第二和第三个参数控制,这两个参数是可选的。如果在没有清理 generation1 的情况下处理了 generation0 100 次,则会处理 generation1。同样,只有在没有清理 generation2 的情况下清理了 generation1 10 次时,才会处理 generation2 中的对象。

在程序分配了大量小对象且没有释放它们时,手动设置阈值是有益的,这会导致垃圾回收器运行得太频繁(每次分配 generation0_threshold 个对象时)。尽管回收器本身速度很快,但当它处理大量对象时,可能会出现性能问题。不过,没有一种通用的策略可以用于选择阈值,这取决于具体用例。

可以使用以下代码片段手动触发回收:

import gc
gc.collect()

垃圾回收是根据分配和释放的次数自动触发的,而不是根据消耗或可用内存量。因此,在处理大对象时,可能会在自动清理被触发之前耗尽内存。这为手动调用垃圾回收器提供了一个很好的用例。

尽管这是可能的,但这并不是一个被鼓励的做法。避免内存泄漏才是最佳选择。然而,在大型项目中,检测内存泄漏可能是一项艰巨的任务,手动触发垃圾回收可以作为一种快速解决方案,直到进一步调试。

对于长期运行的程序,可以根据时间或事件触发垃圾回收。第一个例子是,一个 web 服务器在固定数量的请求后触发回收。对于后者,一个 web 服务器在接收到某种类型的请求时触发垃圾回收。

相关推荐

Python 最常用的语句、函数有哪些?

1.#coding=utf-8①代码中有中文字符,最好在代码前面加#coding=utf-8②pycharm不加可能不会报错,但是代码最终是会放到服务器上,放到服务器上的时候运行可能会报错。③...

PyYAML 实用的使用技巧

作者:Reorx’sForge中文版:https://reorx.com/blog/python-yaml-tips-zh英文版:Tipsthatmaysaveyoufromthehe...

学习编程第127天 python中字符串与数值中的巧妙应用

今天学习的刘金玉老师零基础Python教程第10期,主要内容是python中字符串与数值中的巧妙应用。一、新建一个工程如图,新建一个工程。二、字符串与数值的区别变量只有为数值的时候,才能进行数学运算。...

Python 必学!12 个 “开挂级” 内置函数深度解析(小白也能秒懂)

干货来了以下是Python中12个强大内置函数的深度解析,涵盖数据处理、代码优化和高级场景,助你写出更简洁高效的代码:一、数据处理三剑客1.map(function,iterable)作用:...

Python浮点数保留两位小数的方法

技术背景在Python编程中,经常会遇到需要将浮点数保留特定小数位数的情况,比如在处理货币、统计数据等场景。然而,由于浮点数在计算机中采用二进制表示,存在精度问题,导致直接使用round函数有时无法得...

DAY4-step5 Python示例说明 round()函数

Round()Round()是python提供的内置函数。它将返回一个浮点数,该浮点数将四舍五入到指定的精度。如果未指定要舍入的小数位,则将其视为0,并将舍入到最接近的整数。语法:round(flo...

第五个测试版本了,iOS 9 又有了什么变化?

今天的早些时候苹果发布了iOS9beta5,除去修复BUG和提升系统的稳定性外,苹果还带来了一些新功能。本次更新包括了对Carplay,WiFi以及Siri等功能的优化,Mac...

如何在 Python 中随机排列列表元素

在本教程中,我们将学习在Python中如何打乱列表元素顺序,随机排列列表元素。如何随机排列列表是一项非常有用的技能。它在开发需要选择随机结果游戏中非常有用。它还适用于数据相关的工作中,可能需要提取...

Python获取随机数方法汇总

1.random.random()作用:随机生成一个[0,1)之间的浮点数importrandomprint(f'随机生成一个[0,1)之间的浮点数={random.random()}&...

Python程序开发之简单小程序实例(11)小游戏-跳动的小球

Python程序开发之简单小程序实例(11)小游戏-跳动的小球一、项目功能用户控制挡板来阻挡跳动的小球。二、项目分析根据项目功能自定义两个类,一个用于控制小球在窗体中的运动,一个用于接收用户按下左右键...

Keras人工智能神经网络 Regressor 回归 神经网络搭建

前期分享了使用tensorflow来进行神经网络的回归,tensorflow构建神经网络本期我们来使用Keras来搭建一个简单的神经网络Keras神经网络可以用来模拟回归问题(regression)...

我让DeepSeek写程序,有懂的看看写的对不对?

他写的时候就像教学生解方程一样,解释一段写一段,因为中间太长,我就截了最后的结果,应该是手机版本复制字数有限,可能也没复制全,有没有懂的看看写的对不对?下面是他写的最后结果。importrandom...

Picker Wheel 线上随机抽签轮盘

#头条创作挑战赛#办公室经常会碰到「中午要吃什么?」、「要订哪家饮料店?」或「谁要去帮大家跑腿?」等各种情境,为了公平起见,我们可以使用随机方式进行抽签,这样一来就能确保公平公正性,其他人也就不...

思维链COT原理探究

TEXTANDPATTERNS:FOREFFECTIVECHAINOFTHOUGHTITTAKESTWOTOTANGO测试模型:PaLM-62B,GPT3,CODEXCOT元素...

永别了iPod!系列产品回顾,你用过几款?

中关村在线消息:就在本周,苹果官方宣布iPod系列将不再生产新品,现货售完即止。作为一个偏向音乐播放的系列,iPod系列想必陪伴了很多朋友的学生年代。近日有外媒总结了iPod系列的全部产品,来看看你用...