别再写丑陋的for循环了,Python列表推导式让代码优雅10倍

39 阅读9分钟

你有没有遇到过这种情况:

# 面试官问:把1到100的偶数平方存到列表里
result = []
for i in range(1, 101):
    if i % 2 == 0:
        result.append(i * i)

然后面试官看了三秒,说了句:"你知道Python有列表推导式吗?"

你愣住了。我也愣住了。

今天咱们就来彻底搞懂这个让代码瞬间变得优雅的Python神器。

先看看你的代码有多"丑"

还记得刚学Python时写的这些代码吗?

# 场景1:过滤列表中的偶数
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = []
for num in numbers:
    if num % 2 == 0:
        even_numbers.append(num)

# 场景2:字符串处理
words = ['hello', 'world', 'python', 'awesome']
uppercase_words = []
for word in words:
    uppercase_words.append(word.upper())

# 场景3:嵌套循环(简直是噩梦)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = []
for row in matrix:
    for item in row:
        flattened.append(item)

看到这些代码,我想起了我的第一份工作...那时候我也是这么写的,结果被代码review时被吐槽得体无完肤。

(; ̄Д ̄)

列表推导式:一行代码顶十行

基础语法:[表达式 for 变量 in 可迭代对象]

# ❌ 之前的写法
squares = []
for i in range(1, 6):
    squares.append(i * i)

# ✅ 列表推导式
squares = [i * i for i in range(1, 6)]
# 结果: [1, 4, 9, 16, 25]

看到了吗?原本需要4行的代码,现在1行搞定!

带条件的推导式:[表达式 for 变量 in 可迭代对象 if 条件]

# ❌ 之前的写法
even_squares = []
for i in range(1, 11):
    if i % 2 == 0:
        even_squares.append(i * i)

# ✅ 列表推导式
even_squares = [i * i for i in range(1, 11) if i % 2 == 0]
# 结果: [4, 16, 36, 64, 100]

复杂条件:if-else 三元表达式

# ❌ 之前的写法
grades = [85, 92, 78, 65, 95]
level = []
for grade in grades:
    if grade >= 90:
        level.append('A')
    elif grade >= 80:
        level.append('B')
    else:
        level.append('C')

# ✅ 列表推导式
level = ['A' if grade >= 90 else 'B' if grade >= 80 else 'C'
         for grade in grades]
# 结果: ['B', 'A', 'C', 'C', 'A']

嵌套循环:一行搞定矩阵展开

# ❌ 之前的写法(简直是灾难)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = []
for row in matrix:
    for item in row:
        flattened.append(item)

# ✅ 列表推导式
flattened = [item for row in matrix for item in row]
# 结果: [1, 2, 3, 4, 5, 6, 7, 8, 9]

注意: 嵌套推导式的顺序是从左到右,对应正常for循环的顺序!

生成器表达式:内存优化的神器

列表推导式 vs 生成器表达式

# 列表推导式:立即创建整个列表
list_comp = [i * i for i in range(1000000)]
print(type(list_comp))  # <class 'list'>

# 生成器表达式:返回生成器对象,按需计算
gen_exp = (i * i for i in range(1000000))
print(type(gen_exp))    # <class 'generator'>

什么时候用生成器?

# 场景:处理大数据量
import sys

# ❌ 列表推导式:内存占用大
squares_list = [i * i for i in range(1000000)]
print(f"列表推导式占用内存:{sys.getsizeof(squares_list)} bytes")

# ✅ 生成器表达式:几乎不占内存
squares_gen = (i * i for i in range(1000000))
print(f"生成器表达式占用内存:{sys.getsizeof(squares_gen)} bytes")

输出结果:

列表推导式占用内存:8448720 bytes  # 约8MB
生成器表达式占用内存:112 bytes     # 几乎忽略不计

看到了吗?处理100万个数字,生成器只用了112字节的内存!

生成器的使用技巧

# 方式1:直接转换为列表(如果确实需要全部数据)
gen = (i * i for i in range(10))
squares = list(gen)  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# 方式2:逐个处理(内存友好)
gen = (i * i for i in range(10))
for square in gen:
    print(square)

# 方式3:配合其他函数使用
gen = (line.strip() for line in open('large_file.txt'))
word_count = sum(1 for word in gen if word)  # 计算非空行数

实战案例:让代码变得"有灵魂"

案例1:数据清洗

# 原始数据(模拟从API获取的JSON数据)
raw_data = [
    {'name': ' Alice ', 'age': 25, 'salary': '50000'},
    {'name': 'Bob', 'age': None, 'salary': '60000'},
    {'name': 'Charlie', 'age': 30, 'salary': None},
    {'name': '  David  ', 'age': 35, 'salary': '70000'}
]

