当前位置: 首页 > news >正文

Python性能优化实战(二):让循环跑得比博尔特还快

循环就像程序中的"交通枢纽",一旦拥堵,整个系统都会变慢。在Python中,循环优化往往能带来立竿见影的性能提升。本文将揭秘让循环"瘦身提速"的三大实战技巧,让你的代码从"步履蹒跚"变成"健步如飞"。

一、用内置函数给循环"装上引擎"

手动for循环就像徒步旅行,而内置函数则是乘坐高铁——同样的目的地,效率天差地别。

列表推导式 vs 手动for循环

场景:将列表中的每个数字平方

反例:传统for循环

import time# 生成100万个数字
numbers = list(range(1000000))# 手动循环平方
start = time.time()
result = []
for num in numbers:result.append(num **2)
end = time.time()print(f"手动循环耗时:{(end - start) * 1000:.2f}ms")  # 约80-100ms

正例:列表推导式

import timenumbers = list(range(1000000))# 列表推导式
start = time.time()
result = [num** 2 for num in numbers]
end = time.time()print(f"列表推导式耗时:{(end - start) * 1000:.2f}ms")  # 约40-50ms

map()函数的"隐形翅膀"

场景:将字符串列表转换为整数列表

import time# 100万个数字字符串
str_numbers = [str(i) for i in range(1000000)]# 方法1:for循环
start = time.time()
int_numbers = []
for s in str_numbers:int_numbers.append(int(s))
loop_time = (time.time() - start) * 1000# 方法2:map()函数
start = time.time()
int_numbers = list(map(int, str_numbers))  # map返回迭代器,转成列表
map_time = (time.time() - start) * 1000print(f"for循环耗时:{loop_time:.2f}ms")    # 约120ms
print(f"map函数耗时:{map_time:.2f}ms")     # 约60ms

filter()函数的"精准筛选"

场景:筛选出列表中的偶数

import timenumbers = list(range(1000000))# 方法1:for循环+if判断
start = time.time()
evens = []
for num in numbers:if num % 2 == 0:evens.append(num)
loop_time = (time.time() - start) * 1000# 方法2:filter()函数
start = time.time()
evens = list(filter(lambda x: x % 2 == 0, numbers))
filter_time = (time.time() - start) * 1000print(f"for循环筛选:{loop_time:.2f}ms")   # 约70ms
print(f"filter筛选:{filter_time:.2f}ms")  # 约40ms

为什么内置函数更快?

  • 内置函数(map、filter、列表推导式)是用C语言实现的,执行时绕过了Python解释器的字节码解释步骤
  • 手动for循环需要反复执行Python的字节码指令,有更多的"管理成本"
  • 数据量越大,内置函数的优势越明显(百万级数据通常快2-5倍)

避坑指南

  • 复杂逻辑优先用列表推导式(可读性更好)
  • 简单转换操作(如类型转换)用map()更简洁
  • 避免在列表推导式中写复杂条件(会降低可读性)

二、给循环"减负":把计算搬出循环

循环内部的每一行代码都会被执行成千上万次。把不必要的计算移到循环外,就像给负重奔跑的人卸下包袱。

案例1:避免重复计算

场景:计算列表中每个元素与平均值的差值

反例:循环内重复计算

import timenumbers = list(range(1000000))# 循环内计算平均值(重复计算了100万次!)
start = time.time()
differences = []
for num in numbers:# 每次循环都重新计算总和和平均值avg = sum(numbers) / len(numbers)differences.append(num - avg)
bad_time = (time.time() - start) * 1000# 循环外计算平均值(只算1次)
start = time.time()
avg = sum(numbers) / len(numbers)  # 移到循环外
differences = []
for num in numbers:differences.append(num - avg)
good_time = (time.time() - start) * 1000print(f"循环内计算:{bad_time:.2f}ms")  # 约5000ms+
print(f"循环外计算:{good_time:.2f}ms")  # 约50ms

性能差异:快了100倍以上!因为sum(numbers)是O(n)操作,在循环内执行就变成了O(n²)。

案例2:避免重复调用函数

场景:根据配置过滤列表元素

import time# 模拟配置检查函数
def is_allowed(value):# 模拟一些复杂计算return value % 3 == 0numbers = list(range(1000000))# 反例:循环内重复获取配置
start = time.time()
allowed = []
for num in numbers:# 每次循环都调用函数检查配置(其实配置不会变)if is_allowed(num):allowed.append(num)
bad_time = (time.time() - start) * 1000# 正例:提前获取判断条件(如果条件固定)
start = time.time()
# 假设允许的条件是固定的,提前计算判断逻辑
allowed = [num for num in numbers if is_allowed(num)]
good_time = (time.time() - start) * 1000print(f"重复调用函数:{bad_time:.2f}ms")  # 约90ms
print(f"优化后:{good_time:.2f}ms")       # 约60ms

循环优化黄金法则

  1. 循环外计算所有不变的值(总和、平均值、配置参数等)
  2. 提前定义循环中需要用到的函数或方法(避免每次循环查找)
  3. 复杂表达式拆分到循环外(如math.sqrt(5)这类固定值)
  4. 用局部变量替代循环内的属性访问(如len(numbers)在循环外计算)

