飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • 第1章 Python简介

    • Python是什么
    • 安装与运行
    • 交互式解释器
    • 注释与编码规范
  • 第2章 变量与数据类型

    • 变量与对象
    • 整数 int
    • 浮点数 float
    • 复数 complex
    • 布尔值 bool
    • 字符串 str
    • 空值 None
    • 类型转换
  • 第3章 运算符与表达式

    • 算术运算符
    • 比较运算符
    • 赋值运算符
    • 逻辑运算符
    • 位运算符
    • 身份与成员运算符
    • 海象运算符
    • 运算符优先级
  • 第4章 流程控制

    • if 语句
    • if-else 语句
    • if-elif-else 语句
    • match-case 语句
    • 条件表达式(三元运算符)
    • while 循环
    • for 循环
    • range 函数
    • break 与 continue
    • 循环的 else 子句
    • pass 语句
  • 第5章 数据结构

    • 列表创建与索引
    • 列表方法
    • 列表推导式
    • 元组
    • 序列解包
    • 集合
    • 字典创建与访问
    • 字典方法
    • 字典推导式
    • range 对象
  • 第6章 函数

    • 定义函数
    • 位置参数与关键字参数
    • 默认参数
    • 可变参数
    • 解包实参
    • 函数返回值
    • lambda 表达式
    • 文档字符串与注解
    • 作用域与命名空间
    • global 与 nonlocal
  • 第7章 模块与包

    • 模块导入
    • 模块搜索路径
    • 包与相对导入
    • 标准库概览
  • 第8章 文件与输入输出

    • 文件读写
    • 上下文管理器
    • 字符串格式化
    • JSON 与 CSV
  • 第9章 面向对象

    • 类与对象
    • 方法
    • 实例变量与类变量
    • 私有变量
    • 继承
    • 多重继承
    • 魔术方法
    • 属性装饰器
    • 数据类 dataclass
  • 第10章 异常处理

    • 语法错误与异常
    • try-except
    • 异常链与 raise
    • 清理操作
    • 自定义异常
  • 第11章 迭代器与生成器

    • 迭代器协议
    • 生成器
    • 生成器表达式
    • 迭代工具
  • 第12章 高级特性

    • 装饰器
    • 函数式编程
  • 第13章 工程实践

    • 测试与调试
    • 代码质量
    • 虚拟环境

装饰器

装饰器是 Python 中用于在不修改原函数定义的前提下扩展功能的高阶函数。@ 符号只是语法糖,它把 func = decorator(func) 的赋值操作提前到函数定义阶段,让代码更整洁。

装饰器的基本原理

装饰器本质上是一个接收函数作为参数并返回函数的 callable。最朴素的实现不用 @ 符号,而是手动包装:

def add_log(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] 调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

def greet(name):
    """向用户问好"""
    return f"你好, {name}"

# 手动包装
greet = add_log(greet)
print(greet("航仔"))  # [LOG] 调用 greet → 你好, 航仔

@ 语法糖把上述赋值自动化。下面的写法与上面完全等价:

@add_log
def greet(name):
    return f"你好, {name}"

functools.wraps 与元信息保留

如果不做处理,装饰器返回的 wrapper 函数会覆盖原函数的元信息(__name__、__doc__、__annotations__ 等),导致调试和文档生成时看到的信息都是 wrapper 的:

def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@bad_decorator
def target():
    """目标函数"""
    pass

print(target.__name__)   # wrapper —— 丢失了原函数名
print(target.__doc__)    # None —— 丢失了文档字符串

functools.wraps 使用 functools.update_wrapper 在内部把原函数的 __module__、__name__、__qualname__、__annotations__、__doc__ 以及 __wrapped__ 等属性复制到 wrapper 上,是编写自定义装饰器的必加项:

import functools

def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@good_decorator
def target():
    """目标函数"""
    pass

print(target.__name__)   # target
print(target.__doc__)    # 目标函数
print(target.__wrapped__)  # <function target ...>  可通过它访问原函数

带参数的装饰器

当装饰器本身需要配置(如重试次数、缓存大小),需要三层嵌套:外层接收配置参数并返回真正的装饰器,中层接收被装饰函数,内层是实际 wrapper:

import time
import functools

def retry(max_attempts=3, delay=1):
    """失败时自动重试的装饰器工厂"""
    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:
                    print(f"第 {attempt} 次失败: {e}")
                    if attempt == max_attempts:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=2, delay=0.1)
def fetch_data(url):
    if "bad" in url:
        raise ConnectionError("连接超时")
    return f"数据: {url}"

print(fetch_data("good_url"))      # 正常返回
# print(fetch_data("bad_url"))     # 重试2次后抛出 ConnectionError

