🐍 第04课:函数

—— 代码复用的艺术

🏆 参数/返回/lambda/装饰器
✅ Python验证通过

📌 本课目标

1️⃣ 函数基础

# 定义函数
def greet(name: str) -> str:
    """打招呼函数(docstring)"""
    return f"你好,{name}!"

# 调用
msg = greet("张三")
print(msg)  # 你好,张三!

# 多返回值(实际返回元组)
def min_max(numbers):
    return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 4, 1, 5])
print(f"最小={lo}, 最大={hi}")  # 最小=1, 最大=5

2️⃣ 参数类型全解

# 1. 位置参数
def power(base, exp):
    return base ** exp
print(power(2, 10))  # 1024

# 2. 关键字参数
print(power(exp=10, base=2))  # 1024 顺序无关

# 3. 默认参数
def connect(host, port=3306, timeout=30):
    return f"{host}:{port} (超时{timeout}s)"
print(connect("localhost"))            # localhost:3306 (超时30s)
print(connect("db.com", port=5432))   # db.com:5432 (超时30s)

# 4. 可变位置参数 *args
def total(*numbers):
    return sum(numbers)
print(total(1, 2, 3, 4))  # 10

# 5. 可变关键字参数 **kwargs
def make_profile(**info):
    for k, v in info.items():
        print(f"  {k}: {v}")
make_profile(name="张三", age=28, city="北京")

# 6. 仅位置参数 (/) 和仅关键字参数 (*)
def func(a, b, /, c, d, *, e, f):
    """
    a, b: 仅位置参数(不能用作关键字)
    c, d: 位置或关键字
    e, f: 仅关键字参数(必须用关键字)
    """
    return a + b + c + d + e + f

print(func(1, 2, 3, 4, e=5, f=6))  # 21
⚠️ 默认参数陷阱:不要用可变对象(list、dict)作为默认值!
# ❌ 错误写法
def append_to(item, lst=[]):
    lst.append(item)
    return lst

# ✅ 正确写法
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

3️⃣ Lambda 与高阶函数

# lambda: 匿名函数
square = lambda x: x ** 2
print(square(5))  # 25

# 排序中使用 lambda
students = [("张三", 85), ("李四", 92), ("王五", 78)]
students.sort(key=lambda s: s[1], reverse=True)
print(students)  # [('李四', 92), ('张三', 85), ('王五', 78)]

# map: 映射
nums = [1, 2, 3, 4]
squares = list(map(lambda x: x**2, nums))
print(squares)  # [1, 4, 9, 16]

# filter: 过滤
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)  # [2, 4]

# reduce: 累积
from functools import reduce
product = reduce(lambda a, b: a * b, nums)
print(product)  # 24

# sorted: 返回新列表(不修改原列表)
sorted_nums = sorted([3, 1, 4, 1, 5])
print(sorted_nums)  # [1, 1, 3, 4, 5]

4️⃣ 装饰器(Decorator)

装饰器是 Python 最强大的特性之一——在不修改函数代码的情况下,增强函数功能。

基础装饰器

import time
import functools

def timer(func):
    """计时装饰器:测量函数执行时间"""
    @functools.wraps(func)  # 保留原函数元信息
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"[{func.__name__}] 耗时: {elapsed:.4f}s")
        return result
    return wrapper

@timer
def slow_sum(n):
    """计算1到n的和"""
    return sum(range(n + 1))

result = slow_sum(1000000)
# [slow_sum] 耗时: 0.0234s

带参数的装饰器

def retry(max_attempts=3, delay=1):
    """重试装饰器:失败自动重试"""
    import time
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"第{attempt}次失败: {e},{delay}s后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_api():
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络错误")
    return "成功"

常用装饰器模式

# 缓存装饰器
from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # 瞬间出结果!

# 类型检查装饰器
def typecheck(**expected):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(**kwargs):
            for name, typ in expected.items():
                if name in kwargs and not isinstance(kwargs[name], typ):
                    raise TypeError(
                        f"{name} 应为 {typ.__name__},实际为 {type(kwargs[name]).__name__}"
                    )
            return func(**kwargs)
        return wrapper
    return decorator

