目 录CONTENT

文章目录

关于 Python 如何管理内存你需要知道的一切

Administrator
2026-01-22 / 0 评论 / 0 点赞 / 2 阅读 / 0 字

📢 转载信息

原文链接:https://machinelearningmastery.com/everything-you-need-to-know-about-how-python-manages-memory/

原文作者:Bala Priya C


在本文中,您将了解 Python 如何使用引用计数和分代垃圾回收来分配、跟踪和回收内存,以及如何使用 gc 模块来检查这种行为。

我们将涵盖的主题包括:

  • 引用的作用以及在常见场景中 Python 引用计数如何变化。
  • 纯引用计数下循环引用为何会导致内存泄漏,以及循环引用是如何被回收的。
  • gc 模块的实际用法,用于观察阈值、计数和回收。

让我们直接进入主题。

everything need know python manages memory

如何管理 Python 内存你需要知道的一切
图片来源:Editor

 

介绍

在 C 等语言中,您需要手动分配和释放内存。忘记释放内存就会导致泄漏。释放两次则会导致程序崩溃。Python 通过自动垃圾回收为您处理了这种复杂性。您创建对象,使用它们,当它们不再需要时,Python 会清理它们。

但“自动”并不意味着“魔法”。了解 Python 的垃圾收集器如何工作,可以帮助您编写更高效的代码、调试内存泄漏并优化性能关键型应用程序。在本文中,我们将探讨引用计数、分代垃圾回收以及如何使用 Python 的 gc 模块。以下是您将学到的内容:

  • 什么是引用,以及引用计数在 Python 中如何工作
  • 什么是循环引用以及为什么它们有问题
  • Python 的分代垃圾回收
  • 使用 gc 模块检查和控制回收

让我们开始吧。

🔗 您可以在 GitHub 上找到代码

Python 中的引用是什么?

在讨论垃圾回收之前,我们需要了解什么是“引用”。

当您编写以下代码时:

1
x = 123

实际发生的情况如下:

  1. Python 在内存中的某个位置创建了一个整数对象 123
  2. 变量 x 存储了该对象内存位置的指针
  3. x 并不“包含”整数值——它指向它

因此,在 Python 中,变量是标签,而不是盒子。变量不保存值;它们是指向内存中对象的名称。将对象想象成漂浮在内存中的气球,将变量想象成系在这些气球上的绳子。多根绳子可以系在同一个气球上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Create an object
my_list = [1, 2, 3]  # my_list points to a list object in memory
 
# Create another reference to the SAME object
another_name = my_list  # another_name points to the same list
 
# They both point to the same object
print(my_list is another_name)
print(id(my_list) == id(another_name))
 
# Modifying through one affects the other (same object!)
my_list.append(4)
print(another_name)
 
# But reassigning creates a NEW reference
my_list = [5, 6, 7]  # my_list now points to a DIFFERENT object
print(another_name)

当您编写 another_name = my_list 时,您不是在复制列表。您是在为同一个对象创建另一个指针。两个变量都引用(指向)内存中的同一个列表。这就是为什么通过一个变量所做的更改会出现在另一个变量中的原因。因此,上述代码将产生以下输出:

1
2
3
4
True
True
[1, 2, 3, 4]
[1, 2, 3, 4]

id() 函数显示对象的内存地址。当两个变量具有相同的 id() 时,它们引用的是同一个对象。

好的,但什么是“循环”引用?

当对象相互引用形成一个周期时,就会发生循环引用。这里有一个非常简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class Person:
    def __init__(self, name):
        self.name = name
        self.friend = None  # Will store a reference to another Person
 
# Create two people
alice = Person("Alice")
bob = Person("Bob")
 
# Make them friends - this creates a circular reference
alice.friend = bob  # Alice's object points to Bob's object
bob.friend = alice  # Bob's object points to Alice's object

现在我们有了一个循环:alice → Person(“Alice”) → .friend → Person(“Bob”) → .friend → Person(“Alice”) → …

这就是它被称为“循环”的原因(以防您还没有猜到)。如果您遵循引用,您就会进入一个循环:Alice 的对象引用 Bob 的对象,Bob 的对象引用 Alice 的对象,依次类推……永远循环。这是一个循环。

Python 如何使用引用计数和分代垃圾回收来管理内存

Python 使用两种主要机制进行垃圾回收:

  1. 引用计数:这是主要方法。当对象的引用计数达到零时,该对象就会被删除。
  2. 分代垃圾回收:一个备份系统,用于查找和清理引用计数无法处理的循环引用。

让我们详细探索这两者。

引用计数的工作原理

每个 Python 对象都有一个引用计数,即指向它的引用数量,意味着指向它的变量(或其他对象)。当引用计数达到零时,内存会立即释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
 
# Create an object - reference count is 1
my_list = [1, 2, 3]
print(f"Reference count: {sys.getrefcount(my_list)}")
 
