Python代码慢?这5个性能优化技巧让速度提升100倍

Python代码慢?这5个性能优化技巧让速度提升100倍

你一定有过这种感觉:

代码逻辑写对了,结果也对,但跑起来就是慢。一个循环要跑几分钟,一个数据处理脚本要等半小时,你盯着屏幕发呆,怀疑是不是电脑该换了。

其实大概率不是电脑的问题,而是你的代码"姿势"不对。Python本身确实不是最快的语言,但大多数人的Python代码连它应有速度的1%都没发挥出来。

下面这5个优化技巧,每一个都能带来数量级的性能提升。它们不是什么高深理论,而是实打实能让你今天就用上的方法。


技巧一:把for循环换成向量化(提升10-100倍)

这是最大的一颗低垂果实。绝大多数Python慢代码,罪魁祸首都是for循环。

慢代码长这样:

ini 复制代码
# 计算100万个数的平方
import time

nums = list(range(1_000_000))

start = time.time()
result = []
for n in nums:
    result.append(n ** 2)
end = time.time()

print(f"耗时:{end - start:.4f} 秒")  # 约 0.15 秒

看着还行?那是因为数据量还不够大。当数据量到1000万时,这个循环要跑将近2秒。而且这只是最简单的计算,如果循环里涉及条件判断、字典查找、字符串操作,速度会进一步恶化。

优化后:

ini 复制代码
import numpy as np
import time

nums = np.arange(1_000_000)

start = time.time()
result = nums ** 2  # 向量化运算,一行搞定
end = time.time()

print(f"耗时:{end - start:.6f} 秒")  # 约 0.002 秒

75倍的差距。 ​ 而且代码还更短更清晰了。

为什么差距这么大?

Python的for循环,每一次迭代都要做类型检查、函数调用、内存分配。而NumPy的向量化运算,底层是C语言写的,直接在连续内存块上做批量计算,跳过了Python的所有解释器开销。

实战场景:数据处理

假设你有一个10万行的销售数据,要计算每条记录的"折扣后价格":

bash 复制代码
# ❌ 慢:for循环逐行计算
df['final_price'] = None
for i in range(len(df)):
    df.loc[i, 'final_price'] = df.loc[i, 'price'] * (1 - df.loc[i, 'discount'])
# 10万行,约 20-30 秒

# ✅ 快:向量化
df['final_price'] = df['price'] * (1 - df['discount'])
# 10万行,约 0.01 秒

2000倍以上的差距。 ​ 这就是很多人觉得pandas"快得离谱"的原因------它底层全是向量化运算。

适用场景

操作类型 用for循环 用向量化
数值计算 ✅ NumPy
DataFrame列运算 ✅ pandas直接运算
字符串处理 ✅ pandas.str 或 regex
条件筛选 ✅ 布尔索引
逐行依赖前一行结果 ✅ 有时不得不用 ❌ 试试 cumsum/shift

💡 核心原则:只要你的操作能写成"对整个数组/列做同一个运算",就不要用for循环。


技巧二:用生成器替代列表(内存降90%,速度提升2-5倍)

当你处理大文件或大数据集时,内存往往是瓶颈。内存不够,系统开始用磁盘交换(swap),速度瞬间掉到地板上。

慢代码:

python 复制代码
# 读取一个大文件,把所有行加载到内存
def read_lines(filename):
    lines = []
    with open(filename, 'r') as f:
        for line in f:
            lines.append(line.strip())
    return lines

# 如果文件有100万行,lines列表占用几百MB内存

优化后:

python 复制代码
# 用生成器,一次只加载一行到内存
def read_lines(filename):
    with open(filename, 'r') as f:
        for line in f:
            yield line.strip()

# 使用
for line in read_lines('huge_file.txt'):
    process(line)  # 逐行处理,内存占用恒定在几KB

更Pythonic的写法:

ini 复制代码
# 直接用生成器表达式
squares = (x ** 2 for x in range(10_000_000))  # 几乎不占内存
# 对比列表推导式
squares = [x ** 2 for x in range(10_000_000)]  # 立即占用几百MB

实战场景:处理大日志文件

python 复制代码
# ❌ 慢:一次性读入内存
with open('server.log', 'r') as f:
    lines = f.readlines()  # 2GB日志文件?内存直接爆
errors = [line for line in lines if 'ERROR' in line]

# ✅ 快:流式处理
errors = []
with open('server.log', 'r') as f:
    for line in f:  # 一次只读一行
        if 'ERROR' in line:
            errors.append(line)

如果只需要统计错误数量,甚至不需要存下来:

arduino 复制代码
error_count = sum(1 for line in open('server.log') if 'ERROR' in line)

内存占用从GB级别降到KB级别,速度自然也快了。


技巧三:用集合和字典替代列表查找(提升100-1000倍)

在列表里查找元素,Python要做线性扫描------从头到尾一个一个比对。列表有100万个元素,最坏情况要比较100万次。

慢代码:

ini 复制代码
# 检查10万个ID是否在白名单中
whitelist = list(range(1_000_000))  # 100万个ID的列表
user_ids = list(range(0, 2_000_000, 20))  # 10万个待检查的ID

