【Python】第 6 章:函数与作用域

第 6 章:函数与作用域

6.1 函数对象

原理讲解

函数是一等公民(First-class Citizen):

┌─────────────────────────────────────────────────────────┐
│              Python 函数对象                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  函数在 Python 中是完全的对象:                          │
│  - 可以赋值给变量                                       │
│  - 可以作为参数传递                                     │
│  - 可以作为返回值                                       │
│  - 可以存储在数据结构中                                 │
│                                                         │
│  函数对象属性:                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │ __code__      → 代码对象 (字节码)                │   │
│  │ __globals__   → 全局命名空间                     │   │
│  │ __defaults__  → 默认参数                         │   │
│  │ __kwdefaults__ → 关键字默认参数                  │   │
│  │ __closure__   → 闭包单元格                       │   │
│  │ __dict__      → 函数属性                         │   │
│  │ __name__      → 函数名                           │   │
│  │ __doc__       → 文档字符串                       │   │
│  │ __module__    → 模块名                           │   │
│  │ __annotations__ → 类型注解                       │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

闭包原理

闭包(Closure):函数 + 环境

┌─────────────────────────────────────────────────────────┐
│                  闭包工作原理                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  def make_multiplier(n):           # 外层函数           │
│      def multiplier(x):            # 内层函数           │
│          return x * n              # 引用外层变量        │
│      return multiplier             # 返回内层函数        │
│                                                         │
│  内存布局:                                             │
│  ┌─────────────────────────────────────────────────┐   │
│  │ make_multiplier 调用时:                         │   │
│  │ ┌─────────────────────────────────────────┐     │   │
│  │ │ 栈帧:n = 5                              │     │   │
│  │ │ ┌─────────────────────────────────┐     │     │   │
│  │ │ │ multiplier 函数对象              │     │     │   │
│  │ │ │ __closure__ → [cell(n=5)]       │     │     │   │
│  │ │ └─────────────────────────────────┘     │     │   │
│  │ └─────────────────────────────────────────┘     │   │
│  │         ↓ 返回                                  │   │
│  │ ┌─────────────────────────────────────────┐     │   │
│  │ │ times5 = multiplier (带环境 n=5)        │     │   │
│  │ └─────────────────────────────────────────┘     │   │
│  │                                                 │   │
│  │ 即使 make_multiplier 返回,n 仍然被保留!        │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

闭包的单元格(Cell)结构:

// CPython 源码
typedef struct {
    PyObject_HEAD
    PyObject *ob_ref;  // 引用的对象
} PyCellObject;

查看闭包信息:

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

times5 = make_multiplier(5)

# 查看闭包
print(f"闭包内容:{times5.__closure__}")
print(f"单元格:{times5.__closure__[0]}")
print(f"单元格值:{times5.__closure__[0].cell_contents}")

# 查看代码对象的自由变量
print(f"自由变量:{times5.__code__.co_freevars}")

closurecode

def outer(x):
    def inner(y):
        return x + y
    return inner

closure_func = outer(10)

# __closure__ - 闭包变量
print("__closure__:", closure_func.__closure__)
# (<cell at 0x...: int object at 0x...>,)

# __code__ - 代码对象
code = closure_func.__code__
print("\n__code__ 属性:")
print(f"  co_varnames: {code.co_varnames}")      # 局部变量
print(f"  co_freevars: {code.co_freevars}")      # 自由变量
print(f"  co_cellvars: {code.co_cellvars}")      # 单元格变量
print(f"  co_argcount: {code.co_argcount}")      # 参数数量

6.2 作用域规则

LEGB 规则

Python 的作用域查找顺序:

┌─────────────────────────────────────────────────────────┐
│              LEGB 作用域规则                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  L - Local (局部作用域)                                 │
│  │ 当前函数内部的变量                                   │
│  │                                                      │
│  E - Enclosing (外层函数作用域)                         │
│  │ 嵌套函数中外层函数的变量                             │
│  │                                                      │
│  G - Global (全局作用域)                                │
│  │ 模块级别的变量                                       │
│  │                                                      │
│  B - Built-in (内置作用域)                              │
│  │ Python 内置名称 (len, print, etc.)                  │
│  │                                                      │
│  查找顺序:L → E → G → B                                │
│                                                         │
│  示例:                                                 │
│  ┌─────────────────────────────────────────────────┐   │
│  │ x = "global"           # G                       │   │
│  │                                                 │   │
│  │ def outer():                                    │   │
│  │     x = "enclosing"     # E                      │   │
│  │     def inner():                                │   │
│  │         x = "local"     # L                      │   │
│  │         print(x)        # 输出:local            │   │
│  │     inner()                                     │   │
│  │                                                 │   │
│  │ outer()                                         │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

global 和 nonlocal

# global: 修改全局变量
count = 0

def increment():
    global count
    count += 1

increment()
print(count)  # 1

# nonlocal: 修改外层函数变量
def make_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment

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

作用域链图示:

