在本文中,我将介绍几种简单而有效的方法,可以显著提高 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 代码,提升你的编程水平和项目性能。希望这篇文章能为你提供有价值的参考与启发!