start = time.time()
valid_users = []
for uid in user_ids:
    if uid in whitelist:  # 每次都是O(n)的线性查找
        valid_users.append(uid)
end = time.time()

print(f"耗时:{end - start:.2f} 秒")  # 约 50-80 秒

优化后:

ini 复制代码
# 把白名单转成集合
whitelist_set = set(whitelist)

start = time.time()
valid_users = [uid for uid in user_ids if uid in whitelist_set]  # O(1)查找
end = time.time()

print(f"耗时:{end - start:.4f} 秒")  # 约 0.02 秒

3000倍以上的差距。

为什么集合这么快?

列表查找是线性扫描(O(n)),集合和字典的查找基于哈希表(O(1))。原理类似字典查字:列表是从头翻到尾,集合是直接跳到那一页。

实战场景:数据去重和关联

ini 复制代码
# ❌ 慢:用列表做去重
unique_items = []
for item in huge_list:
    if item not in unique_items:  # O(n)查找
        unique_items.append(item)

# ✅ 快:用集合做去重
unique_items = list(set(huge_list))  # O(1)查找,一行搞定

技巧四:用内置函数和库函数(提升5-50倍)

Python的内置函数和标注库,底层都是C语言实现的。你自己写的Python代码再怎么优化,也跑不过C。

慢代码:

ini 复制代码
# 自己写求和
total = 0
for num in numbers:
    total += num

# 自己写排序
def bubble_sort(arr):
    for i in range(len(arr)):
        for j in range(len(arr)-1-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]

优化后:

ini 复制代码
# 用内置函数
total = sum(numbers)        # C语言实现,快5-10倍
sorted_arr = sorted(arr)    # Timsort算法,快20-50倍
max_val = max(numbers)      # 内置,快很多

实战场景:字符串拼接

ini 复制代码
# ❌ 慢:字符串累加
result = ''
for word in words:
    result += word + ' '  # 每次都创建新字符串对象

# ✅ 快:join方法
result = ' '.join(words)  # 一次分配内存,快10-100倍

实战场景:计数

ini 复制代码
# ❌ 慢:手动计数
counts = {}
for item in items:
    if item in counts:
        counts[item] += 1
    else:
        counts[item] = 1

# ✅ 快:用Counter
from collections import Counter
counts = Counter(items)  # 快3-5倍,代码还短

常用高性能内置替代方案

你的代码 用这个替代 提速倍数
手动for循环求和 sum() 5-10x
手动排序 sorted() 20-50x
字符串拼接 += 'sep'.join() 10-100x
手动计数 Counter 3-5x
手动查找最大/最小 max()/min() 5-10x
手动反转列表 reversed() 10-20x
手动判断是否存在 any()/all() 5-10x

技巧五:用多进程突破GIL限制(提升4-8倍)

Python有个著名的"缺陷"叫GIL(全局解释器锁),它让同一时刻只有一个线程能执行Python字节码。所以多线程对CPU密集型任务几乎没用。

多进程可以绕过GIL,每个进程有独立的Python解释器和内存空间,可以真正利用多核CPU。

慢代码:

python 复制代码
import time

def heavy_task(n):
    """模拟一个耗时计算"""
    result = 0
    for i in range(n):
        result += i ** 0.5
    return result

tasks = [10_000_000] * 8  # 8个耗时任务

start = time.time()
results = []
for task in tasks:
    results.append(heavy_task(task))
end = time.time()

print(f"单进程耗时:{end - start:.2f} 秒")  # 约 16-20 秒

优化后:

python 复制代码
import time
from multiprocessing import Pool

start = time.time()
with Pool(processes=8) as pool:  # 8个进程,对应8个CPU核心
    results = pool.map(heavy_task, tasks)
end = time.time()

print(f"多进程耗时:{end - start:.2f} 秒")  # 约 3-5 秒

4-6倍的提升。 ​ 如果你有8核CPU,理论上限就是8倍(实际因为进程创建和通信开销,到不了8倍,但4-6倍是很常见的)。

实战场景:批量图片处理

python 复制代码
from multiprocessing import Pool
from PIL import Image
import os

def resize_image(filename):
    """调整单张图片尺寸"""
    img_path = f"inputs/{filename}"
    img = Image.open(img_path)
    img = img.resize((224, 224))
    img.save(f"outputs/{filename}")
    return filename

image_files = os.listdir("inputs")

# 用多进程并行处理
with Pool(processes=os.cpu_count()) as pool:
    pool.map(resize_image, image_files)

⚠️ 注意事项

多进程不是银弹,有几个坑要注意:

  1. 进程启动有开销:任务太小(比如只算几毫秒),多进程的创建开销反而比直接跑还大。一般任务耗时在0.1秒以上才值得用多进程。
  2. 内存占用翻倍:每个进程都有独立的内存空间。如果主进程占2GB内存,开8个进程可能占到16GB+。确保内存够用。
  3. Windows上注意 __main__ 保护 :Windows的进程创建方式不同,必须把主代码放在 if __name__ == '__main__': 下面,否则会无限递归创建进程。
