03-Java工程师的Python第三课-函数与装饰器

4 阅读9分钟

Java方法 vs Python函数:lambda、装饰器、生成器

摘要:Java的方法和Python的函数看似相似,实则差异巨大。Java没有装饰器和生成器的概念,而这两个是Python最有特色的地方。


写在前面

习惯了Java的方法重载、泛型约束、注解等概念,学习Python函数时会感到一种"自由的眩晕"——Python函数没有类型声明、没有访问修饰符、可以返回任意类型、可以赋值给变量、可以作为参数传递……

理解了这些"自由"背后的设计思路,就能理解Python的简洁。


一、函数定义对比

1.1 基本定义

// Java - 必须声明返回类型和参数类型
public int add(int a, int b) {
    return a + b;
}

// Java 10+ 类型推断(局部变量)
var add = (int a, int b) -> a + b;
# Python - 无需类型声明
def add(a, b):
    return a + b

# Python 3.5+ 类型提示(可选)
def add(a: int, b: int) -> int:
    return a + b

1.2 多返回值

// Java - 需要包装成对象或数组
public int[] divide(int a, int b) {
    return new int[]{a / b, a % b};
}

// 使用记录(Java 16+)或自定义类
public record DivisionResult(int quotient, int remainder) {}

public DivisionResult divide(int a, int b) {
    return new DivisionResult(a / b, a % b);
}
# Python - 天然支持多返回值
def divide(a, b):
    return a // b, a % b

quotient, remainder = divide(10, 3)  # 3, 1

二、参数对比

2.1 默认参数

// Java - 不支持默认参数
public void greet(String name, String prefix) {
    // 必须传两个参数
}

// 变通方案:重载
public void greet(String name) {
    greet(name, "Hello");
}
# Python - 支持默认参数
def greet(name, prefix="Hello"):
    print(f"{prefix}, {name}!")

greet("Alice")              # Hello, Alice!
greet("Bob", prefix="Hi")  # Hi, Bob!

2.2 可变参数

// Java - varargs
public int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}

sum(1, 2, 3, 4, 5);
# Python - *args
def sum(*numbers):
    total = 0
    for n in numbers:
        total += n
    return total

sum(1, 2, 3, 4, 5)

2.3 关键字参数

// Java - 没有原生关键字参数概念
public void createUser(String name, int age, String email) {
    // 调用时必须按顺序
}
createUser("Alice", 30, "alice@example.com");
# Python - **kwargs
def create_user(name, age, email):
    print(f"{name}, {age}, {email}")

create_user(name="Alice", age=30, email="alice@example.com")
create_user("Bob", email="bob@example.com", age=25)  # 顺序可以不同

2.4 参数组合对比

场景JavaPython
默认值不支持,需重载def f(x=1):
可变数量int... args*args
关键字参数**kwargs
强制命名参数def f(*, x, y):
# Python 强制命名参数
def greet(*, name, prefix="Hello"):
    print(f"{prefix}, {name}!")

greet(name="Alice")  # 必须用关键字
# greet("Alice")  # TypeError!

三、Lambda表达式

3.1 基本语法

// Java - 箭头函数
Function<Integer, Integer> square = (x) -> x * x;
BinaryOperator<Integer> add = (a, b) -> a + b;

// 带类型(可选)
BiFunction<Integer, Integer, Integer> add = (Integer a, Integer b) -> a + b;
# Python - lambda关键字
square = lambda x: x * x
add = lambda a, b: a + b

# 调用
print(square(5))  # 25
print(add(2, 3))  # 5

3.2 使用场景对比

// Java - 用于函数式接口
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
nums.stream()
    .filter(x -> x % 2 == 0)
    .map(x -> x * x)
    .collect(Collectors.toList());
# Python - 更简洁
nums = [1, 2, 3, 4, 5]
result = [x**2 for x in nums if x % 2 == 0]

# 或用 filter/map(较少用)
result = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, nums)))

3.3 Lambda限制

// Java lambda - 可以有多行语句
Runnable r = () -> {
    System.out.println("Hello");
    System.out.println("World");
};
# Python lambda - 只能是单行表达式
# f = lambda x: (print(x), print(x*2))  # 可以但很不推荐

