【Day13 Java转Python】装饰器、生成器与lambda——Python的函数式“三件套”

Java老兵看到Python里的@decoratoryieldlambda,可能会一脸懵:"这都什么黑魔法?"

别慌,这些正是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不行)。
典型场景 :配合mapfiltersorted等内置函数。

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中测试通过。如果觉得有收获,请点赞、收藏、转发,你的支持是我持续输出的动力!)

相关推荐
大气层煮月亮2 小时前
ERP-Agent 记忆系统优化方案
java·大数据·elasticsearch
石榴树下的七彩鱼2 小时前
Python OCR 文字识别 API 接入完整教程
开发语言·人工智能·后端·python·ocr·api·图片识别
会飞的胖达喵2 小时前
基于qt开发的RedisDesk
开发语言·qt
信看2 小时前
看所有网卡参数,确认 RM520N-GL 网卡
开发语言·python
qq_189807032 小时前
c++怎么解决ifstream在读取UTF-16文件时的乱码_imbue用法【避坑】
jvm·数据库·python
不过如此19512 小时前
pyinstaller打包GUI项目实践
windows·python·ui
油炸自行车2 小时前
【Qt】运行 `windeployqt.exe` 打包Qt发布包,遇到警告的解决方法 (Warning: Cannot find any.....)
开发语言·qt·vs·打包·windeployqt·软件部署
yu85939582 小时前
C++ 虚拟磁盘与虚拟光驱实现
开发语言·c++
青苔猿猿2 小时前
【3】jupyter单元格Cell操作
python·jupyter·单元格