第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__) # factorial2.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()) # 22.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 的作用是什么?
如何实现一个可以带参数或不带参数使用的装饰器?