Python 核心概念解读

本文深入解释了 Python 中的生成器与迭代器、内存管理、引用计数、循环引用与 gc 机制等核心概念。

这是一个非常深入且重要的 Python 核心概念组合。我们逐一详细解释:


生成器(Generator)与迭代器(Iterator)

迭代器(Iterator)

定义:迭代器是实现了 __iter__()__next__() 方法的对象,用于逐个访问容器中的元素。

  • __iter__():返回迭代器对象本身。
  • __next__():返回下一个元素,如果没有更多元素,则抛出 StopIteration 异常。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        value = self.data[self.index]
        self.index += 1
        return value

it = MyIterator([1, 2, 3])
for x in it:
    print(x)  # 输出 1, 2, 3

Python 中很多内置类型(如 list、dict、set)都支持迭代,因为它们实现了 __iter__(),返回一个迭代器对象。


生成器(Generator)

定义:生成器是一种特殊的迭代器,使用 yield 关键字定义。它在每次调用 next() 时“暂停”并“恢复”函数状态,而不是一次性返回所有值。

优点

  • 节省内存:按需生成值,不一次性加载所有数据。
  • 惰性求值(Lazy Evaluation):只在需要时计算。

示例

1
2
3
4
5
6
7
8
def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()
for x in gen:
    print(x)  # 输出 1, 2, 3

生成器表达式:

1
2
3
gen = (x * 2 for x in range(5))
for x in gen:
    print(x)  # 0, 2, 4, 6, 8

生成器 vs 迭代器

特性生成器迭代器
定义方式yield 或生成器表达式类实现 __iter____next__
易用性更简洁需手动实现协议
状态管理自动保存函数局部状态需手动管理状态(如索引)
内存效率高(惰性求值)取决于实现,可高可低

生成器是迭代器的一种实现方式,但不是所有迭代器都是生成器。


内存管理(Memory Management)

Python 使用自动内存管理,主要依赖:

  1. 引用计数(Reference Counting)
  2. 垃圾回收器(Garbage Collector, GC)

内存管理的目标是:自动分配和回收对象内存,避免内存泄漏和悬空指针。


引用计数(Reference Counting)

原理:

每个对象维护一个计数器,记录有多少个“引用”指向它。

  • 引用增加:赋值、传参、放入容器等。
  • 引用减少:变量离开作用域、被重新赋值、从容器中删除等。
  • 当引用计数归零 → 对象被立即销毁,内存被回收。

示例:

1
2
3
4
5
6
7
8
import sys

a = [1, 2, 3]  # 引用计数 = 1
b = a          # 引用计数 = 2
print(sys.getrefcount(a))  # 输出 3(包括 getrefcount 自身的临时引用)

del b          # 引用计数 = 1
del a          # 引用计数 = 0 → 对象被销毁

优点:

  • 实时性:对象一旦无引用,立即回收。
  • 简单高效。

缺点:

  • 无法处理循环引用(Cycle Reference)

循环引用(Cycle Reference)

什么是循环引用?

两个或多个对象互相引用,导致它们的引用计数永不归零。

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Node:
    def __init__(self, name):
        self.name = name
        self.parent = None
        self.children = []

a = Node("A")
b = Node("B")

a.children.append(b)  # A 引用 B
b.parent = a          # B 引用 A → 循环引用!

del a
del b
# 此时两个对象引用计数均为 1(互相引用),不会被引用计数回收!

后果:

内存泄漏 —— 对象无法被回收,占用内存持续增长。


GC 机制(Garbage Collector)

Python 使用 分代垃圾回收(Generational GC) 来解决循环引用问题。

基本原理:

  • 跟踪所有容器对象(如 list、dict、class instance 等),因为只有它们能形成循环引用。
  • 使用可达性分析:从“根对象”(如栈帧、全局变量)出发,标记所有可达对象,未被标记的就是垃圾。
  • 定期运行回收算法(通常是标记-清除)。

分代回收(Generational Collection)

Python 将对象分为三代(0, 1, 2):

  • 新创建对象 → 第 0 代。
  • 经历一次 GC 未被回收 → 升到第 1 代。
  • 再经历一次 → 升到第 2 代。

GC 频率:

  • 第 0 代最频繁(默认每分配 700 个对象触发一次)
  • 第 1 代较少
  • 第 2 代最少

策略:大多数对象“朝生暮死”,优先回收年轻代,提高效率。

手动控制 GC:

1
2
3
4
5
6
7
import gc

gc.collect()          # 手动触发垃圾回收
gc.get_count()        # 查看各代对象数量
gc.set_threshold(800, 15, 15)  # 设置触发阈值
gc.disable()          # 禁用 GC(不推荐)
gc.enable()           # 启用 GC

循环引用 + __del__ 方法的陷阱

如果循环引用中的对象定义了 __del__ 方法,Python 的 GC 无法确定销毁顺序,会将这些对象放入 gc.garbage 列表,不会自动回收

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Node:
    def __init__(self, name):
        self.name = name
        self.ref = None

    def __del__(self):
        print(f"Deleting {self.name}")

a = Node("A")
b = Node("B")
a.ref = b
b.ref = a

del a, b
gc.collect()  # 可能不会调用 __del__,对象进入 gc.garbage
print(gc.garbage)  # 显示未回收对象

最佳实践:避免在可能形成循环引用的类中使用 __del__,改用 weakref 或上下文管理器。


弱引用(Weak Reference)——解决循环引用的利器

弱引用不增加对象的引用计数,因此不会阻止对象被回收。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import weakref

class Node:
    def __init__(self, name):
        self.name = name
        self.parent = None
        self._children = []

    @property
    def children(self):
        return self._children

    def add_child(self, child):
        self._children.append(child)
        child.parent = weakref.ref(self)  # 弱引用!

a = Node("A")
b = Node("B")
a.add_child(b)

del a  # A 被回收,即使 B 仍持有对 A 的弱引用
print(b.parent())  # None —— 弱引用已失效

总结对比表:

概念说明优点缺点/注意点
迭代器实现 __iter____next__ 的对象标准协议,灵活可控需手动管理状态
生成器yield 定义的迭代器,自动保存状态代码简洁,内存高效一次性,不能回退
引用计数对象被引用次数归零时立即回收实时、高效无法处理循环引用
循环引用对象互相引用,导致引用计数 > 0无(是问题)导致内存泄漏
GC 机制分代回收 + 标记清除,解决循环引用自动处理循环引用有性能开销;__del__ 可能导致回收失败
弱引用不增加引用计数的引用打破循环引用,避免内存泄漏需手动检查是否失效

实际建议:

  1. 优先使用生成器处理大数据流,节省内存。
  2. 避免手动管理内存,Python 自动管理很可靠。
  3. 警惕循环引用,尤其在树形、图结构或父子关系中。
  4. 使用 weakref 打破循环引用。
  5. 避免在复杂对象中使用 __del__,改用 contextlib 或显式清理方法。
  6. 监控内存:使用 tracemallocobjgraphgc.get_objects() 等工具调试内存问题。

掌握这些概念,可以帮助写出更高效、更健壮、内存安全的 Python 代码!

版权声明:本文为原创,依据 CC BY-NC-SA 4.0 许可证进行授权,转载请附上出处链接及本声明。