ini 复制代码
# Windows上正确的写法
def main():
    # 你的多进程代码
    pass

if __name__ == '__main__':
    main()

综合实战:优化一个真实场景

把5个技巧结合起来,看一个真实的优化案例。

场景:分析1000万条用户行为日志

需求:从1000万条日志中,找出每个用户最后10次行为中,有多少次是"购买"行为,并且只统计购买次数≥3的用户。

原始代码(慢):

ini 复制代码
# 读取日志
logs = []
with open('user_logs.txt', 'r') as f:
    for line in f:
        user_id, action, timestamp = line.strip().split(',')
        logs.append((int(user_id), action, int(timestamp)))

# 按用户分组
user_logs = {}
for user_id, action, timestamp in logs:
    if user_id not in user_logs:
        user_logs[user_id] = []
    user_logs[user_id].append((action, timestamp))

# 分析每个用户
result = {}
for user_id, actions in user_logs.items():
    # 按时间排序
    actions.sort(key=lambda x: x[1])
    # 取最后10次
    last_10 = actions[-10:]
    # 统计购买次数
    purchase_count = 0
    for action, _ in last_10:
        if action == 'purchase':
            purchase_count += 1
    if purchase_count >= 3:
        result[user_id] = purchase_count

print(len(result))

运行时间:约 120-180 秒。

优化后代码:

ini 复制代码
import pandas as pd
from collections import defaultdict

# 技巧二:用pandas高效读取(底层C实现)
df = pd.read_csv('user_logs.txt', names=['user_id', 'action', 'timestamp'])

# 技巧一:向量化过滤
purchases = df[df['action'] == 'purchase']

# 技巧三:用groupby(底层用哈希表,比手动字典快)
user_purchase_counts = (
    purchases.groupby('user_id')
    .size()
    .reset_index(name='purchase_count')
)

# 过滤购买次数≥3的用户
result = user_purchase_counts[user_purchase_counts['purchase_count'] >= 3]

print(len(result))

运行时间:约 3-5 秒。

提速30-50倍。 ​ 而且代码从30行变成了10行,可读性反而更好了。

如果数据量更大(上亿行),还可以进一步:

ini 复制代码
# 技巧四+五:分块读取 + 多进程
chunk_size = 1_000_000
results = []

for chunk in pd.read_csv('user_logs.txt', names=['user_id', 'action', 'timestamp'], chunksize=chunk_size):
    purchases = chunk[chunk['action'] == 'purchase']
    counts = purchases.groupby('user_id').size()
    results.append(counts)

# 合并结果
final_counts = pd.concat(results).groupby(level=0).sum()
result = final_counts[final_counts >= 3]

性能优化速查表

把5个技巧总结成一张速查表,方便你写代码时对照:

技巧 核心思想 适用场景 预期提速
向量化 用NumPy/pandas替代for循环 数值计算、数据处理 10-100x
生成器 用yield或()替代\[\] 大文件读取、大数据流 内存降90%+
集合/字典 用set/dict替代list查找 成员判断、去重、关联 100-1000x
内置函数 用C实现的内置函数 求和、排序、计数等 5-50x
多进程 绕过GIL用多核CPU CPU密集型批量任务 4-8x

写在最后

优化Python代码,最重要的不是记住这些技巧,而是养成一个习惯:写完代码后,问自己一句"这里有没有更Pythonic的写法?"

大多数情况下:

  • 你在写for循环 → 想想能不能向量化
  • 你在列表里做查找 → 想想能不能用集合
  • 你在手动实现常见功能 → 想想有没有内置函数
  • 你在处理大数据 → 想想能不能用生成器
  • 你在跑CPU密集型任务 → 想想能不能用多进程

不需要一开始就追求极致性能。先让代码跑起来,再找到瓶颈,针对性优化。用 time 模块或 cProfile 测一下,哪里慢优化哪里,比盲目优化有效得多。

python 复制代码
# 快速定位瓶颈
import cProfile
cProfile.run('your_function()')

这行代码会告诉你每个函数花了多少时间,哪里是真正的瓶颈一目了然。

相关推荐
超超不吵吵34 分钟前
Java AI转型实战(十):Agent 智能体初探
后端
步行cgn1 小时前
@AllArgsConstructor 是 Lombok 提供的注解
后端
柠檬味拥抱1 小时前
鸿蒙原生 AI 应用开发实战:用蓝耘 MaaS 给纪念日 App 装上「大模型大脑」
后端
步行cgn1 小时前
Lombok 详解:通过注解消除样板代码
后端
用户41180034153411 小时前
MySql 事务和锁
后端
一只小闪闪1 小时前
easy-trans java数据通用翻译框架v2.3.0
java·后端·架构
2601_962055811 小时前
Spring全部注解
java·后端·spring
步行cgn2 小时前
Spring Boot 启动报错:MissingServletWebServerFactoryBean 的排查与解决
java·spring boot·后端
步行cgn2 小时前
Spring Boot 项目打包部署详解
java·spring boot·后端