为什么 Python 代码在函数中运行得更快
当谈到编程效率和性能优化时,Python 常常被调侃为'慢如蜗牛'。有趣的是,在实际开发中发现,Python 代码在函数内部运行往往比在全局范围内(脚本顶层)运行要快得多。很多开发者可能会疑惑:为什么在函数中运行的 Python 代码速度更快?
Python 执行机制简述
要理解这一现象,首先需要了解 Python 是如何执行代码的。Python 是一种解释型语言,它会逐行读取并执行代码。当运行一个 Python 程序时,首先将源代码编译成字节码(一种更接近机器码的中间语言),然后 Python 解释器执行这些字节码。
需要注意的是,Python 解释器是一个执行字节码的虚拟机。默认的 Python 解释器是 CPython,它是用 C 编写的。此外还有 Jython(用 Java 编写)、IronPython(用于 .NET)和 PyPy(用 Python 和 C 编写)等其他实现。
Python 中的 dis 模块可以将函数分解为字节码指令,帮助我们查看底层执行细节。
局部变量与全局变量的存储差异
我们来通过一个简单的例子来对比。定义一个函数 my_function,函数内部包含一个 for 循环。
def my_function():
for i in range(100):
pass
编译该函数时,生成的字节码中包含关键指令 STORE_FAST,用于存储循环变量 i。这表示变量存储在局部作用域的快速数组中。
现在,我们将同样的 for 循环放在 Python 脚本的顶层(全局范围内):
for i in range(100):
pass
再次查看其字节码,可以看到关键指令变成了 STORE_NAME,而不是 STORE_FAST。
核心原因分析
字节码 STORE_FAST 比 STORE_NAME 快,原因在于变量存储机制的不同:
- 局部变量:在函数中,局部变量存储在固定长度的数组中。这个数组可以通过索引直接访问,使得变量检索非常快。本质上,它只是一个指向列表的指针,并增加了 PyObject 的引用计数,这两个都是高效的操作。
- 全局变量:全局变量存储在一个字典(哈希表)中。当访问全局变量时,Python 必须执行哈希表查找,这涉及计算哈希值,然后检索与之关联的值。虽然经过优化,但仍然比基于索引的查找慢。
因此,减少全局变量的使用,尽量使用局部变量,是提升 Python 代码性能的关键策略之一。
基准测试验证
为了量化这种差异,我们可以使用 Python 内置的 timeit 模块对代码进行计时。
函数内执行
def factorial(n):
result = 1
for i in range(1, n + 1):
result *= i
return result
print(timeit.timeit('factorial(10)', setup='from __main__ import factorial', number=10000))
全局范围执行
result = 1
for i in range(1, 11):
result *= i
print(timeit.timeit('result', 'from __main__ import result', number=10000))
注意:这两段代码最好不要放在同一脚本中同时运行,因为
benchmark()函数在执行时间上增加了一些开销,且全局代码在内部进行了特定优化。分开单独运行更能反映真实差异。
测试结果通常显示,函数代码的执行速度明显优于全局作用域代码。
cProfile 性能分析
Python 提供了一个 cProfile 内置模块,用于分析代码的性能瓶颈。
让我们用它来分析一个新例子:在局部和全局范围内计算平方和。
# 局部版本
def sum_of_squares_local(n):
total = 0
for i in range(n):
total += i * i
return total
# 全局版本
total = 0
for i in range(1000):
total += i * i
从性能分析结果中可以看到,函数代码在执行时间方面比全局代码更有效,主要消耗在函数调用开销上,但变量访问开销显著降低。
如何优化 Python 函数的性能
既然我们知道 Python 代码在函数中运行往往比在全局范围内运行要快得多,那么如何进一步优化呢?
1. 优先使用局部变量
避免在循环中使用全局变量。如果必须在循环中使用某个值,请将其赋值给局部变量。
# 不推荐:每次循环都查找全局变量
data = [1, 2, 3]
for item in data:
print(item)
# 推荐:使用局部变量
def process_data():
local_data = [1, 2, 3]
for item in local_data:
print(item)
2. 使用内置函数和库
尽可能使用内置函数和标准库。Python 的许多内置函数是用 C 实现的,比纯 Python 代码快得多。
- NumPy 和 Pandas:这些库底层使用 C 或 C++ 实现,处理数值运算和数据操作的速度远超原生 Python 列表。
- 内置函数:同样是实现数字求和功能,Python 内置的
sum函数要比自己编写循环速度快得多。
# 推荐
numbers = [1, 2, 3, 4, 5]
print(sum(numbers))
# 不推荐
print(sum([x for x in numbers])) # 额外生成列表
3. 避免不必要的属性查找
在类方法或函数中,频繁访问对象属性也会带来开销。可以将常用属性缓存到局部变量中。
总结
Python 代码在函数中运行更快的根本原因在于变量查找机制的差异:局部变量通过数组索引快速访问,而全局变量需要通过哈希表查找。通过遵循最佳实践,如封装代码到函数、使用局部变量以及利用高性能的内置库,可以显著提升 Python 程序的执行效率。