# 多行逻辑还是用def
def process(x):
    print(x)
    print(x * 2)

四、装饰器(Java没有!)

4.1 什么是装饰器

装饰器是Python最独特的特性之一。它允许你在不修改原函数的情况下,增加额外的功能。

Java对比:Java使用AOP(面向切面编程)和注解来实现类似功能,但语法要复杂得多。

4.2 简单装饰器

// Java - 注解方式(需要Spring AOP等框架)
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("调用前:" + joinPoint.getSignature());
    }
}
# Python - 装饰器实现
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"调用函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"函数执行完毕")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

# 等价于:add = log_decorator(add)

4.3 带参数的装饰器

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")
# 输出: Hello, Alice! 三次

4.4 常用内置装饰器

装饰器Java对应说明
@staticmethodstatic方法静态方法
@classmethod无直接对应类方法,第一个参数是类本身
@propertygetter方法属性访问
@functools.lru_cache缓存结果
class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("半径不能为负")
        self._radius = value

    @classmethod
    def from_diameter(cls, diameter):
        return cls(diameter / 2)

    @staticmethod
    def pi():
        return 3.14159

# 使用
c = Circle(5)
print(c.radius)        # 5 (像属性一样访问)
c.radius = 10          # 10
Circle.from_diameter(20)  # 类方法调用

五、生成器(Java也没有!)

5.1 什么是生成器

生成器是一种特殊的迭代器,用yield关键字来产生值,而不是return。它惰性计算,节省内存。

Java对比:Java用Iterator模式或Stream API实现类似功能,但没有yield这么简洁。

5.2 创建生成器的两种方式

生成器可以通过生成器函数生成器表达式来创建:

# 方式一:生成器函数(使用 yield)
def range_gen(start, end):
    current = start
    while current < end:
        yield current
        current += 1

gen1 = range_gen(5)
print(type(gen1))  # <class 'generator'>

# 方式二:生成器表达式(不使用 yield)
gen2 = (x for x in range(5))
print(type(gen2))  # <class 'generator'>

两种方式本质相同,生成器表达式是生成器函数的语法糖,Python内部会自动将其转换为生成器函数。

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

# 列表推导式 - 一次性生成所有元素到内存
squares = [x*x for x in range(1000000)]  # 占用约8MB内存

# 生成器表达式 - 惰性求值,按需生成
squares_gen = (x*x for x in range(1000000))  # 仅占用约200字节
for sq in squares_gen:
    print(sq)
    if sq > 100:
        break

核心区别

  • 列表推导式在创建时就已分配所有元素的内存
  • 生成器表达式只在迭代时按需计算,不占用额外内存
# 对比:内存占用
import sys
a = [x for x in range(10000)]
b = (x for x in range(10000))
print(sys.getsizeof(a))  # ~87624 字节
print(sys.getsizeof(b))  # ~200 字节左右

5.4 生成器只能迭代一次

gen = (x for x in range(3))
print(list(gen))  # [0, 1, 2]
print(list(gen))  # [] —— 生成器已耗尽,不会重置

5.5 常用场景

# 处理大文件 - 逐行读取,不一次性加载
lines = (line for line in open('big_file.txt', 'r', encoding='utf-8'))
for line in lines:
    if 'ERROR' in line:
        print(line)

# 管道操作 - 多生成器串联
nums = (x for x in range(100))
evens = (x for x in nums if x % 2 == 0)
squares_of_evens = (x*x for x in evens if x > 5)

5.6 生成器 vs Iterator

// Java - 创建Iterator较繁琐
List<String> list = Arrays.asList("a", "b", "c");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}
# Python - for循环自动处理迭代器
my_list = ["a", "b", "c"]
for item in my_list:  # 自动调用 __iter__ 和 __next__
    print(item)

# 手动控制
it = iter(my_list)
print(next(it))  # a
print(next(it))  # b

六、闭包对比

6.1 Java的闭包

// Java - 有效局部变量必须是final或effectively final
int factor = 2;
Function<Integer, Integer> multiplier = (x) -> x * factor;
// factor = 3;  // 编译错误!

