返回专题首页

Python 专题

别忽视这些坑:性能优化、内存行为与常见陷阱

Python 的语法很友好,但这并不意味着它没有坑。可变默认参数、拷贝语义、引用共享、生成器和列表的内存差异、GIL 的影响,这些问题一旦在实际项目里出现,往往都不是一句“语法记错了”能带过的。

Python 专题第 27 篇 / 39 篇4 分钟

Python 的语法很友好,但这并不意味着它没有坑。可变默认参数、拷贝语义、引用共享、生成器和列表的内存差异、GIL 的影响,这些问题一旦在实际项目里出现,往往都不是一句“语法记错了”能带过的。

这一节我们会把这些高频陷阱集中放在一起,不只是为了避坑,更是为了帮助你建立一种更底层的理解:代码为什么会这样表现,优化又应该从哪里开始。

可变默认参数为什么危险?

这个问题我们前面讲函数时已经遇到过,但它值得在“性能与陷阱”章节里再次强调,因为它本质上暴露的是对象共享和生命周期问题。

可变默认参数危险,不只是因为“结果会串”,更因为它会制造隐式共享状态,让函数行为变得难以推断。

这类问题最可怕的地方,不在于难修,而在于它在小样例里很容易隐藏起来,等到真实项目里才突然爆出来。

深拷贝、浅拷贝与引用共享

Python 里变量绑定的是对象引用,而不是值本体。这意味着当你复制一个容器时,很容易只复制了外层结构,而内部对象仍然共享。

所以浅拷贝适合:

  • 结构简单;
  • 内部元素不需要独立修改;
  • 明确知道共享是可接受的场景。

而一旦内部存在嵌套可变对象,又需要真正独立副本,就要考虑深拷贝。

理解这件事的意义在于,很多“为什么改了 A,B 也跟着变了”的问题,根本不是 Python 古怪,而是对象共享本来就是这样发生的。

列表、生成器与内存占用差异

当你写:

[x * 2 for x in range(1000000)]

和:

(x * 2 for x in range(1000000))

虽然表面只差一个括号,但内存行为完全不同。

前者会立刻生成完整列表,后者则是生成器,按需产出结果。

所以性能问题并不总是“算法太差”,很多时候只是数据处理方式和真实需求不匹配。你明明只需要逐步消费数据,却先把全量结果都堆进了内存。

GIL 到底影响了什么?

GIL 经常被提及,但也经常被误解。

你不用把它理解成“Python 不能并发”,更准确地说,它会影响同一进程内多个线程并行执行 Python 字节码的方式。

这会让 CPU 密集型任务在线程模型下很难真正利用多核优势,但对 I/O 密集型任务来说,线程依然可能是合适方案。

所以 GIL 的关键不是“它好不好”,而是提醒你:并发模型选择必须基于任务特征,而不是只看概念名字。

常见性能优化思路

先测量,再优化

性能优化里最危险的事,就是凭感觉乱改。

很多时候真正慢的地方和你以为的地方根本不是同一处。所以优化前先测量,是非常重要的工程纪律。你至少要知道:

  • 时间花在哪;
  • 内存消耗在哪;
  • 是算法问题、数据结构问题,还是 I/O 边界问题。

善用内置能力与标准库

Python 很多性能上的“低成本提升”,并不是去写复杂技巧,而是:

  • 用对数据结构;
  • 用对标准库;
  • 避免不必要中间结果;
  • 把高频循环内能外提的动作外提。

这类优化比起炫技式微调,往往更稳也更容易维护。

总结与预告

这一节我们把一批常见但很容易被忽略的问题集中摊开了来看。很多“奇怪行为”背后其实并不奇怪,只是我们此前还没有站到足够底层的角度去理解 Python 的对象模型和执行方式。

下一节我们会顺势进入并发主题,先把线程、进程和协程之间的边界与选择逻辑讲清楚。