在本文中,我将介绍几种简单而有效的方法,可以显著提高 Python 中 for 循环的执行速度,提升倍率从 1.3 倍到 900 倍不等。为了帮助你理解这些优化技巧,我们将使用 Python 内建的 timeit 模块来测量每种方法的性能。
通过对每种方法进行基线测试,包括在 10 次测试运行中执行被测函数 100,000 次循环,然后计算每个循环的平均时间(以纳秒为单位),我们可以清楚地看到改进后的效果。
1. 使用列表推导式
基线版本(低效的方式)
# 基础版本:未使用列表推导式 def test_01_v0(numbers): output = [] for n in numbers: output.append(n ** 2.5) # 计算每个数字的 2.5 次方 return output
改进版本(使用列表推导式)
# 改进版本:使用列表推导式 def test_01_v1(numbers): output = [n ** 2.5 for n in numbers] # 使用列表推导式计算 return output
测试结果
import timeit numbers = range(100000) # Baseline performance baseline_time = timeit.timeit("test_01_v0(numbers)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_01_v1(numbers)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop") return output
结果分析
- 基线性能: 32.158 ns 每次循环
- 改进性能: 16.040 ns 每次循环
- 性能提升: 50.1%
- 速度提升: 2.00x
使用列表推导式使得计算速度加快了 2 倍,提高了代码的简洁性和可读性。
2. 在外部计算长度
基线版本(低效的方式)
# 基础版本:在循环内部计算长度 def test_02_v0(numbers): output_list = [] for i in range(len(numbers)): output_list.append(i * 2) # 依赖于列表长度 return output_list
改进版本(在外部计算长度)
# 改进版本:将长度计算移出循环 def test_02_v1(numbers): my_list_length = len(numbers) # 提前计算长度 output_list = [] for i in range(my_list_length): output_list.append(i * 2) return output_list
测试结果
# Baseline performance baseline_time = timeit.timeit("test_02_v0(numbers)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_02_v1(numbers)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 112.135 ns 每次循环
- 改进性能: 68.304 ns 每次循环
- 性能提升: 39.1%
- 速度提升: 1.64x
将列表长度计算移出循环后,整体加速效果达到 1.64 倍,减少了多余的计算。
3. 使用 Set 进行集合比较
基线版本(低效的方式)
# 基础版本:嵌套循环查找公共元素 def test_03_v0(list_1, list_2): common_items = [] for item in list_1: if item in list_2: # 使用 for 循环进行查找 common_items.append(item) return common_items
改进版本(使用 Set)
# 改进版本:使用集合替代嵌套循环 def test_03_v1(list_1, list_2): s_1 = set(list_1) # 将列表转换为集合 s_2 = set(list_2) # 将列表转换为集合 return list(s_1.intersection(s_2)) # 找出交集并返回
测试结果
list_1 = range(10000) list_2 = range(5000, 15000) # Baseline performance baseline_time = timeit.timeit("test_03_v0(list_1, list_2)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_03_v1(list_1, list_2)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 9047.078 ns 每次循环
- 改进性能: 18.161 ns 每次循环
- 性能提升: 99.8%
- 速度提升: 498.17x
在使用集合后,查找公共元素的速度提升显著,达到了近 500 倍的提升。
4. 跳过不相关的迭代
基线版本(低效的方式)
# 基础版本:冗余计算 def function_do_something(numbers): for n in numbers: square = n * n # 计算平方 if square % 2 == 0: # 检查是否为偶数 return square # 返回第一个偶数平方 return None # 未找到偶数平方
改进版本(避免冗余计算)
# 改进版本:先过滤偶数 def function_do_something_v1(numbers): even_numbers = [n for n in numbers if n % 2 == 0] # 先筛选偶数 for n in even_numbers: square = n * n return square # 返回第一个偶数平方 return None # 未找到偶数平方
测试结果
# Baseline performance baseline_time = timeit.timeit("function_do_something(range(100000))", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("function_do_something_v1(range(100000))", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop"
结果分析
- 基线性能: 16.912 ns 每次循环
- 改进性能: 8.697 ns 每次循环
- 性能提升: 48.6%
- 速度提升: 1.94x
通过跳过不相关的迭代,性能提高了近 2 倍,使得计算更加高效。
5. 代码合并以减少函数调用
基线版本(低效的方式)
# 基础版本:多次调用 is_prime 函数 def is_prime(n): if n <= 1: return False for i in range(2, int(n**0.5) + 1): if n % i == 0: return False return True def test_05_v0(n): count = 0 for i in range(2, n + 1): # 从 2 到 n 之间的数 if is_prime(i): # 调用 is_prime 函数 count += 1 return count
改进版本(内联逻辑)
# 改进版本:将 is_prime 的逻辑内联到循环中 def test_05_v1(n): count = 0 for i in range(2, n + 1): if i <= 1: continue for j in range(2, int(i**0.5) + 1): if i % j == 0: break else: count += 1 return count
测试结果
# Baseline performance baseline_time = timeit.timeit("test_05_v0(1000)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_05_v1(1000)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 1271.188 ns 每次循环
- 改进性能: 939.603 ns 每次循环
- 性能提升: 26.1%
- 速度提升: 1.35x
将函数逻辑内联到循环中减少了函数调用的开销,从而提高了性能。
6. 避免重复计算
基线版本(低效的方式)
# 基础版本:在嵌套循环中重复计算 def test_07_v0(n): result = 0 for i in range(n): for j in range(n): result += i * j # 重复计算 return result
改进版本(利用预计算)
# 改进版本:使用预计算值 def test_07_v1(n): pv = [[i * j for j in range(n)] for i in range(n)] # 预计算 result = 0 for i in range(n): result += sum(pv[i][:i + 1]) # 使用预计算的值 return result
测试结果
# Baseline performance baseline_time = timeit.timeit("test_07_v0(100)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_07_v1(100)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 139.146 ns 每次循环
- 改进性能: 92.325 ns 每次循环
- 性能提升: 33.6%
- 速度提升: 1.51x
通过预计算避免了重复计算,性能提高明显。
7. 使用生成器
基线版本(低效的方式)
# 基础版本:使用列表计算 Fibonacci 数列 def test_08_v0(n): f_list = [0, 1] for i in range(2, n + 1): f_list.append(f_list[i - 1] + f_list[i - 2]) # 计算 Fibonacci return f_list[n]
改进版本(使用生成器)
# 改进版本:使用生成器动态计算 Fibonacci def test_08_v1(n): a, b = 0, 1 for _ in range(n): yield a # 生成当前 Fibonacci 值 a, b = b, a + b
测试结果
# Baseline performance baseline_time = timeit.timeit("test_08_v0(100)", globals=globals(), number=10) # Improved performance (using generator) improved_time = timeit.timeit("list(test_08_v1(100))", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 0.083 ns 每次循环
- 改进性能: 0.004 ns 每次循环
- 性能提升: 95.5%
- 速度提升: 22.06x
使用生成器不仅可以节省内存,还能显著提高性能。
8. 使用 map() 函数
基线版本(低效的方式)
# 基础版本:使用 for 循环处理列表 def some_function_X(x): return x ** 2 # 执行某些操作 def test_09_v0(numbers): output = [] for i in numbers: output.append(some_function_X(i)) # 使用显式 for 循环 return output
改进版本(使用 map())
# 改进版本:使用内置的 map() 函数 def test_09_v1(numbers): return list(map(some_function_X, numbers)) # 使用 map
测试结果
# Baseline performance baseline_time = timeit.timeit("test_09_v0(numbers)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_09_v1(numbers)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 4.402 ns 每次循环
- 改进性能: 0.005 ns 每次循环
- 性能提升: 99.9%
- 速度提升: 970.69x
使用 map() 显著提高了处理速度,因为它是用 C 语言实现的,性能更优。
9. 使用记忆化(Memoization)
基线版本(低效的方式)
# 基础版本:递归计算 Fibonacci 数列 def fibonacci(n): if n == 0: return 0 elif n == 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2) def test_10_v0(numbers): output = [] for i in numbers: output.append(fibonacci(i)) # 计算 Fibonacci return output
改进版本(使用 lru_cache)
import functools # 使用 lru_cache 进行记忆化 @functools.lru_cache() def fibonacci_v2(n): if n == 0: return 0 elif n == 1: return 1 return fibonacci_v2(n - 1) + fibonacci_v2(n - 2) def test_10_v1(numbers): output = [] for i in numbers: output.append(fibonacci_v2(i)) # 利用记忆化计算 return output
测试结果
# Baseline performance baseline_time = timeit.timeit("test_10_v0(range(30))", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_10_v1(range(30))", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 63.664 ns 每次循环
- 改进性能: 1.104 ns 每次循环
- 性能提升: 98.3%
- 速度提升: 57.69x
通过使用 lru_cache 实现记忆化,成功减少了函数调用的重复计算,大幅提升性能。
10. 向量化(利用 NumPy)
基线版本(低效的方式)
import numpy as np # 基础版本:使用 for 循环计算和 def test_11_v0(n): output = 0 for i in range(0, n): output += i # 累加数字 return output
改进版本(使用 NumPy 向量化)
# 改进版本:使用 NumPy 向量化计算 def test_11_v1(n): return np.sum(np.arange(n)) # 利用 NumPy 计算和
测试结果
# Baseline performance baseline_time = timeit.timeit("test_11_v0(10000)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_11_v1(10000)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 32.936 ns 每次循环
- 改进性能: 1.171 ns 每次循环
- 性能提升: 96.4%
- 速度提升: 28.13x
向量化利用 NumPy 的快速运算,显著提高了性能。
11. 避免创建中间列表
基线版本(低效的方式)
# 基础版本:创建中间列表 def test_12_v0(numbers): filtered_data = [] for i in numbers: filtered_data.extend(list(filter(lambda x: x % 5 == 0, range(1, i**2)))) # 创建中间列表 return filtered_data
改进版本(使用 filterfalse)
from itertools import filterfalse # 改进版本:使用 filterfalse 避免中间列表 def test_12_v1(numbers): filtered_data = [] for i in numbers: filtered_data.extend(list(filterfalse(lambda x: x % 5 != 0, range(1, i**2)))) # 直接过滤 return filtered_data
测试结果
# Baseline performance baseline_time = timeit.timeit("test_12_v0(range(100))", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_12_v1(range(100))", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 333167.790 ns 每次循环
- 改进性能: 2541.850 ns 每次循环
- 性能提升: 99.2%
- 速度提升: 131.07x
通过避免创建中间列表,显著降低了内存使用,同时提高了性能。
12. 高效连接字符串
基线版本(低效的方式)
# 基础版本:使用 + 操作符连接字符串 def test_13_v0(l_strings): output = "" for a_str in l_strings: output += a_str # 字符串连接 return output
改进版本(使用 join)
# 改进版本:使用 join 函数连接字符串 def test_13_v1(l_strings): return "".join(l_strings) # 使用 join 连接
测试结果
from faker import Faker def generate_fake_names(count: int = 10000): fake = Faker() output_list = [] for _ in range(count): output_list.append(fake.name()) return output_list l_strings = generate_fake_names(count=50000) # Baseline performance baseline_time = timeit.timeit("test_13_v0(l_strings)", globals=globals(), number=10) # Improved performance improved_time = timeit.timeit("test_13_v1(l_strings)", globals=globals(), number=10) print(f"Baseline: {baseline_time / 10:.3f} ns per loop") print(f"Improved: {improved_time / 10:.3f} ns per loop")
结果分析
- 基线性能: 32.423 ns 每次循环
- 改进性能: 21.051 ns 每次循环
- 性能提升: 35.1%
- 速度提升: 1.54x
使用 join() 函数而不是 + 运算符连接字符串的性能得到了提升,这是因为 join 的时间复杂度为 O(n),而 + 的时间复杂度为 O(n²)。
总结
本文介绍了一系列有效的 Python 优化技巧,这些技巧可以提升 for 循环的性能,从 1.3 倍到 970 倍。这些方法包括:
- 使用列表推导式
- 在外部计算长度
- 使用集合取代嵌套循环
- 跳过不相关的迭代
- 合并代码以减少函数调用
- 避免重复计算
- 使用生成器
- 使用 map() 函数
- 使用记忆化(Memoization)
- 向量化操作
- 避免创建中间列表
- 高效连接字符串
掌握这些技巧将帮助你编写更高效、更优雅的 Python 代码,提升你的编程水平和项目性能。希望这篇文章能为你提供有价值的参考与启发!