三、生成器:处理大数据的"内存魔术师"

当数据量大到一定程度,列表会像巨石一样压垮内存。生成器则像"水流"——按需生成,用完即弃,几乎不占内存。

列表 vs 生成器:内存占用大比拼

场景:处理1000万个整数的平方

import memory_profiler
import time# 测试内存占用的装饰器
def memory_test(func):def wrapper(*args, **kwargs):mem_usage = memory_profiler.memory_usage((func, args, kwargs))print(f"内存使用峰值:{max(mem_usage):.2f} MB")return wrapper# 用列表存储(内存杀手)
@memory_test
def big_list():# 生成1000万个数字的平方(约占用800MB内存)return [i** 2 for i in range(10000000)]# 用生成器(内存友好)
@memory_test
def big_generator():# 生成器表达式,内存占用几乎为0return (i **2 for i in range(10000000))print("列表内存占用:")
big_list()  # 约800MBprint("\n生成器内存占用:")
big_generator()  # 约0.1MB

yield关键字:自定义生成器

场景:读取大文件并处理每行数据

反例:一次性读取整个文件

# 处理10GB的日志文件(这样做会直接内存溢出)
with open("huge_logfile.txt", "r") as f:lines = f.readlines()  # 尝试将所有行读入内存for line in lines:process(line)  # 处理单行数据

正例:用生成器逐行处理

def log_processor(file_path):"""生成器:逐行读取并处理日志"""with open(file_path, "r") as f:for line in f:  # 迭代器方式逐行读取# 处理逻辑(如提取IP地址)ip = line.split()[0]yield ip  # 逐个返回结果# 使用生成器
for ip in log_processor("huge_logfile.txt"):analyze_ip(ip)  # 处理每个IP,内存占用稳定

生成器适用场景

  • 处理大型文件(日志、CSV、数据库备份等)
  • 生成大量数据(如测试数据、序列生成)
  • 流式处理(网络数据、实时日志)
  • 递归遍历(如目录树、JSON嵌套结构)

生成器小技巧

  • (x for x in iterable)创建简单生成器表达式
  • 复杂逻辑用yield关键字定义生成器函数
  • 生成器只能迭代一次,需要多次使用可转成列表(谨慎!)
  • 结合itertools模块使用,威力更大(如itertools.islice分页处理)

循环优化实战总结

1.** 内置函数优先 **- 简单转换用map(),筛选用filter()

  • 复杂场景用列表推导式(可读性与性能兼顾)
  • 避免在循环中使用append(),尽量一次性构建

2.** 循环减负原则 **- 口诀:“不变的计算,统统搬出循环”

  • 提前计算:总和、长度、配置参数
  • 减少函数调用:循环外定义或缓存结果
  • 局部变量比全局变量/属性访问更快

3.** 大数据用生成器 **- 内存敏感场景,用(表达式)替代[表达式]

  • 自定义生成器用yield,实现"边生成边处理"
  • 处理文件时,永远用迭代器方式逐行读取

最后记住:优化循环时,先用timeit模块测试真实性能差异:

import timeit# 测试不同方法的执行时间
print("列表推导式:", timeit.timeit('[x*2 for x in range(1000)]', number=10000))
print("for循环:", timeit.timeit('''
result=[]
for x in range(1000):result.append(x*2)
''', number=10000))

掌握这些技巧,你的Python代码将在处理大量数据时如虎添翼,既跑得快,又吃得少(内存)!

http://www.xdnf.cn/news/1360855.html

相关文章:

  • 27.编程思想
  • 【golang长途旅行第30站】channel管道------解决线程竞争的好手
  • Teams Bot机器人实时语音识别的多引擎的处理
  • TCP--执行Linux命令(虚拟xshell)
  • 数据建模怎么做?一文讲清数据建模全流程
  • 一、基因组选择(GS)与基因组预测(GP)
  • 网络安全转型书籍清单
  • 【Java开发日记】我们来讲一讲 Channel 和 FileChannel
  • 深度学习之第一课深度学习的入门
  • VirtualBox安装openEuler24.03
  • daily notes[5]
  • 前端 vs 后端请求:核心差异与实战对比
  • 05 线性代数【动手学深度学习v2】
  • 中介者模式与几个C++应用实例
  • imx6ull-驱动开发篇39——Linux INPUT 子系统实验
  • 【基础算法】初识搜索:递归型枚举与回溯剪枝
  • 【ElasticSearch】springboot整合es案例
  • Smooze Pro for mac 鼠标手势增强软件
  • 【C语言练习】青蛙跳台阶
  • Vue状态管理工具pinia的使用以及Vue组件通讯
  • 强光干扰下检出率↑93%!陌讯多模态融合算法在充电桩车位占用检测的实战解析
  • 力扣【1277. 统计全为1的正方形子矩阵】——从暴力到最优的思考过程
  • 【网络运维】Shell脚本编程:函数
  • 深度学习之第二课PyTorch与CUDA的安装
  • AOSP构建指南:从零开始的Android源码之旅
  • Docker 容器(一)
  • 【Docker基础】Docker-compose常用命令实践(三):镜像与配置管理
  • 【零代码】OpenCV C# 快速开发框架演示
  • 电路学习(四)二极管
  • 【计算机视觉】CaFormer