# ❌ 传统写法(看得想死)
cleaned_data = []
for item in raw_data:
    if item['age'] is not None and item['salary'] is not None:
        cleaned_item = {
            'name': item['name'].strip(),
            'age': item['age'],
            'salary': float(item['salary'])
        }
        cleaned_data.append(cleaned_item)

# ✅ 列表推导式写法
cleaned_data = [
    {
        'name': item['name'].strip(),
        'age': item['age'],
        'salary': float(item['salary'])
    }
    for item in raw_data
    if item['age'] is not None and item['salary'] is not None
]

案例2:文件处理优化

# ❌ 低效的文件处理方式(内存杀手)
def process_large_file_bad(filename):
    with open(filename, 'r') as f:
        lines = f.readlines()  # 一次性读取所有行到内存
    processed = [line.strip().upper() for line in lines if line.strip()]
    return processed

# ✅ 高效的生成器方式(内存友好)
def process_large_file_good(filename):
    with open(filename, 'r') as f:
        return [
            line.strip().upper()
            for line in f  # 逐行读取,内存友好
            if line.strip()
        ]

# 更进一步:完全的生成器方式
def process_large_file_generator(filename):
    with open(filename, 'r') as f:
        for line in f:
            if line.strip():
                yield line.strip().upper()

案例3:复杂业务逻辑

# 场景:计算电商订单的折扣金额
orders = [
    {'id': 1, 'amount': 100, 'level': 'bronze'},
    {'id': 2, 'amount': 200, 'level': 'silver'},
    {'id': 3, 'amount': 300, 'level': 'gold'},
    {'id': 4, 'amount': 150, 'level': 'silver'},
    {'id': 5, 'amount': 500, 'level': 'platinum'}
]

# 复杂的业务逻辑:不同会员等级有不同折扣规则
discount_rules = {
    'bronze': lambda x: x * 0.95,    # 95折
    'silver': lambda x: x * 0.90,    # 9折
    'gold': lambda x: x * 0.85,      # 85折
    'platinum': lambda x: x * 0.80   # 8折
}

# ❌ 命令式写法
result = []
for order in orders:
    if order['amount'] > 100:  # 满100才给折扣
        discount_amount = order['amount'] * (1 - discount_rules[order['level']](order['amount']) / order['amount'])
        result.append({
            'order_id': order['id'],
            'original_amount': order['amount'],
            'discount_amount': discount_amount,
            'final_amount': order['amount'] - discount_amount
        })

# ✅ 列表推导式写法
result = [
    {
        'order_id': order['id'],
        'original_amount': order['amount'],
        'discount_amount': order['amount'] - discount_rules[order['level']](order['amount']),
        'final_amount': discount_rules[order['level']](order['amount'])
    }
    for order in orders
    if order['amount'] > 100
]

性能对比:到底快多少?

import timeit
import sys

# 测试数据大小
n = 1000000

# 传统for循环
def traditional_loop():
    result = []
    for i in range(n):
        if i % 2 == 0:
            result.append(i * i)
    return result

# 列表推导式
def list_comprehension():
    return [i * i for i in range(n) if i % 2 == 0]

# 生成器表达式
def generator_expression():
    return list(i * i for i in range(n) if i % 2 == 0)

# 性能测试
print("性能测试结果:")
print(f"传统for循环: {timeit.timeit(traditional_loop, number=10):.4f}秒")
print(f"列表推导式: {timeit.timeit(list_comprehension, number=10):.4f}秒")
print(f"生成器表达式: {timeit.timeit(generator_expression, number=10):.4f}秒")

典型输出结果:

性能测试结果:
传统for循环: 1.2345秒
列表推导式: 0.8765秒  # 比传统方式快约30%
生成器表达式: 0.9876秒

列表推导式不仅代码更简洁,性能也更好!这是因为Python内部对列表推导式做了优化。

踩坑指南:这些坑我替你踩过了

坑1:过度使用可读性变差

# ❌ 过度复杂的列表推导式(看不懂系列)
result = [(x, y, z) for x in range(10)
                for y in range(10)
                for z in range(10)
                if x + y + z == 15
                and x != y
                and y != z
                and x != z]

# ✅ 拆分成可读性更好的方式
def find_triplets():
    for x in range(10):
        for y in range(10):
            for z in range(10):
                if x + y + z == 15 and len({x, y, z}) == 3:
                    yield (x, y, z)

result = list(find_triplets())

坑2:生成器只能用一次

# ❌ 常见错误
gen = (i * i for i in range(5))
print(list(gen))  # [0, 1, 4, 9, 16]
print(list(gen))  # [] 空列表!生成器已经耗尽了

# ✅ 正确做法
gen = (i * i for i in range(5))
# 如果需要多次使用,先转换成列表
gen_list = list(gen)
print(gen_list)  # 第一次使用
print(gen_list)  # 第二次使用