6.2 Python的闭包

# Python - 可以修改外层变量(非局部变量)
def multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

times_two = multiplier(2)
times_three = multiplier(3)

print(times_two(5))  # 10
print(times_three(5))  # 15

# Python 3+ nonlocal关键字
def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

七、高阶函数对比

7.1 map/filter/reduce

// Java Stream API
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
    .filter(x -> x % 2 == 0)
    .mapToInt(x -> x * x)
    .sum();

List<String> names = Arrays.asList("alice", "bob", "charlie");
List<String> upper = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
# Python
from functools import reduce

numbers = [1, 2, 3, 4, 5]

# filter
evens = list(filter(lambda x: x % 2 == 0, numbers))

# map
squares = list(map(lambda x: x * x, evens))

# reduce
total = reduce(lambda x, y: x + y, squares, 0)

# Python更喜欢列表推导式
squares = [x*x for x in numbers if x % 2 == 0]
total = sum(x*x for x in numbers if x % 2 == 0)

八、实战:日志装饰器

// Java - 需要注解+切面
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Logged {
}

@Aspect
@Component
public class LoggingAspect {
    @Around("@annotation(Logged)")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed();
        System.out.println(pjp.getSignature() + " 耗时 " + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

// 使用
@Logged
public void myMethod() { }
# Python - 装饰器简单直接
import time
from functools import wraps

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时 {time.time() - start:.2f}s")
        return result
    return wrapper

@logged
def slow_function():
    time.sleep(1)
    return "done"

# 等价于:slow_function = logged(slow_function)

九、Python特有的else用法

Java等语言中,else 只用于 if 语句。但Python中 else 还可以用于循环和异常处理,这是Python特有的语法糖。

9.1 for...else — 循环正常结束时执行

# 查找质数:没有找到因数才是质数
def find_primes(n):
    primes = []
    for i in range(2, n):
        for j in range(2, i):
            if i % j == 0:
                break              # 找到因数,不是质数,跳出
        else:                      # 内层for正常结束(没break)= 质数
            primes.append(i)
    return primes

print(find_primes(20))  # [2, 3, 5, 7, 11, 13, 17, 19]

语义else 里的代码在循环没有执行 break 时才会运行。

9.2 while...else — 条件变为False时执行

count = 0
while count < 3:
    print(count)
    count += 1
else:
    print("循环正常结束")    # count >= 3 时执行

9.3 try...except...else — 没有异常时执行

def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("除数不能为零")
    else:
        print(f"结果: {result}")    # 仅在没有异常时执行
        return result              # 成功时才返回

divide(10, 2)    # 结果: 5.0
divide(10, 0)    # 除数不能为零

适用场景else 适合放"仅在成功时"执行的代码,如资源清理、结果记录等。

9.4 实际应用场景

# 场景:搜索元素
def search_index(items, target):
    for i, item in enumerate(items):
        if item == target:
            print(f"找到了,在索引 {i}")
            break
    else:                               # for循环没有break
        print("没找到")

search_index([1, 3, 5, 7], 5)   # 找到了
search_index([1, 3, 5, 7], 6)   # 没找到

# 场景:文件处理
def read_until_empty():
    try:
        with open('data.txt', 'r', encoding='utf-8') as f:
            return f.readlines()
    except FileNotFoundError:
        print("文件不存在")
    else:
        print(f"成功读取 {len(result)} 行")

9.5 对比总结

语法else 执行时机Java对应
if ... else条件为 Falseif...else
for ... else循环正常结束(未 break)
while ... else条件变为 False
try ... except ... else没有异常时

十、总结

特性JavaPythonPython优势
多返回值需包装对象return a, b简洁
默认参数不支持支持减少重载
Lambda->lambda表达式级别
装饰器AOP+注解@decorator语法简单
生成器Iterator+Streamyield代码简洁
类型提示编译时泛型Type Hint可选且灵活

Python函数设计遵循"给程序员最大自由度"的原则。没有修饰符、没有类型强制,但有约定和工具(Type Hint、wraps等)。装饰器和生成器是Python最独特的功能,值得深入理解。