加速 Python for 循环的12个操作

简介: 加速 Python for 循环的12个操作

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

相关文章
|
5天前
|
Python
在 Python 中实现各种类型的循环判断
在 Python 中实现各种类型的循环判断
12 2
|
6月前
|
PHP Python
最新【Python】 实现循环最快的方式_python while循环加速,2024年最新阿里php面试题
最新【Python】 实现循环最快的方式_python while循环加速,2024年最新阿里php面试题
|
3月前
|
C语言 Python
Python 实现循环的最快方式(for、while 等速度对比)
Python 实现循环的最快方式(for、while 等速度对比)
|
3月前
|
C语言 Python
加速 Python for 循环的12个操作
加速 Python for 循环的12个操作
38 0
|
6月前
|
Python
【Python操作基础】——函数
【Python操作基础】——函数
|
5月前
|
Python
掌握Python循环:从基础到应用的完整指南
掌握Python循环:从基础到应用的完整指南
|
Java Python