┌─────────────────────────────────────────────────────────┐
│                  作用域链示例                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  x = "G"  # 全局                                        │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │ def outer():                                    │   │
│  │     x = "E"  # 外层                             │   │
│  │                                                 │   │
│  │     ┌─────────────────────────────────────┐     │   │
│  │     │ def inner():                        │     │   │
│  │     │     x = "L"  # 局部                 │     │   │
│  │     │     print(x)  # L → 找到,停止      │     │   │
│  │     └─────────────────────────────────────┘     │   │
│  │                                                 │   │
│  │     ┌─────────────────────────────────────┐     │   │
│  │     │ def inner2():                       │     │   │
│  │     │     print(x)  # L(无) → E → 找到    │     │   │
│  │     └─────────────────────────────────────┘     │   │
│  │                                                 │   │
│  │     ┌─────────────────────────────────────┐     │   │
│  │     │ def inner3():                       │     │   │
│  │     │     print(y)  # L(无) → E(无) → G   │     │   │
│  │     │               → B(无) → NameError!  │     │   │
│  │     └─────────────────────────────────────┘     │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

6.3 装饰器原理

装饰器的本质

装饰器 = 高阶函数

┌─────────────────────────────────────────────────────────┐
│              装饰器工作原理                              │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  语法糖:                                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │ @decorator                                     │   │
│  │ def func():                                    │   │
│  │     pass                                       │   │
│  │                                                 │   │
│  │ 等价于:                                        │   │
│  │ def func():                                    │   │
│  │     pass                                       │   │
│  │ func = decorator(func)                         │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  装饰器执行时机:                                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │ 1. 模块加载时(函数定义时)                      │   │
│  │    @decorator                                   │   │
│  │    def func(): ...  ← decorator(func) 立即执行  │   │
│  │                                                 │   │
│  │ 2. 函数调用时                                    │   │
│  │    func()  ← 调用的是装饰后的函数                │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

装饰器栈

多个装饰器的执行顺序:

@decorator_a
@decorator_b
@decorator_c
def func():
    pass

# 等价于
func = decorator_a(decorator_b(decorator_c(func)))

# 执行顺序(从下到上):
# 1. decorator_c(func)
# 2. decorator_b(result_c)
# 3. decorator_a(result_b)

# 调用顺序(从上到下):
# func() → decorator_a 的 wrapper → decorator_b 的 wrapper → decorator_c 的 wrapper → func

装饰器栈图示:

┌─────────────────────────────────────────────────────────┐
│                  装饰器栈                                │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  定义时(从内到外):                                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │ func                                            │   │
│  │  ↓                                              │   │
│  │ decorator_c(func)                               │   │
│  │  ↓                                              │   │
│  │ decorator_b(decorator_c(func))                  │   │
│  │  ↓                                              │   │
│  │ decorator_a(decorator_b(decorator_c(func)))     │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  调用时(从外到内):                                   │
│  ┌─────────────────────────────────────────────────┐   │
│  │ decorator_a wrapper                             │   │
│  │     ↓                                           │   │
│  │     decorator_b wrapper                         │   │
│  │         ↓                                       │   │
│  │         decorator_c wrapper                     │   │
│  │             ↓                                   │   │
│  │             原始 func                           │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

functools.wraps

为什么需要 wraps:

from functools import wraps

