Java老兵看到Python里的
@decorator、yield和lambda,可能会一脸懵:"这都什么黑魔法?"别慌,这些正是Python函数式编程的精华。Java 8+虽然也引入了lambda和Stream,但Python早在十年前就把这些玩得炉火纯青。今天我们就来拆解这"三件套",看看它们如何让代码更简洁、更Pythonic。
1. Lambda:匿名函数的"一句话哲学"
Java的Lambda
java
// Java: 需要定义函数式接口,语法稍显啰嗦
List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
Python的Lambda
python
# Python: lambda 直接表达式,更轻量
nums = [1,2,3,4]
even_nums = list(filter(lambda x: x % 2 == 0, nums))
print(even_nums) # [2,4]
语法 :lambda 参数: 返回值
限制 :只能写单行表达式,不能包含语句(如if可以用三元表达式,但for不行)。
典型场景 :配合map、filter、sorted等内置函数。
python
# 按字符串长度排序
words = ["apple", "banana", "cherry", "date"]
sorted_words = sorted(words, key=lambda s: len(s))
print(sorted_words) # ['date', 'apple', 'banana', 'cherry']
Java对比 :Java的lambda需要依赖函数式接口,语法上
->和Python的:各有千秋,但Python的lambda更简单,没有checked exception的烦恼。
2. 生成器(Generator):懒人编程的利器
当你需要一个可以迭代的序列,但不想一次性把所有元素都放进内存(比如读取10GB的日志文件),生成器就是救星。
创建生成器的两种方式
方式一:生成器表达式(圆括号)
python
# 列表推导式:立即生成所有平方数,占用内存
squares_list = [x*x for x in range(100000000)] # 可能爆内存
# 生成器表达式:惰性求值,不占内存
squares_gen = (x*x for x in range(100000000))
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
# 可以for循环,但只能迭代一次
方式二:yield关键字(生成器函数)
python
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
for num in fibonacci(10):
print(num, end=" ") # 0 1 1 2 3 5 8 13 21 34
yield的作用 :函数执行到yield会返回一个值,并暂停 ,下次调用next()时从暂停处继续。这就是协程的雏形。
Java对比
Java 8的Stream也是惰性的,但需要手动调用终端操作(如collect)。Python的生成器更接近底层的Iterator,但语法更友好。Java中要写一个惰性序列,通常需要实现Iterator接口,代码量多好几倍。
3. 装饰器(Decorator):给函数穿上"外套"
装饰器是Python最标志性的语法糖。它允许你在不修改函数本身的情况下,给函数添加额外功能(日志、计时、缓存、权限校验等)。
基础装饰器:计时器
python
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 耗时: {end-start:.4f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "done"
slow_function() # 输出: slow_function 耗时: 1.0002秒
原理 :@timer 等价于 slow_function = timer(slow_function)。timer返回一个wrapper函数,替换原来的函数。
带参数的装饰器
python
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}")
greet("Alice") # 打印三次
Java对比
Java没有内置的装饰器语法,类似功能可以通过注解+AOP(如Spring的@Around)实现,但配置复杂,学习曲线陡峭。Python的装饰器是语言原生的,理解起来简单,用起来爽。
4. 三件套的梦幻联动:实战练习
题目 :写一个@cache装饰器,用于缓存函数的返回值(类似functools.lru_cache)。然后结合生成器,写一个读取超大CSV文件的函数,只返回指定列,并使用lambda过滤行。最后测试缓存效果。
参考代码(可直接运行)
python
import time
from functools import wraps
# 1. 手写缓存装饰器(简化版,不考虑最大容量)
def cache(func):
memo = {}
@wraps(func) # 保留原函数的元数据(如__name__)
def wrapper(*args, **kwargs):
# 将参数转为可哈希的键
key = (args, tuple(sorted(kwargs.items())))
if key not in memo:
print(f"计算 {func.__name__}{args}")
memo[key] = func(*args, **kwargs)
else:
print(f"缓存命中 {func.__name__}{args}")
return memo[key]
return wrapper
@cache
def expensive_square(x):
time.sleep(1) # 模拟耗时计算
return x * x
# 测试缓存
print(expensive_square(4)) # 计算,等待1秒 → 16
print(expensive_square(4)) # 缓存命中,立即 → 16
print(expensive_square(5)) # 计算,等待1秒 → 25
# 2. 生成器 + lambda:处理大文件
def read_csv_columns(file_path, column_indices):
"""生成器,逐行读取CSV,只返回指定列索引的值(元组)"""
with open(file_path, 'r') as f:
for line in f:
parts = line.strip().split(',')
# 如果列索引超出范围,忽略该行(或抛出异常)
try:
cols = [parts[i] for i in column_indices]
yield tuple(cols)
except IndexError:
continue # 跳过格式错误的行
# 模拟创建一个CSV文件内容
import io
fake_csv = io.StringIO("name,age,city\nAlice,25,Beijing\nBob,30,Shanghai\nCharlie,22,Guangzhou\n")
# 使用生成器,并用lambda过滤年龄大于25的行
def filter_by_age(rows, min_age):
for row in rows:
# row: (name, age, city) 我们只取了列索引0,1,2? 注意上面的column_indices
# 这里假设我们只取name和age,所以row是(name, age)
pass
# 更完整的示例:
def process_large_csv():
# 假设真实文件很大,我们模拟StringIO
data = io.StringIO("name,age,city\nAlice,25,Beijing\nBob,30,Shanghai\nCharlie,22,Guangzhou\nDavid,35,Shenzhen")
# 只取第1列(name)和第2列(age),索引从0开始
rows = read_csv_columns(data, [0, 1]) # 注意:第一行是标题,但我们的生成器没有跳过标题,可以改进
# 跳过标题:调用一次next
try:
next(rows) # 跳过标题行
except StopIteration:
pass
# 过滤年龄大于25的,使用lambda
filtered = filter(lambda row: int(row[1]) > 25, rows)
for name, age in filtered:
print(f"{name} is {age} years old.")
process_large_csv()
# 输出:
# Bob is 30 years old.
# David is 35 years old.
解释:
@cache装饰器利用了闭包和字典存储结果。@wraps来自functools,用于复制原函数的__name__等属性,否则expensive_square.__name__会变成'wrapper'。read_csv_columns是一个生成器函数,yield一行处理结果,内存友好。filter函数配合lambda实现了过滤逻辑。
5. 总结对比
| 特性 | Java | Python |
|---|---|---|
| Lambda | (x) -> x*x,需要函数式接口 |
lambda x: x*x,更轻量 |
| 生成器 | 需手动实现Iterator,代码臃肿 |
yield关键字,优雅简洁 |
| 装饰器 | 注解+AOP(如Spring),重如泰山 | 语法级支持,轻如鸿毛 |
| 惰性求值 | Stream API,但需终端操作 | 生成器/迭代器协议,原生惰性 |
6. 今日挑战
写一个装饰器@retry(max_attempts=3, delay=1),当被装饰的函数抛出异常时,自动重试,最多重试max_attempts次,每次重试间隔delay秒。如果所有尝试都失败,则抛出最后一次异常。然后在某个可能失败的函数(比如随机失败)上测试。
提示 :使用time.sleep(delay),并用for循环尝试。
欢迎评论区贴代码!
下篇预告:Day 14 我们将进行实战项目:命令行工具,综合运用14天所学,写一个实用的脚本(比如文件批量重命名、待办事项CLI)。
(本文代码基于Python 3.14,在VSCode中测试通过。如果觉得有收获,请点赞、收藏、转发,你的支持是我持续输出的动力!)