@typecheck(name=str, age=int)
def register(name, age):
    return f"{name}, {age}岁 已注册"

5️⃣ 作用域与闭包

# LEGB 规则: Local → Enclosing → Global → Built-in
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)    # local
    inner()
    print(x)        # enclosing

outer()
print(x)            # global

# 闭包: 函数记住外部变量
def make_counter(start=0):
    count = start
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter(10)
print(c())  # 11
print(c())  # 12
print(c())  # 13

6️⃣ 验证脚本

#!/usr/bin/env python3
"""第04课 函数验证"""
import functools
import time

def test_params():
    """参数类型测试"""
    def power(base, exp=2):
        return base ** exp
    assert power(3) == 9
    assert power(2, 10) == 1024
    assert power(exp=3, base=2) == 8

    def total(*nums):
        return sum(nums)
    assert total(1, 2, 3) == 6

    def build(**kw):
        return kw
    assert build(a=1, b=2) == {"a": 1, "b": 2}
    print("✅ 参数类型测试通过")

def test_return():
    """返回值测试"""
    def min_max(nums):
        return min(nums), max(nums)
    lo, hi = min_max([3, 1, 4])
    assert lo == 1 and hi == 4
    print("✅ 返回值测试通过")

def test_lambda():
    """Lambda测试"""
    nums = [3, 1, 4, 1, 5]
    assert sorted(nums) == [1, 1, 3, 4, 5]

    squares = list(map(lambda x: x**2, [1,2,3]))
    assert squares == [1, 4, 9]

    evens = list(filter(lambda x: x%2==0, range(10)))
    assert evens == [0, 2, 4, 6, 8]

    from functools import reduce
    product = reduce(lambda a,b: a*b, [1,2,3,4])
    assert product == 24
    print("✅ Lambda测试通过")

def test_decorator():
    """装饰器测试"""
    def timer(func):
        @functools.wraps(func)
        def wrapper(*a, **kw):
            start = time.perf_counter()
            result = func(*a, **kw)
            elapsed = time.perf_counter() - start
            return result, elapsed
        return wrapper

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

    result, elapsed = add(3, 4)
    assert result == 7
    assert elapsed >= 0
    assert add.__name__ == "add"  # functools.wraps 保留函数名
    print("✅ 装饰器测试通过")

def test_closure():
    """闭包测试"""
    def make_counter(start=0):
        count = start
        def counter():
            nonlocal count
            count += 1
            return count
        return counter

    c = make_counter(10)
    assert c() == 11
    assert c() == 12
    assert c() == 13
    print("✅ 闭包测试通过")

def test_lru_cache():
    """缓存装饰器测试"""
    from functools import lru_cache

    call_count = 0
    @lru_cache(maxsize=100)
    def fib(n):
        nonlocal call_count
        call_count += 1
        if n < 2:
            return n
        return fib(n-1) + fib(n-2)

    assert fib(30) == 832040
    print("✅ LRU缓存测试通过")

if __name__ == "__main__":
    test_params()
    test_return()
    test_lambda()
    test_decorator()
    test_closure()
    test_lru_cache()
    print("\n🎉 第04课全部验证通过!")
✅ 参数类型测试通过 ✅ 返回值测试通过 ✅ Lambda测试通过 ✅ 装饰器测试通过 ✅ 闭包测试通过 ✅ LRU缓存测试通过 🎉 第04课全部验证通过!

🔑 本课要点

  1. *args / **kwargs——可变参数,让函数更灵活
  2. 仅位置参数 /仅关键字参数 *——Python 3.8+ 参数约束
  3. 默认参数用 None——避免可变默认值陷阱
  4. 装饰器 = 语法糖——@timer 等价于 func = timer(func)
  5. functools.wraps——装饰器必备,保留原函数元信息
  6. lru_cache——一键加缓存,递归性能神器