# Create another reference - count increases
another_ref = my_list
print(f"Reference count: {sys.getrefcount(my_list)}")
 
# Delete one reference - count decreases
del another_ref
print(f"Reference count: {sys.getrefcount(my_list)}")
 
# Delete the last reference - object is destroyed
del my_list

输出:

1
2
3
Reference count: 2
Reference count: 3
Reference count: 2

引用计数的工作方式如下。Python 会跟踪每个对象上的计数器,记录有多少引用指向它。每次您:

  • 将对象分配给变量 → 计数增加
  • 将其传递给函数 → 计数暂时增加
  • 将其存储在容器中 → 计数增加
  • 删除引用 → 计数减少

当计数达到零(没有剩余引用)时,Python 会立即释放内存。

📑 关于 sys.getrefcount()sys.getrefcount() 显示的计数总是比您预期的多 1,因为将对象传递给函数会创建一个临时引用。如果您看到“2”,则实际上只有一个外部引用。

示例:引用计数实战

让我们用一个自定义类来演示引用计数,该类在被删除时会发出通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class DataObject:
    """Object that announces when it's created and destroyed"""
    
    def __init__(self, name):
        self.name = name
        print(f"Created {self.name}")
    
    def __del__(self):
        """Called when object is about to be destroyed"""
        print(f"Deleting {self.name}")
 
# Create and immediately lose reference
print("Creating object 1:")
obj1 = DataObject("Object 1")
 
print("\nCreating object 2 and deleting it:")
obj2 = DataObject("Object 2")
del obj2
 
print("\nReassigning obj1:")
obj1 = DataObject("Object 3")
 
print("\nFunction scope test:")
def create_temporary():
    temp = DataObject("Temporary")
    print("Inside function")
 
create_temporary()
print("After function")
 
print("\nScript ending...")

在这里,当对象的引用计数达到零时,__del__ 方法(析构函数)就会被调用。通过引用计数,这会立即发生。

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Creating object 1:
Created Object 1
 
Creating object 2 and deleting it:
Created Object 2
Deleting Object 2
 
Reassigning obj1:
Created Object 3
Deleting Object 1
 
Function scope test:
Created Temporary
Inside function
Deleting Temporary
After function
 
Script ending...
Deleting Object 3

请注意,Temporary 对象在函数退出时被删除,因为局部变量 temp 超出了作用域。当 temp 消失时,对象不再有引用,因此它被立即释放。

Python 如何处理循环引用

如果您仔细观察,就会发现引用计数无法处理循环引用。让我们看看原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import gc
import sys
 
class Node:
    def __init__(self, name):
        self.name = name
        self.reference = None
    
    def __del__(self):
        print(f"Deleting {self.name}")
 
# Create two separate objects
print("Creating two nodes:")
node1 = Node("Node 1")
node2 = Node("Node 2")
 
# Now create the circular reference
print("\nCreating circular reference:")
node1.reference = node2
node2.reference = node1
 
print(f"Node 1 refcount: {sys.getrefcount(node1) - 1}")
print(f"Node 2 refcount: {sys.getrefcount(node2) - 1}")
 
# Delete our variables
print("\nDeleting our variables:")
del node1
del node2
 
print("Objects still alive! (reference counts aren't zero)")
print("They only reference each other, but counts are still 1 each")

当您尝试删除这些对象时,仅靠引用计数无法清理它们,因为它们会互相保持“存活”。即使没有外部变量引用它们,它们仍然互相引用。因此,它们的引用计数永远不会达到零

输出:

1
2
3
4
5
6
7
8
9
Creating two nodes:
 
Creating circular reference:
Node 1 refcount: 2
Node 2 refcount: 2
 
Deleting our variables:
Objects still alive! (reference counts aren't zero)
They only reference each other, but counts are still 1 each

以下是引用计数在此处无效的详细分析:

  • 当我们删除 node1node2 变量后,对象仍然存在于内存中
  • Node 1 的对象有一个引用(来自 Node 2 的 .reference 属性)
  • Node 2 的对象有一个引用(来自 Node 1 的 .reference 属性)
  • 每个对象的引用计数都是 1(不是 0),因此它们不会被释放
  • 但现在没有任何代码可以访问这些对象了!它们是垃圾,但引用计数无法检测到它们。

这就是为什么 Python 需要第二种垃圾收集机制来查找和清理这些循环。以下是如何手动触发垃圾回收以查找循环并像这样删除对象的方法:

1
2
3
4
5
6
7
8
9
# Manually run garbage collection to find the cycle
gc.collect()
print("\nAfter manual collection:")
print(f"Node 1 refcount: {sys.getrefcount(node1) - 1}")
print(f"Node 2 refcount: {sys.getrefcount(node2) - 1}")



🚀 想要体验更好更全面的AI调用?

欢迎使用青云聚合API,约为官网价格的十分之一,支持300+全球最新模型,以及全球各种生图生视频模型,无需翻墙高速稳定,文档丰富,小白也可以简单操作。

0

评论区