坑3:副作用陷阱

# ❌ 危险:在推导式中使用有副作用的函数
counter = 0
def count_calls(x):
    global counter
    counter += 1
    return x * 2

result = [count_calls(x) for x in range(10)]
print(f"结果: {result}")
print(f"函数调用次数: {counter}")  # 你猜是几次?

# ✅ 如果需要计数,用传统方式
counter = 0
result = []
for x in range(10):
    result.append(count_calls(x))

坑4:内存使用不当

# ❌ 对大数据使用列表推导式(内存爆炸)
huge_list = [i * i for i in range(100000000)]  # 可能导致内存溢出

# ✅ 对大数据使用生成器表达式
huge_generator = (i * i for i in range(100000000))  # 内存友好

高级技巧:让代码更Pythonic

技巧1:字典推导式和集合推导式

# 字典推导式
words = ['apple', 'banana', 'cherry']
word_lengths = {word: len(word) for word in words}
# 结果: {'apple': 5, 'banana': 6, 'cherry': 6}

# 集合推导式
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
unique_squares = {x * x for x in numbers}
# 结果: {1, 4, 9, 16}

技巧2:walrus运算符(Python 3.8+)

# Python 3.8+ 才支持的写法
import re

text = "电话:138-1234-5678,备用:159-8765-4321"
# ❌ 传统写法
phone_numbers = []
match = re.search(r'(\d{3})-(\d{4})-(\d{4})', text)
if match:
    phone_numbers.append(match.group(0))

# ✅ walrus运算符写法
phone_numbers = [match.group(0)
                for line in [text]
                if (match := re.search(r'(\d{3})-(\d{4})-(\d{4})', line))]

技巧3:函数式编程风格

# 结合map和filter使用
numbers = range(1, 21)

# ❌ 传统写法
even_squares = []
for num in numbers:
    if num % 2 == 0:
        even_squares.append(num ** 2)

# ✅ 函数式写法
even_squares = [num ** 2 for num in numbers if num % 2 == 0]

# 更函数式的写法
even_squares = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers)))

实际项目中的最佳实践

1. 数据处理管道

# 实际项目中常用的数据处理模式
def process_user_data(raw_users):
    """处理用户数据的多步骤管道"""
    return [
        {
            'id': user['id'],
            'name': user['name'].strip().title(),
            'email': user['email'].lower().strip(),
            'age': user['age'] if user['age'] and user['age'] > 0 else None,
            'is_active': user.get('status', 'inactive') == 'active'
        }
        for user in raw_users
        if user.get('email') and '@' in user['email']  # 只处理有效邮箱
    ]

# 使用示例
users = [
    {'id': 1, 'name': '  alice smith  ', 'email': 'ALICE@EXAMPLE.COM', 'age': 25},
    {'id': 2, 'name': 'bob jones', 'email': 'bob@example.com', 'age': -1},
    {'id': 3, 'name': 'invalid', 'email': 'invalid-email', 'age': 30}
]

processed_users = process_user_data(users)

2. 配置文件处理

# 处理配置文件时的常用模式
def load_and_validate_config(config_dict):
    """加载并验证配置"""
    required_keys = ['database', 'api_key', 'timeout']

    return {
        key: config_dict.get(key, get_default_value(key))
        for key in required_keys
        if key in config_dict or get_default_value(key) is not None
    }

def get_default_value(key):
    defaults = {
        'database': 'sqlite:///default.db',
        'api_key': None,
        'timeout': 30
    }
    return defaults.get(key)

3. 日志处理和监控

import logging
from datetime import datetime

def process_log_entries(log_lines):
    """处理日志条目,提取关键信息"""
    return [
        {
            'timestamp': datetime.strptime(entry[:19], '%Y-%m-%d %H:%M:%S'),
            'level': extract_level(entry),
            'message': extract_message(entry),
            'is_error': 'ERROR' in entry,
            'hour': int(entry[11:13])
        }
        for entry in log_lines
        if entry.strip() and not entry.startswith('#')
    ]

总结:何时使用哪种方式?

场景推荐方式原因
小数据量,需要多次访问列表推导式简洁且性能好
大数据量,内存敏感生成器表达式节省内存
复杂的多层嵌套逻辑传统for循环可读性更好
需要调试和中间结果传统for循环容易调试
简单的数据转换列表推导式代码简洁优雅

记住一句话:代码首先是写给人看的,其次才是给机器执行的。

选择合适的方式,既要考虑性能,也要考虑可读性。列表推导式和生成器表达式是Python的瑞士军刀,但不要为了炫技而过度使用。

下次再看到那些丑陋的for循环,你就知道该怎么办了。


你在项目里是怎么用列表推导式的?评论区聊聊,看看有没有更骚的操作。