# 不使用 wraps
def decorator_no_wraps(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator_no_wraps
def my_func():
    """这是 my_func 的文档"""
    pass

print(my_func.__name__)  # 'wrapper' (错误!)
print(my_func.__doc__)   # None (错误!)

# 使用 wraps
def decorator_with_wraps(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator_with_wraps
def my_func2():
    """这是 my_func2 的文档"""
    pass

print(my_func2.__name__)  # 'my_func2' (正确!)
print(my_func2.__doc__)   # '这是 my_func2 的文档' (正确!)

wraps 做了什么:

# functools.wraps 等价于
def wraps(func):
    def decorator(wrapper):
        wrapper.__name__ = func.__name__
        wrapper.__doc__ = func.__doc__
        wrapper.__module__ = func.__module__
        wrapper.__qualname__ = func.__qualname__
        wrapper.__annotations__ = func.__annotations__
        wrapper.__wrapped__ = func
        wrapper.__dict__.update(func.__dict__)
        return wrapper
    return decorator

6.4 实践:手写装饰器

实验代码

# examples/chapter-06/decorator_examples.py
import time
import functools
from typing import Callable, Any

print("=" * 70)
print("装饰器实践示例")
print("=" * 70)

# ========== 1. 基础装饰器 ==========
print("\n【示例 1】基础装饰器 - 计时器")
print("-" * 50)

def timer(func: Callable) -> Callable:
    """计算函数执行时间的装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} 执行时间:{end - start:.6f} 秒")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(0.1)

slow_function()

# ========== 2. 带参数的装饰器 ==========
print("\n【示例 2】带参数的装饰器 - 重试机制")
print("-" * 50)

def retry(max_attempts: int = 3, delay: float = 1.0):
    """失败时重试的装饰器"""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"尝试 {attempt + 1}/{max_attempts} 失败:{e}")
                    if attempt < max_attempts - 1:
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.1)
def might_fail():
    import random
    if random.random() < 0.7:
        raise ValueError("随机失败")
    return "成功!"

try:
    result = might_fail()
    print(f"结果:{result}")
except Exception as e:
    print(f"最终失败:{e}")

# ========== 3. 类装饰器 ==========
print("\n【示例 3】类装饰器 - 单例模式")
print("-" * 50)

class Singleton:
    """单例模式装饰器"""
    def __init__(self, cls):
        self.cls = cls
        self.instance = None
        functools.update_wrapper(self, cls)
    
    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = self.cls(*args, **kwargs)
        return self.instance

@Singleton
class Database:
    def __init__(self):
        print("数据库连接创建")
    
    def query(self, sql):
        return f"执行:{sql}"

db1 = Database()
db2 = Database()
print(f"db1 is db2: {db1 is db2}")  # True

# ========== 4. 装饰器链 ==========
print("\n【示例 4】装饰器链 - 多重装饰")
print("-" * 50)

def log_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

def validate_types(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        for i, arg in enumerate(args):
            if not isinstance(arg, (int, float)):
                raise TypeError(f"参数 {i} 类型错误:{type(arg)}")
        return func(*args, **kwargs)
    return wrapper

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

print(f"结果:{add(1, 2)}")

# ========== 5. 查看装饰器效果 ==========
print("\n【示例 5】装饰器 introspection")
print("-" * 50)

@timer
def decorated_func():
    """这是一个被装饰的函数"""
    pass

print(f"函数名:{decorated_func.__name__}")
print(f"文档:{decorated_func.__doc__}")
print(f"原始函数:{decorated_func.__wrapped__}")

实验练习

练习 1:实现缓存装饰器

import functools

def memoize(func):
    """缓存函数结果的装饰器"""
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    
    # 暴露缓存信息
    wrapper.cache_info = lambda: f"缓存大小:{len(cache)}"
    wrapper.cache_clear = lambda: cache.clear()
    
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# 测试
print(f"fib(10) = {fibonacci(10)}")
print(fibonacci.cache_info())
print(f"fib(20) = {fibonacci(20)}")
print(fibonacci.cache_info())

练习 2:实现权限检查装饰器

from functools import wraps

class User:
    def __init__(self, name, role):
        self.name = name
        self.role = role

_current_user = None

def set_current_user(user):
    global _current_user
    _current_user = user

def require_role(required_role):
    """需要特定角色才能访问的装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if _current_user is None:
                raise PermissionError("未登录")
            if _current_user.role != required_role:
                raise PermissionError(f"需要 {required_role} 角色")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_user(user_id):
    return f"删除用户 {user_id}"

# 测试
set_current_user(User("Alice", "user"))
try:
    delete_user(1)
except PermissionError as e:
    print(f"权限错误:{e}")

set_current_user(User("Bob", "admin"))
print(delete_user(1))

练习 3:实现异步装饰器

import asyncio
import time
from functools import wraps

def async_timer(func):
    """异步函数计时装饰器"""
    @wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = await func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} 执行时间:{end - start:.6f} 秒")
        return result
    return wrapper

@async_timer
async def async_sleep(seconds):
    await asyncio.sleep(seconds)
    return "完成"

# 运行
asyncio.run(async_sleep(0.5))

常见问题

Q1: 装饰器会影响性能吗?

A: 会,但通常可忽略:

  • 每次调用增加一层函数调用
  • 复杂装饰器(如重试、缓存)影响更大
  • 热点代码慎用装饰器

Q2: 如何调试被装饰的函数?

A:

# 使用 __wrapped__ 访问原始函数
decorated_func.__wrapped__()

# 使用 functools.wraps 保留元数据
# 调试器可以看到原始函数名和文档

Q3: 装饰器能否带多个参数?

A:

@decorator(arg1, arg2, kwarg1=value)
def func():
    pass

# 装饰器工厂模式
def decorator(arg1, arg2, kwarg1=default):
    def actual_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 使用 arg1, arg2, kwarg1
            return func(*args, **kwargs)
        return wrapper
    return actual_decorator

Q4: 类方法如何装饰?

A:

class MyClass:
    @classmethod
    @decorator
    def class_method(cls):
        pass
    
    @staticmethod
    @decorator
    def static_method():
        pass
    
    @property
    @decorator
    def prop(self):
        pass

# 注意装饰器顺序!@classmethod/@staticmethod 应在最内层

Q5: 如何创建可配置的装饰器类?

A:

class DecoratorClass:
    def __init__(self, config_option):
        self.config_option = config_option
    
    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 使用 self.config_option
            return func(*args, **kwargs)
        return wrapper

@DecoratorClass(config_option="value")
def func():
    pass

本章小结

  • 函数是完全的对象,有属性和方法
  • 闭包 = 函数 + 环境,通过__closure__访问
  • LEGB 规则决定变量查找顺序
  • global 修改全局变量,nonlocal 修改外层变量
  • 装饰器是高阶函数,在定义时执行
  • functools.wraps 保留原函数元数据
  • 装饰器可以链式使用,从内到外执行

下一章预告

第 7 章将深入探讨 生成器与协程,包括迭代器协议、yield 原理和 async/await。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值