你有没有遇到过这种情况:
# 面试官问:把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循环,你就知道该怎么办了。
你在项目里是怎么用列表推导式的?评论区聊聊,看看有没有更骚的操作。