3Fluent Python(第2版)

函数即对象

一等公民

函数属性闭包装饰器参数规则

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章:函数即对象

一、导读

1.1 本章学习目标

第3章讲解 Python 中函数作为一等公民的特性:函数属性、闭包、装饰器、参数规则。

本章核心学习目标:

  • 理解函数是一等对象:可以赋值、传递、作为返回值
  • 掌握闭包:函数记住定义时的环境
  • 掌握装饰器:用 @ 语法增强函数行为
  • 理解参数传递规则:位置参数、关键字参数、*args、**kwargs

1.2 为什么本章如此重要

函数是 Python 中最强大的抽象工具。理解闭包和装饰器,能写出更灵活、更模块化的代码。

1.3 核心问题引导

闭包是如何实现的?

装饰器的执行顺序是什么?

*args 和 **kwargs 如何工作?


二、核心概念详解

2.1 函数是一等对象

python
# 函数可以赋值给变量
def greet(name):
    return f"Hello, {name}!"

say_hello = greet
print(say_hello("Alice"))  # Hello, Alice!

# 函数可以作为参数
def call_twice(func, arg):
    return func(func(arg))

print(call_twice(str.upper, "hello"))  # HELLO

# 函数可以有属性
def factorial(n):
    return 1 if n <= 1 else n * factorial(n - 1)

factorial.description = "Compute factorial"
print(factorial.description)  # Compute factorial
print(factorial.__name__)     # factorial

2.2 闭包

python
# 闭包: 函数记住定义时的词法环境
def make_averager():
    series = []
    
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total / len(series)
    
    return averager

avg = make_averager()
print(avg(10))  # 10.0
print(avg(11))  # 10.5
print(avg(12))  # 11.0

# 闭包的本质: averager 引用了外层函数的 series 变量
# 即使 make_averager 已经返回,series 仍然存在

# 使用 nonlocal 修改闭包变量
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c())  # 1
print(c())  # 2

2.3 装饰器

python
import functools
import time

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

def retry(max_attempts=3, delay=1):
    """带参数的装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts - 1:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@timer
def slow_function():
    time.sleep(1)
    return 42

@retry(max_attempts=3, delay=0.5)
def flaky_api_call():
    import random
    if random.random() < 0.5:
        raise ConnectionError("Network error")
    return "success"

2.4 参数规则

python
def func(pos_only, /, pos_or_kw, *, kw_only):
    pass

# pos_only: 只能按位置传递
# pos_or_kw: 可以按位置或关键字传递
# kw_only: 只能按关键字传递

func(1, 2, kw_only=3)      # OK
# func(pos_only=1, ...)    # TypeError
# func(1, 2, 3)            # TypeError (kw_only 必须用关键字)

# *args 和 **kwargs
def flexible(*args, **kwargs):
    print(f"args: {args}")
    print(f"kwargs: {kwargs}")

flexible(1, 2, 3, name="Alice", age=30)
# args: (1, 2, 3)
# kwargs: {'name': 'Alice', 'age': 30}

三、实战案例

3.1 案例一:缓存装饰器

python
def cache(maxsize=128):
    """简单的 LRU 缓存装饰器"""
    def decorator(func):
        from collections import OrderedDict
        _cache = OrderedDict()
        
        @functools.wraps(func)
        def wrapper(*args):
            if args in _cache:
                _cache.move_to_end(args)
                return _cache[args]
            result = func(*args)
            _cache[args] = result
            if len(_cache) > maxsize:
                _cache.popitem(last=False)
            return result
        
        wrapper.cache_clear = _cache.clear
        return wrapper
    return decorator

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

print(fibonacci(100))  # 354224848179261915075 (瞬间完成)

四、本章小结

函数是一等对象,可以赋值、传递、返回

闭包让函数记住定义时的环境

装饰器用 @ 语法增强函数行为

参数规则:位置参数、关键字参数、*args、**kwargs


五、思考与练习

functools.wraps 的作用是什么?

如何实现一个可以带参数或不带参数使用的装饰器?


3/8函数即对
0%