注意 @retry() 必须带括号,即使不传参数也要写 @retry(),否则 decorator 会被当成直接装饰器使用,导致参数传递层级错乱。

类装饰器

装饰器不仅可以是函数,也可以是实现了 __call__ 的类。类装饰器的优势在于可以保存状态、支持继承和更复杂的配置:

import functools

class CountCalls:
    """统计函数被调用次数的装饰器类"""
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 被调用了 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    return "hello"

say_hello()  # say_hello 被调用了 1 次
say_hello()  # say_hello 被调用了 2 次

类装饰器也可以用来装饰类本身,例如自动注册子类、添加类属性或验证接口:

def singleton(cls):
    """单例模式装饰器:确保类只有一个实例"""
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

@singleton
class Database:
    def __init__(self):
        print("初始化数据库连接")

db1 = Database()
db2 = Database()
print(db1 is db2)  # True —— 两次获取的是同一实例

多个装饰器叠加

多个装饰器从上到下书写,执行顺序是从下往上(靠近函数定义的装饰器先执行):

def decorator_a(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("A 之前")
        result = func(*args, **kwargs)
        print("A 之后")
        return result
    return wrapper

def decorator_b(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("B 之前")
        result = func(*args, **kwargs)
        print("B 之后")
        return result
    return wrapper

@decorator_a
@decorator_b
def demo():
    print("原函数")

demo()
# 输出顺序:A 之前 → B 之前 → 原函数 → B 之后 → A 之后

这等价于 demo = decorator_a(decorator_b(demo))。理解这个顺序对调试多层装饰器(如权限检查在最外层、日志在中间、性能计时在最内层)至关重要。

内置装饰器深入

@staticmethod 与 @classmethod

@staticmethod 把方法变成不依赖实例和类的普通函数,不会自动接收 self 或 cls,常用于与类概念相关但不需要访问类状态的辅助函数。@classmethod 的第一个参数是类本身 cls,常用于工厂方法或需要操作类状态的场景:

class Employee:
    _company = "飞翔科技"

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    @staticmethod
    def validate_id(emp_id: str) -> bool:
        """校验工号格式,不依赖任何实例/类数据"""
        return isinstance(emp_id, str) and len(emp_id) == 6

    @classmethod
    def from_dict(cls, data: dict):
        """从字典创建实例,可被子类复用"""
        return cls(data["name"], data["salary"])

    @classmethod
    def set_company(cls, name: str):
        """修改类级别的公司名"""
        cls._company = name

print(Employee.validate_id("FX0001"))  # False —— 长度不足
emp = Employee.from_dict({"name": "航仔", "salary": 18888})

@property

@property 把方法变成只读属性访问,调用时不用加括号。配合 setter 和 deleter 可以实现受控的属性修改和删除:

class Employee:
    def __init__(self, name, base_salary):
        self._name = name
        self._base_salary = base_salary

    @property
    def name(self):
        """只读属性:员工姓名不允许外部修改"""
        return self._name

    @property
    def annual_salary(self):
        """年薪自动计算"""
        return self._base_salary * 12

    @property
    def base_salary(self):
        return self._base_salary

    @base_salary.setter
    def base_salary(self, value):
        if value < 2300:
            raise ValueError("工资不能低于最低工资标准")
        self._base_salary = value

    @base_salary.deleter
    def base_salary(self):
        raise AttributeError("禁止删除工资属性")

emp = Employee("航仔", 18888)
print(emp.annual_salary)   # 226656 —— 像属性一样访问
emp.base_salary = 20000    # 通过 setter 修改
# emp.base_salary = 2000   # ValueError: 工资不能低于最低工资标准
# del emp.base_salary      # AttributeError: 禁止删除工资属性

实际应用场景

权限检查装饰器:在 Web 框架或内部系统中,装饰器是权限控制的经典实现方式:

def require_role(role: str):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get("role") != role:
                raise PermissionError(f"需要 {role} 权限")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin")
def delete_employee(user, emp_id: str):
    return f"已删除员工 {emp_id}"

admin = {"name": "翼王", "role": "admin"}
guest = {"name": "访客", "role": "guest"}
print(delete_employee(admin, "FX001"))
# print(delete_employee(guest, "FX001"))  # PermissionError

缓存装饰器:利用 functools.lru_cache 或自定义字典缓存耗时函数的返回值,避免重复计算:

import functools

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

print(fibonacci(100))  # 瞬间完成,递归结果自动缓存
print(fibonacci.cache_info())  # CacheInfo(hits=98, misses=101, ...)

lru_cache 要求被装饰函数的参数必须是可哈希的(hashable),列表和字典不能直接作为参数传入。maxsize=None 表示无限制缓存,适合参数空间有限的场景。

下一页
函数式编程