DAY33 类的装饰器

|---|------------------------|
| | 知识点回顾 |
| | 1.类的装饰器 |
| | 2. 装饰器思想的进一步理解:外部修改、动态 |
| | 3. 类方法的定义:内部定义和外部定义 |

零基础 Python 学习:类的装饰器、装饰器思想、类方法定义

作为你的老师,我会从前置知识铺垫开始 ,把每个知识点拆成小模块,用 "大白话 + 分步代码" 的方式讲解,确保你能听懂。首先明确:学习这些知识点需要先掌握函数基础、类的基础、简单的函数装饰器,我会先快速回顾这些前置内容,再进入核心知识点。


前置知识快速回顾(零基础必看)

在学新内容前,先记住 3 个基础概念,用最简单的例子说明:

1. 函数的基本定义与调用

函数是 "实现特定功能的代码块",可以重复使用。

python 复制代码
# 定义函数:打印问候语
def say_hello():
    print("你好,我是零基础学习者!")

# 调用函数
say_hello()  # 输出:你好,我是零基础学习者!
2. 类的基本定义与实例化

类是 "对象的模板",比如 "人" 这个类,可以定义人的属性(名字、年龄)和行为(说话、走路)。

python 复制代码
# 定义类
class Person:
    # 初始化方法:创建实例时给属性赋值
    def __init__(self, name):
        self.name = name  # 实例属性:每个实例的名字不同
    
    # 实例方法:类的行为(第一个参数必须是self,代表实例本身)
    def say_name(self):
        print(f"我的名字是{self.name}")

# 实例化:用类创建具体的对象(实例)
p = Person("小明")  # 创建一个叫"小明"的人
p.say_name()  # 调用实例方法,输出:我的名字是小明
3. 函数装饰器的简单理解(核心铺垫)

装饰器可以理解为 "给函数 / 类穿的'外套'":不修改原函数 / 类的代码,却能给它增加新功能。比如给函数加一个 "计时功能",看函数运行了多久:

python 复制代码
# 定义装饰器函数:计算函数运行时间
def timer_decorator(func):  # func是被装饰的函数
    # 包装函数:实现新增功能
    def wrapper():
        import time
        start = time.time()  # 记录开始时间
        func()  # 执行原函数
        end = time.time()  # 记录结束时间
        print(f"函数运行了{end - start:.6f}秒")  # 新增功能:打印运行时间
    return wrapper  # 返回包装函数

# 使用装饰器:@+装饰器名,放在函数定义上方
@timer_decorator
def say_hello():
    print("你好呀!")

# 调用函数(看似调用原函数,实际调用的是wrapper)
say_hello()
"""
输出:
你好呀!
函数运行了0.000005秒
"""

关键理解 :装饰器的本质是 "函数嵌套 + 闭包 + 语法糖(@)",核心是不修改原代码,扩展功能


知识点 1:类的装饰器

类的装饰器分为两种情况

  • 情况 1:装饰器函数装饰类(装饰器是函数,参数是类,返回修改后的类)
  • 情况 2:类作为装饰器 (装饰器本身是类,通过__call__方法实现装饰功能)
子点 1:装饰器函数装饰类

这种用法是用函数给类添加属性或方法,就像给 "类模板" 加新的特征,所有实例都能用到。

分步讲解 + 代码

步骤 1:定义装饰器函数(参数是类,返回修改后的类)

python 复制代码
def add_attr_decorator(cls):  # cls是被装饰的类(比如后面的Person类)
    # 给类添加一个**类属性**(所有实例共享)
    cls.version = "1.0"  # 给类加版本号属性
    # 给类添加一个**实例方法**
    def show_version(self):  # self是实例
        print(f"当前类的版本号:{self.version}")
    cls.show_version = show_version  # 把方法绑定到类上
    return cls  # 返回修改后的类

步骤 2:用装饰器装饰类(@+ 装饰器名,放在类定义上方)

python 复制代码
@add_attr_decorator  # 给Person类穿"外套",添加属性和方法
class Person:
    def __init__(self, name):
        self.name = name  # 原有实例属性

步骤 3:创建实例,测试新增的属性和方法

python 复制代码
p1 = Person("小明")
p2 = Person("小红")

# 访问新增的类属性(所有实例共享)
print(p1.version)  # 输出:1.0
print(p2.version)  # 输出:1.0
print(Person.version)  # 输出:1.0(类也能直接访问)

# 调用新增的实例方法
p1.show_version()  # 输出:当前类的版本号:1.0
p2.show_version()  # 输出:当前类的版本号:1.0

通俗理解:就像给 "人" 这个类统一加了 "身份证版本号",所有的人都有这个属性,还能调用方法展示版本号,而且没改 Person 类的原有代码。

子点 2:类作为装饰器

装饰器不仅可以是函数,也可以是 。要让类能当装饰器,必须实现__call__方法(让类的实例可以像函数一样被调用)。

分步讲解 + 代码

步骤 1 :定义装饰器类(通过__init__接收被装饰的函数,__call__实现装饰功能)

python 复制代码
class TimerDecorator:
    def __init__(self, func):  # 初始化时接收被装饰的函数
        self.func = func  # 保存被装饰的函数(比如后面的say_hi)
    
    def __call__(self):  # 调用实例时执行的方法(核心)
        import time
        start = time.time()
        self.func()  # 执行原函数
        end = time.time()
        print(f"函数运行了{end - start:.6f}秒")

步骤 2:用类装饰器装饰函数

python 复制代码
@TimerDecorator  # 等价于:say_hi = TimerDecorator(say_hi)
def say_hi():
    print("Hi!我是类装饰器测试")

步骤 3:调用函数(实际是调用类的实例)

python 复制代码
say_hi()
"""
输出:
Hi!我是类装饰器测试
函数运行了0.000004秒
"""

关键理解

  • @TimerDecorator 执行后,say_hi 不再是原函数,而是TimerDecorator类的一个实例。
  • 调用say_hi()时,实际调用的是实例的__call__方法,从而实现装饰功能。

知识点 2:装饰器思想的进一步理解:外部修改、动态

装饰器的核心思想是 **"开放封闭原则"**:对扩展功能开放,对修改原代码封闭。我们从 "外部修改" 和 "动态" 两个角度拆解。

子点 1:外部修改 ------ 不碰原代码,在外部加功能

通俗解释:就像你有一个手机,不想拆开机身(修改原代码),但想加保护功能,就给手机套上手机壳(装饰器),手机本身没改,却多了保护功能。

例子验证
python 复制代码
# 原函数:只有打印功能(绝不修改这个函数)
def original_func():
    print("我是原函数,代码从未被修改!")

# 装饰器1:添加"日志功能"(外部扩展)
def log_decorator(func):
    def wrapper():
        print("【日志】函数开始执行")  # 新增功能
        func()
        print("【日志】函数执行结束")  # 新增功能
    return wrapper

# 装饰器2:添加"计时功能"(外部扩展)
def timer_decorator(func):
    def wrapper():
        import time
        start = time.time()
        func()
        end = time.time()
        print(f"【计时】函数运行了{end - start:.6f}秒")
    return wrapper

# 用装饰器扩展原函数(不碰原函数代码)
original_func = log_decorator(original_func)
original_func()
"""
输出:
【日志】函数开始执行
我是原函数,代码从未被修改!
【日志】函数执行结束
"""

结论 :原函数的代码一行没改,却通过外部的装饰器增加了新功能,这就是外部修改的核心。

子点 2:动态 ------ 随时加 / 减 / 换装饰器

通俗解释:手机壳可以随时换(换装饰器)、随时拆(移除装饰器)、随时加多个(多个装饰器叠加),非常灵活。

例子验证
python 复制代码
# 原函数
def say_hi():
    print("Hi!")

# 定义两个装饰器
def log_decorator(func):
    def wrapper():
        print("【日志】开始执行")
        func()
        print("【日志】执行结束")
    return wrapper

def timer_decorator(func):
    def wrapper():
        import time
        start = time.time()
        func()
        end = time.time()
        print(f"【计时】运行了{end - start:.6f}秒")
    return wrapper

# 动态操作1:添加计时装饰器
say_hi = timer_decorator(say_hi)
say_hi()
"""
输出:
Hi!
【计时】运行了0.000003秒
"""

# 动态操作2:换成日志装饰器(先拿到原函数,再装饰)
say_hi = log_decorator(say_hi.__wrapped__)  # __wrapped__是Python内置属性,获取原函数
say_hi()
"""
输出:
【日志】开始执行
Hi!
【日志】执行结束
"""

# 动态操作3:移除所有装饰器,恢复原函数
say_hi = say_hi.__wrapped__
say_hi()  # 输出:Hi!

# 动态操作4:叠加多个装饰器
say_hi = log_decorator(timer_decorator(say_hi))
say_hi()
"""
输出:
【日志】开始执行
Hi!
【计时】运行了0.000002秒
【日志】执行结束
"""

结论 :装饰器可以动态添加、更换、移除、叠加 ,不用修改原函数代码,这就是动态的核心。


知识点 3:类方法的定义:内部定义和外部定义

在 Python 中,类的方法主要有 3 种:实例方法、类方法、静态方法 。我们重点讲类方法的定义(内部 + 外部),先明确类方法的基本特征:

  • 类方法用@classmethod装饰,第一个参数是cls(代表类本身,不是实例)。
  • 类方法可以通过类直接调用 ,也可以通过实例调用 ,主要用来操作类属性(所有实例共享的属性)。
子点 1:类内部定义类方法(最常用)
分步讲解 + 代码

步骤 1:定义类,在内部定义类方法、实例方法、静态方法(对比学习)

python 复制代码
class Student:
    # 类属性:所有学生共享的学校名称
    school = "北京大学"

    # 初始化方法:实例属性(每个学生的名字不同)
    def __init__(self, name):
        self.name = name

    # 1. 实例方法:操作实例属性(第一个参数self)
    def show_name(self):
        print(f"我的名字是{self.name}")

    # 2. 类方法:操作类属性(@classmethod装饰,第一个参数cls)
    @classmethod
    def change_school(cls, new_school):
        cls.school = new_school  # 修改类属性
        print(f"学校已改为:{cls.school}")

    # 3. 静态方法:和类、实例无关(@staticmethod装饰,无默认参数)
    @staticmethod
    def show_rule():
        print("学生必须遵守校规!")

步骤 2:调用各类方法(重点看类方法的调用方式)

python 复制代码
# 创建实例
s1 = Student("张三")
s2 = Student("李四")

# 调用实例方法(只能通过实例调用)
s1.show_name()  # 输出:我的名字是张三

# 调用类方法(两种方式:类调用/实例调用,推荐类调用)
Student.change_school("清华大学")  # 输出:学校已改为:清华大学
s1.change_school("复旦大学")       # 输出:学校已改为:复旦大学

# 验证类属性的修改(所有实例共享)
print(Student.school)  # 输出:复旦大学
print(s1.school)       # 输出:复旦大学
print(s2.school)       # 输出:复旦大学

# 调用静态方法(类/实例都能调用)
Student.show_rule()  # 输出:学生必须遵守校规!
s1.show_rule()       # 输出:学生必须遵守校规!

关键总结

  • 类方法的核心是@classmethodcls参数,用来操作类属性。
  • 内部定义是最直接的方式,也是日常开发中最常用的。
子点 2:类外部定义类方法(动态扩展类)

有时候我们需要给已经定义好的类添加方法(比如第三方库的类,不能修改源码),这时候就可以在外部定义并绑定到类上。

分 3 种情况讲解(实例方法、类方法、静态方法)

步骤 1:先定义一个基础类(无任何方法,除了初始化)

python 复制代码
class Teacher:
    # 类属性
    subject = "数学"
    # 初始化方法
    def __init__(self, name):
        self.name = name

步骤 2 :外部定义实例方法,并绑定到类

python 复制代码
# 外部定义实例方法(第一个参数必须是self)
def show_name(self):
    print(f"老师的名字是{self.name}")

# 绑定到Teacher类
Teacher.show_name = show_name

# 测试
t1 = Teacher("王老师")
t1.show_name()  # 输出:老师的名字是王老师

步骤 3 :外部定义类方法,并绑定到类

python 复制代码
# 外部定义类方法(必须用@classmethod装饰,第一个参数cls)
@classmethod
def change_subject(cls, new_subject):
    cls.subject = new_subject
    print(f"授课科目已改为:{cls.subject}")

# 绑定到Teacher类
Teacher.change_subject = change_subject

# 测试(类调用/实例调用都可以)
Teacher.change_subject("语文")  # 输出:授课科目已改为:语文
print(Teacher.subject)  # 输出:语文

步骤 4 :外部定义静态方法,并绑定到类

python 复制代码
# 外部定义静态方法(必须用@staticmethod装饰,无默认参数)
@staticmethod
def show_tip():
    print("老师要认真备课!")

# 绑定到Teacher类
Teacher.show_tip = show_tip

# 测试
Teacher.show_tip()  # 输出:老师要认真备课!
t1.show_tip()       # 输出:老师要认真备课!

通俗理解:就像给一个已经做好的玩具(类),后期贴上新的贴纸(方法),玩具本身没重做,却多了新功能。


总结:核心知识点梳理

为了方便你记忆,我把所有核心点整理成表格:

知识点 核心内容 通俗比喻
类的装饰器(函数装饰类) 用函数给类添加属性 / 方法,不修改类的源码 给类模板加新特征
类的装饰器(类作为装饰器) 用类实现装饰器,通过__call__方法执行装饰功能 用 "类外壳" 包装函数
装饰器思想 - 外部修改 不修改原代码,在外部通过装饰器扩展功能 手机套手机壳,不拆机加保护
装饰器思想 - 动态 随时添加、更换、移除装饰器 随时换手机壳
类方法(内部定义) @classmethod装饰,第一个参数cls,操作类属性 类的 "专属工具"
类方法(外部定义) 给已有的类动态添加方法(实例 / 类 / 静态方法) 给玩具后期贴贴纸

课后小练习(巩固所学)

  1. 装饰器函数装饰类 ,给Dog类添加color属性和show_color方法。
  2. 类作为装饰器,给函数添加 "计数功能"(统计函数被调用的次数)。
  3. 给已有的Cat类,在外部定义一个类方法 ,修改类属性breed(品种)。

通过这样的拆解和举例,相信你已经能理解这些知识点了。学习 Python 的关键是多敲代码,把每个例子自己手敲一遍,运行看结果,就能慢慢掌握啦!

练习 1:用装饰器函数装饰类,给 Dog 类添加 color 属性和 show_color 方法

练习目标

通过装饰器函数Dog类动态添加color类属性(所有狗共享)和show_color实例方法(展示颜色),不修改Dog类的原有代码。

步骤流程
  1. 定义装饰器函数 :接收被装饰的类,给类添加color属性和show_color方法;
  2. @装饰器名装饰Dog类;
  3. 创建Dog类的实例,验证新增的属性和方法。
代码实现(写入practice.py
python 复制代码
# --------------- 练习1:装饰器函数装饰类 ---------------
# 步骤1:定义装饰器函数(给类添加属性和方法)
def add_dog_attrs(cls):
    # 给类添加类属性color(所有Dog实例共享)
    cls.color = "黄色"  # 统一设置默认颜色为黄色
    # 定义show_color实例方法(展示颜色)
    def show_color(self):
        print(f"我是{self.name},我的毛色是:{self.color}")
    # 把方法绑定到类上
    cls.show_color = show_color
    # 返回修改后的类
    return cls

# 步骤2:用装饰器装饰Dog类
@add_dog_attrs
class Dog:
    # Dog类原有代码(只定义名字属性,无color和show_color)
    def __init__(self, name):
        self.name = name  # 实例属性:狗的名字

# 步骤3:创建实例,验证效果
if __name__ == "__main__":
    # 测试练习1
    print("===== 练习1测试 =====")
    wangcai = Dog("旺财")
    huanghuang = Dog("黄黄")
    # 访问新增的类属性color
    print(f"Dog类的默认颜色:{Dog.color}")  # 输出:黄色
    print(f"旺财的颜色:{wangcai.color}")    # 输出:黄色
    # 调用新增的show_color方法
    wangcai.show_color()  # 输出:我是旺财,我的毛色是:黄色
    huanghuang.show_color()  # 输出:我是黄黄,我的毛色是:黄色
    
    # 也可以修改类属性,所有实例都会同步
    Dog.color = "黑色"
    wangcai.show_color()  # 输出:我是旺财,我的毛色是:黑色
关键说明
  • add_dog_attrs是装饰器函数,参数cls代表被装饰的Dog类;
  • cls.color = "黄色"是给类加类属性,所有实例共享;
  • show_color是实例方法,通过self访问属性。

练习 2:用类作为装饰器,给函数添加 "计数功能"(统计调用次数)

练习目标

通过类装饰器给函数动态添加计数功能,每次调用函数时,自动统计调用次数并展示。

步骤流程
  1. 定义类装饰器 :通过__init__接收被装饰的函数,用实例属性保存调用次数;
  2. 实现__call__方法:在方法内完成 "计数 + 执行原函数" 的逻辑;
  3. @类装饰器名装饰目标函数;
  4. 多次调用函数,验证计数功能。
代码实现(接在练习 1 代码后)
python 复制代码
# --------------- 练习2:类作为装饰器实现计数功能 ---------------
# 步骤1:定义类装饰器
class CountDecorator:
    def __init__(self, func):
        # 保存被装饰的函数
        self.func = func
        # 初始化调用次数为0(实例属性,记录次数)
        self.count = 0

    def __call__(self, *args, **kwargs):
        # 每次调用函数时,计数+1
        self.count += 1
        # 执行原函数
        result = self.func(*args, **kwargs)
        # 打印调用次数
        print(f"函数{self.func.__name__}已被调用{self.count}次")
        # 返回原函数的执行结果
        return result

# 步骤2:用类装饰器装饰函数
@CountDecorator
def say_hi(name):
    """测试函数:打印问候语"""
    print(f"你好,我是{name}!")

@CountDecorator
def add(a, b):
    """测试函数:计算两数之和"""
    return a + b

# 步骤3:多次调用函数,验证计数
if __name__ == "__main__":
    # 测试练习2
    print("\n===== 练习2测试 =====")
    say_hi("旺财")  # 第1次调用
    say_hi("小白")  # 第2次调用
    add(1, 2)       # 第1次调用
    add(3, 4)       # 第2次调用
    add(5, 6)       # 第3次调用
关键说明
  • 类装饰器必须实现__call__方法,这样类的实例才能像函数一样被调用;
  • self.count是实例属性,用来保存函数的调用次数;
  • *args, **kwargs是为了让装饰器适配任意参数的函数(比如say_hi有一个参数,add有两个参数)。

练习 3:给已有的 Cat 类,在外部定义类方法修改类属性 breed

练习目标

给预先定义的Cat类(无法修改源码的情况),在外部 定义类方法,实现修改类属性breed(品种)的功能。

步骤流程
  1. 定义基础的Cat类(包含类属性breed);
  2. 在外部定义类方法 (用@classmethod装饰,参数为cls);
  3. 将类方法绑定到Cat类上;
  4. 调用类方法修改类属性,验证效果。
代码实现(接在练习 2 代码后)
python 复制代码
# --------------- 练习3:外部定义类方法修改类属性 ---------------
# 步骤1:定义已有的Cat类(假设无法修改此类的源码)
class Cat:
    # 类属性:默认品种为中华田园猫
    breed = "中华田园猫"

    def __init__(self, name):
        self.name = name  # 实例属性:猫的名字

# 步骤2:在外部定义类方法(修改类属性breed)
@classmethod
def change_breed(cls, new_breed):
    # 修改类属性breed
    cls.breed = new_breed
    print(f"猫的品种已修改为:{cls.breed}")

# 步骤3:将类方法绑定到Cat类上
Cat.change_breed = change_breed

# 步骤4:调用类方法,验证效果
if __name__ == "__main__":
    # 测试练习3
    print("\n===== 练习3测试 =====")
    # 查看初始品种
    print(f"初始品种:{Cat.breed}")  # 输出:中华田园猫
    # 调用外部定义的类方法修改品种
    Cat.change_breed("英短")
    # 查看修改后的品种(所有实例共享)
    mimi = Cat("咪咪")
    huahua = Cat("花花")
    print(f"咪咪的品种:{mimi.breed}")  # 输出:英短
    print(f"花花的品种:{huahua.breed}")  # 输出:英短
关键说明
  • 类方法的第一个参数必须是cls(代表类本身),用来操作类属性;
  • Cat.change_breed = change_breed将外部方法绑定到类上,实现动态扩展;
  • 类属性修改后,所有实例的该属性都会同步更新。

类的装饰器的执行顺序是怎样的?

作为零基础的学习者,你肯定会对装饰器的执行顺序感到困惑 ------ 尤其是多个装饰器叠加时,到底先执行哪个?

我会先把执行过程拆成两个核心阶段 (装饰阶段 + 调用阶段),再用大白话 + 带打印的代码例子(每一步都标清执行顺序),分情况讲解:单个装饰器、多个装饰器叠加、类作为装饰器的执行顺序,最后总结成易记的规律,确保你一看就懂。


先明确两个核心阶段(关键!避免混淆)

类的装饰器的执行过程分为两个完全不同的阶段,这是理解执行顺序的根本:

  • 装饰阶段 :Python 解释器加载代码时 (比如运行脚本、导入模块),遇到@装饰器名就会立即执行装饰器函数,完成对类的 "包装"。

通俗说:这是 "给类穿外套" 的阶段 ,代码一加载就执行,不用等创建实例。

  • 调用阶段 :当你创建类的实例 (比如a = A())、调用类的方法时,才会执行装饰器里的包装函数(wrapper)或类装饰器的__call__方法。

通俗说:这是"穿好外套后,使用类" 的阶段。


情况 1:单个函数装饰器装饰类(最基础)

先从最简单的单个装饰器入手,通过打印语句直观看到执行顺序。

代码例子(加大量打印,标清步骤)
python 复制代码
# 定义装饰器函数
def add_attr_decorator(cls):
    print("【装饰阶段】1. 执行装饰器函数 add_attr_decorator,接收的类:", cls.__name__)
    # 给类添加属性
    cls.version = "v1.0"
    # 定义包装函数(只有调用阶段才会执行)
    def wrapper(*args, **kwargs):
        print("【调用阶段】1. 执行装饰器的 wrapper 函数")
        # 创建原类的实例
        instance = cls(*args, **kwargs)
        print("【调用阶段】3. 原类实例创建完成,返回实例")
        return instance
    print("【装饰阶段】2. 装饰器函数执行完毕,返回 wrapper 函数")
    return wrapper

# 用装饰器装饰类(触发装饰阶段)
@add_attr_decorator
class A:
    def __init__(self):
        print("【调用阶段】2. 执行类 A 的 __init__ 方法")

# --------------- 分割线:以上是装饰阶段,以下是调用阶段 ---------------
print("------------------开始创建实例------------------")
a = A()  # 创建实例,触发调用阶段
核心总结(单个装饰器)
阶段 执行顺序
装饰阶段 解释器加载代码时,先执行@add_attr_decorator → 调用add_attr_decorator(A) → 返回wrapper函数 → 把A重命名为wrapper
调用阶段 创建实例a = A() → 实际调用wrapper() → 执行wrapper内的代码 → 调用原类A()创建实例 → 执行A.__init__ → 返回实例

情况 2:多个函数装饰器叠加装饰类(重点!易混淆)

多个装饰器叠加时,装饰阶段和调用阶段的执行顺序完全相反,这是初学者最容易踩坑的点。

代码例子(两个装饰器叠加)
python 复制代码
# 装饰器1:外层装饰器(离类远)
def decorator1(cls):
    print("【装饰阶段】1. 执行 decorator1,接收的类:", cls.__name__)
    def wrapper(*args, **kwargs):
        print("【调用阶段】1. 执行 decorator1 的 wrapper 函数")
        instance = cls(*args, **kwargs)
        print("【调用阶段】4. 从 decorator1 的 wrapper 返回实例")
        return instance
    print("【装饰阶段】2. decorator1 执行完毕,返回 wrapper")
    return wrapper

# 装饰器2:内层装饰器(离类近)
def decorator2(cls):
    print("【装饰阶段】3. 执行 decorator2,接收的类:", cls.__name__)
    def wrapper(*args, **kwargs):
        print("【调用阶段】2. 执行 decorator2 的 wrapper 函数")
        instance = cls(*args, **kwargs)
        print("【调用阶段】3. 从 decorator2 的 wrapper 返回实例")
        return instance
    print("【装饰阶段】4. decorator2 执行完毕,返回 wrapper")
    return wrapper

# 叠加装饰器:@decorator1 在外,@decorator2 在内
@decorator1
@decorator2
class B:
    def __init__(self):
        print("【调用阶段】2.5 执行类 B 的 __init__ 方法")

# --------------- 分割线:装饰阶段结束,开始调用阶段 ---------------
print("------------------开始创建实例------------------")
b = B()
通俗比喻:穿衣服和脱衣服

1. 装饰阶段(穿衣服) :先穿内层 的装饰器(离类近的@decorator2),再穿外层 的装饰器(离类远的@decorator1)。

  • 就像穿秋衣(decorator2)→ 穿外套(decorator1),先穿里面的,再穿外面的。

2. 调用阶段(脱衣服) :先脱外层 的装饰器(decorator1 的 wrapper),再脱内层的装饰器(decorator2 的 wrapper),最后露出原类(B 的__init__)。

  • 就像脱外套(decorator1)→ 脱秋衣(decorator2),先脱外面的,再脱里面的。
核心规律(多个装饰器叠加)
阶段 执行顺序 口诀
装饰阶段 从下到上(离类近的装饰器先执行,离类远的后执行) 近的先穿
调用阶段 从上到下(离类远的装饰器的 wrapper 先执行,离类近的后执行) 远的先脱

情况 3:类作为装饰器的执行顺序

类作为装饰器时,依赖两个特殊方法:__init__(初始化)和__call__(调用),它们分别对应装饰阶段调用阶段

代码例子
python 复制代码
# 定义类装饰器
class TimerDecorator:
    def __init__(self, cls):
        print("【装饰阶段】1. 执行类装饰器的 __init__ 方法,接收的类:", cls.__name__)
        self.cls = cls  # 保存被装饰的类

    def __call__(self, *args, **kwargs):
        print("【调用阶段】1. 执行类装饰器的 __call__ 方法")
        instance = self.cls(*args, **kwargs)
        print("【调用阶段】3. 原类实例创建完成,返回实例")
        return instance

# 用类装饰器装饰类(触发装饰阶段)
@TimerDecorator
class C:
    def __init__(self):
        print("【调用阶段】2. 执行类 C 的 __init__ 方法")

# --------------- 分割线:装饰阶段结束,开始调用阶段 ---------------
print("------------------开始创建实例------------------")
c = C()

核心总结(类作为装饰器)

阶段 执行顺序
装饰阶段 解释器加载代码时,执行@TimerDecorator → 调用TimerDecorator(C) → 执行__init__方法 → 把C重命名为TimerDecorator的实例
调用阶段 创建实例c = C() → 实际调用实例的__call__方法 → 执行__call__内的代码 → 调用原类C()创建实例 → 执行C.__init__ → 返回实例

情况 4:装饰器装饰类的方法(扩展:容易和类的装饰器混淆)

有时候你可能会遇到 "装饰器装饰类里的方法"(比如@classmethod@staticmethod,或自定义装饰器),这里也顺带讲一下执行顺序,避免混淆。

代码例子
python 复制代码
# 定义装饰器:装饰类的方法
def method_decorator(func):
    print("【装饰阶段】1. 执行 method_decorator,接收的方法:", func.__name__)
    def wrapper(self):
        print("【调用阶段】1. 执行 method_decorator 的 wrapper 函数")
        func(self)
        print("【调用阶段】3. 方法执行完毕")
    print("【装饰阶段】2. method_decorator 执行完毕,返回 wrapper")
    return wrapper

class D:
    def __init__(self):
        self.name = "D"

    # 用装饰器装饰实例方法
    @method_decorator
    def show_name(self):
        print("【调用阶段】2. 执行 show_name 方法")

# --------------- 分割线:装饰阶段结束,开始调用阶段 ---------------
print("------------------开始创建实例并调用方法------------------")
d = D()
d.show_name()
核心总结
  • 装饰阶段:加载类时,立即执行装饰器函数,包装方法。
  • 调用阶段:调用方法时,执行包装函数,再执行原方法。

最终总结:类的装饰器执行顺序的核心规律

为了方便你记忆,我把所有情况的核心规律整理成三句口诀

  1. 装饰阶段看 "就近":多个装饰器叠加时,离类近的先执行(从下到上)。
  2. 调用阶段看 "远近":多个装饰器的包装函数,离类远的先执行(从上到下)。
  3. 类装饰器分两法__init__在装饰阶段执行,__call__在调用阶段执行。
零基础学习建议
  1. 写代码加打印 :遇到不懂的执行顺序,就在装饰器、类的方法里加print,运行后看输出顺序,比死记硬背更有效。
  2. 先练单个,再练多个:先掌握单个装饰器的执行顺序,再尝试叠加 2 个装饰器,逐步进阶。
  3. 记住两个阶段:永远区分 "装饰阶段(加载代码时)" 和 "调用阶段(用类时)",这是避免混淆的关键。

类的装饰器的应用场景

结合你零基础的学习背景,我会 从 "实用价值 + 通俗场景描述 + 具体代码例子"的角度,拆解类的装饰器的常见应用场景。所有例子都会保持简单易懂,延续之前的 "大白话 + 分步代码" 风格,让你不仅知道 "能用在哪",还能知道 "怎么用"。

首先回顾下类的装饰器的核心:不修改类的源码,动态给类添加功能、修改类的行为。这一特性让它在实际开发中非常灵活,以下是最常用的 6 大场景:


场景 1:类的属性 / 方法扩展(最基础、最常用)

适用情况

当你需要给多个类统一添加通用的属性或方法,但又不想在每个类里重复写代码时,用类的装饰器是最佳选择。

通俗例子

比如你开发了一个项目,里面有User(用户类)、Product(商品类)、Order(订单类),现在需要给所有类都加上创建时间、版本号 这些通用属性,以及展示通用信息的方法。如果每个类都写一遍,会重复很多代码,用类装饰器就能一次性搞定。

代码实现
python 复制代码
# 定义通用的扩展装饰器
def add_common_attrs(cls):
    # 给类添加通用类属性
    cls.create_time = "2025-12-11"  # 创建时间
    cls.version = "v2.0"            # 版本号
    
    # 给类添加通用实例方法
    def show_common_info(self):
        print(f"【{cls.__name__}】创建时间:{self.create_time},版本号:{self.version}")
    
    cls.show_common_info = show_common_info
    return cls

# 用装饰器装饰各个类
@add_common_attrs
class User:
    def __init__(self, name):
        self.name = name

@add_common_attrs
class Product:
    def __init__(self, price):
        self.price = price

@add_common_attrs
class Order:
    def __init__(self, order_id):
        self.order_id = order_id

# 测试效果
u = User("小明")
u.show_common_info()  # 输出:【User】创建时间:2025-12-11,版本号:v2.0

p = Product(99.9)
p.show_common_info()  # 输出:【Product】创建时间:2025-12-11,版本号:v2.0
核心价值

代码复用:避免重复写通用代码,提升开发效率,后期修改也只需要改装饰器的代码,所有类都会同步更新。


场景 2:单例模式的实现(面试高频、开发常用)

适用情况

单例模式 :让一个类永远只能创建一个实例(比如系统的配置类、数据库连接类,只需要一个实例就够了,多创建会浪费资源)。用类的装饰器实现单例,简洁又灵活。

通俗例子

比如你做一个游戏,游戏的 "全局配置类"GameConfig需要保存游戏的音量、分辨率等设置,整个游戏只需要一个配置实例,所有地方都用这个实例,就可以用类装饰器实现。

代码实现
python 复制代码
# 定义单例装饰器
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 GameConfig:
    def __init__(self, volume=50, resolution="1920x1080"):
        self.volume = volume
        self.resolution = resolution

# 测试单例效果
config1 = GameConfig()
config2 = GameConfig(volume=80)  # 尝试传新参数

# 两个变量指向同一个实例
print(config1 is config2)  # 输出:True
print(config1.volume)      # 输出:50(还是第一个实例的参数,因为只创建一次)
核心价值

资源节约:避免重复创建实例,减少内存占用和初始化开销,保证全局只有一个统一的实例。


场景 3:类的实例化参数校验

适用情况

当你需要检查类实例化时传入的参数是否合法 (比如参数类型、范围、是否为空),但不想在每个类的__init__方法里写校验代码时,用类装饰器可以统一实现。

通俗例子

比如你有一个Student(学生类),要求年龄必须是 10-25 的整数名字不能为空字符串,用类装饰器可以给类加上参数校验功能,不符合条件就直接报错。

代码实现
python 复制代码
# 定义参数校验装饰器
def validate_student(cls):
    # 保存原有的__init__方法
    original_init = cls.__init__
    
    # 重写__init__方法,添加校验逻辑
    def new_init(self, name, age):
        # 校验名字:不能为空
        if not name or isinstance(name, str) is False:
            raise ValueError("名字必须是非空字符串!")
        # 校验年龄:必须是10-25的整数
        if not isinstance(age, int) or age < 10 or age > 25:
            raise ValueError("年龄必须是10-25的整数!")
        # 执行原有的__init__方法
        original_init(self, name, age)
    
    # 替换类的__init__方法
    cls.__init__ = new_init
    return cls

# 用装饰器装饰类
@validate_student
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 测试校验效果
s1 = Student("张三", 18)  # 合法,正常创建
print(s1.name)  # 输出:张三

# s2 = Student("", 20)  # 报错:ValueError: 名字必须是非空字符串!
# s3 = Student("李四", 30)  # 报错:ValueError: 年龄必须是10-25的整数!
核心价值

统一校验:把参数校验的逻辑抽离出来,多个类可以复用同一个装饰器,避免在每个类里写重复的校验代码。


场景 4:日志记录与性能监控

适用情况

当你需要记录类的实例化过程、方法调用情况 ,或者统计类的方法执行时间(性能监控),用类的装饰器可以实现无侵入式的监控。

通俗例子

比如你开发了一个Calculator(计算器类),需要记录每个方法的调用时间、调用次数,方便后期排查性能问题,用类装饰器就能实现。

代码实现
python 复制代码
# 定义日志和计时装饰器
def monitor_class(cls):
    # 给类添加调用次数的类属性
    cls.call_count = 0
    
    # 遍历类的所有方法,给方法添加监控
    for attr_name, attr_value in cls.__dict__.items():
        # 只处理方法(排除属性、初始化方法等)
        if callable(attr_value) and attr_name != "__init__":
            # 定义包装函数
            def wrapper(func):
                def inner(*args, **kwargs):
                    import time
                    # 记录开始时间
                    start = time.time()
                    # 执行原方法
                    result = func(*args, **kwargs)
                    # 记录结束时间
                    end = time.time()
                    # 增加调用次数
                    cls.call_count += 1
                    # 打印日志
                    print(f"【监控】方法{func.__name__}调用耗时:{end - start:.6f}秒")
                    print(f"【监控】累计调用次数:{cls.call_count}")
                    return result
                return inner
            # 替换原方法
            setattr(cls, attr_name, wrapper(attr_value))
    return cls

# 用装饰器装饰类
@monitor_class
class Calculator:
    def add(self, a, b):
        return a + b
    
    def multiply(self, a, b):
        return a * b

# 测试监控效果
calc = Calculator()
print(calc.add(1, 2))  # 输出:3,同时打印监控日志
print(calc.multiply(3, 4))  # 输出:12,同时打印监控日志
核心价值

无侵入监控:不用修改类的原有方法,就能实现日志记录和性能统计,方便调试和线上问题排查。


场景 5:缓存 / 记忆化(提升类方法的执行效率)

适用情况

当类的某些方法执行耗时较长 ,且输入相同参数时返回结果相同(比如计算斐波那契数、查询数据库固定数据),用类装饰器可以给方法添加缓存,避免重复计算。

通俗例子

比如你有一个MathTool(数学工具类),里面的fib方法用来计算斐波那契数,多次调用相同参数时,用缓存直接返回结果,提升效率。

代码实现
python 复制代码
# 定义缓存装饰器
def cache_decorator(cls):
    # 遍历类的方法,给方法添加缓存
    for attr_name, attr_value in cls.__dict__.items():
        if callable(attr_value) and attr_name != "__init__":
            # 用字典保存缓存(参数作为键,结果作为值)
            cache = {}
            def wrapper(func):
                def inner(*args):
                    # 如果参数在缓存里,直接返回结果
                    if args in cache:
                        print(f"【缓存】使用缓存结果:{cache[args]}")
                        return cache[args]
                    # 否则执行方法,并存入缓存
                    result = func(*args)
                    cache[args] = result
                    print(f"【缓存】存入新结果:{result}")
                    return result
                return inner
            setattr(cls, attr_name, wrapper(attr_value))
    return cls

# 用装饰器装饰类
@cache_decorator
class MathTool:
    def fib(self, n):
        # 计算斐波那契数(递归实现,耗时较长)
        if n <= 1:
            return n
        return self.fib(n-1) + self.fib(n-2)

# 测试缓存效果
tool = MathTool()
tool.fib(10)  # 第一次计算,存入缓存
tool.fib(10)  # 第二次直接用缓存,不用重复计算
核心价值

提升效率:避免重复执行耗时的方法,尤其是参数重复的场景,能显著提升程序运行速度。


场景 6:权限控制(限制类方法的调用)

适用情况

当你需要限制某些用户 / 角色调用类的方法(比如只有管理员能调用删除数据的方法),用类装饰器可以统一实现权限校验。

通俗例子

比如你有一个AdminSystem(管理系统类),里面的delete_user方法只有管理员能调用,普通用户调用会报错,用类装饰器就能实现这个权限控制。

代码实现
python 复制代码
# 定义权限校验装饰器
def check_permission(cls):
    # 遍历类的方法,给方法添加权限校验
    for attr_name, attr_value in cls.__dict__.items():
        if callable(attr_value) and attr_name.startswith("delete_"):
            def wrapper(func):
                def inner(self, user_role, *args):
                    # 校验角色:只有admin能调用
                    if user_role != "admin":
                        raise PermissionError("权限不足!只有管理员能执行此操作。")
                    # 否则执行方法
                    return func(self, user_role, *args)
                return inner
            setattr(cls, attr_name, wrapper(attr_value))
    return cls

# 用装饰器装饰类
@check_permission
class AdminSystem:
    def delete_user(self, user_role, user_id):
        print(f"管理员删除了用户{user_id}")

# 测试权限控制
system = AdminSystem()
system.delete_user("admin", 1001)  # 合法,输出:管理员删除了用户1001

# system.delete_user("user", 1002)  # 报错:PermissionError: 权限不足!只有管理员能执行此操作。
核心价值

统一权限管理:把权限校验的逻辑抽离出来,多个类可以复用,避免在每个方法里写重复的权限判断。


总结:类的装饰器应用场景核心梳理

为了方便你记忆,我把核心场景和价值整理成表格:

应用场景 核心需求 通俗比喻
属性 / 方法扩展 给多个类统一加通用属性 / 方法 给所有商品统一贴价格标签
单例模式实现 一个类只能创建一个实例 世界上只有一个太阳
实例化参数校验 检查传入参数是否合法 安检机检查行李是否合规
日志记录 / 性能监控 记录类的使用情况、统计执行时间 给设备装监控摄像头
缓存 / 记忆化 避免重复计算,提升方法执行效率 笔记本记录计算结果,下次直接用
权限控制 限制不同角色调用类的方法 小区大门的门禁系统
学习小贴士

对于零基础的你来说,不用一开始就记住所有场景,先掌握属性扩展、单例模式这两个最常用的场景,动手敲代码实现一遍,就能理解类的装饰器的核心价值。后续遇到实际问题时,再回头看其他场景,会更容易理解。

类的装饰器的优缺点

结合你零基础的学习背景,我会继续用大白话 + 实际例子 + 核心总结的方式,拆解类的装饰器的优点和缺点。每个点都会结合之前学过的例子,让你能关联起来理解,避免抽象难懂。

首先回顾类的装饰器的核心:不修改类的源码,动态给类添加 / 修改功能。这个核心特性既是它的优点来源,也带来了一些潜在问题。


一、类的装饰器的优点

类的装饰器之所以被广泛使用,是因为它解决了开发中的很多实际问题,以下是最核心的 5 个优点:

优点 1:遵循 "开放封闭原则",不破坏原代码(最核心)

通俗解释:就像给手机套壳,不用拆手机(修改原类代码)就能加保护功能,原手机的核心功能完全不受影响。如果后期想去掉壳,手机还是原来的样子。

例子验证(回顾之前的属性扩展例子):

python 复制代码
# 原类:业务逻辑固定,绝不修改
class User:
    def __init__(self, name):
        self.name = name

# 装饰器:外部扩展功能
def add_version(cls):
    cls.version = "v1.0"
    return cls

# 装饰后的类:原类代码一行没改,却多了版本号属性
@add_version
class User:
    def __init__(self, name):
        self.name = name

核心价值 :在实际开发中,原类可能是第三方库的类、项目中已经上线的核心类,直接修改容易引发 bug,而装饰器能做到无侵入式扩展,极大降低风险。

优点 2:代码复用性极高,避免重复 "造轮子"

通俗解释 :一个装饰器可以给多个类 "服务",比如一个 "参数校验装饰器",既能给Student类用,也能给Teacher类用,不用每个类都写一遍校验代码。

例子验证(回顾参数校验例子):

python 复制代码
# 通用的参数校验装饰器(写一次,用多次)
def validate_age(cls):
    original_init = cls.__init__
    def new_init(self, name, age):
        if not isinstance(age, int) or age < 0:
            raise ValueError("年龄必须是正整数!")
        original_init(self, name, age)
    cls.__init__ = new_init
    return cls

# 给Student类用
@validate_age
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 给Teacher类用
@validate_age
class Teacher:
    def __init__(self, name, age):
        self.name = name
        self.age = age

核心价值:减少重复代码,提升开发效率,后期如果要修改校验逻辑,只需要改装饰器的代码,所有被装饰的类都会同步更新,不用逐个修改。

优点 3:功能动态可控,灵活调整

通俗解释:装饰器就像给类 "贴贴纸",可以随时贴(添加装饰器)、撕(移除装饰器)、换(替换装饰器)、叠(多个装饰器叠加),完全不影响类本身。

例子验证(动态切换装饰器):

python 复制代码
# 原类
class Product:
    def __init__(self, price):
        self.price = price

# 装饰器1:加日志
def log_decorator(cls):
    # 实现日志功能...
    return cls

# 装饰器2:加缓存
def cache_decorator(cls):
    # 实现缓存功能...
    return cls

# 开发环境:加日志装饰器
@log_decorator
class Product:
    def __init__(self, price):
        self.price = price

# 生产环境:换成缓存装饰器(只需改@后的装饰器名)
@cache_decorator
class Product:
    def __init__(self, price):
        self.price = price

核心价值:可以根据不同的场景(开发 / 测试 / 生产)灵活调整类的功能,不用重构类的代码。

优点 4:逻辑解耦,让代码更清晰

通俗解释:把通用逻辑(比如日志、校验、缓存)和业务逻辑(比如用户管理、商品管理)分开,就像把衣服的 "装饰图案" 和 "布料本身" 分开,衣服的主体功能更纯粹。

例子对比

无装饰器(耦合) 有装饰器(解耦)
类里既写业务逻辑,又写校验、日志 类里只写业务逻辑,校验 / 日志交给装饰器
代码又长又乱,分不清核心逻辑 代码简洁,核心逻辑一目了然

核心价值:后期维护时,想改业务逻辑就看类本身,想改通用逻辑就看装饰器,不用在一堆代码里找来找去。

优点 5:语法糖@让代码更简洁易读

通俗解释 :用@装饰器名的方式装饰类,比手动调用装饰器函数更直观,一眼就能看出类被添加了什么功能。

例子对比

python 复制代码
# 手动调用装饰器(繁琐)
class User:
    pass
User = add_version(User)

# 用@语法糖(简洁)
@add_version
class User:
    pass

核心价值:代码更简洁,可读性更高,这也是 Python 设计装饰器语法糖的初衷。


二、类的装饰器的缺点

类的装饰器并非完美,尤其是对零基础的学习者来说,它的一些特性可能会带来理解和使用上的困难,以下是最核心的 5 个缺点:

缺点 1:增加代码的理解成本(尤其是多层装饰器)

通俗解释:装饰器的本质是 "函数嵌套 + 闭包 + 语法糖",零基础的你可能会看不懂装饰器的执行流程,尤其是多个装饰器叠加时,执行顺序更难理解。

例子(多层装饰器的执行顺序问题)

python 复制代码
# 装饰器1
def decorator1(cls):
    print("执行decorator1")
    return cls

# 装饰器2
def decorator2(cls):
    print("执行decorator2")
    return cls

# 多层装饰器:执行顺序是从下到上(先decorator2,再decorator1)
@decorator1
@decorator2
class User:
    pass

# 输出:
# 执行decorator2
# 执行decorator1

踩坑点 :很多初学者会以为装饰器的执行顺序是从上到下,实际是就近原则(离类最近的装饰器先执行),这会增加理解难度。

缺点 2:调试难度增加,"隐藏" 了原类的信息

通俗解释 :装饰器会替换原类或原方法,调试时你看到的可能是装饰器的包装函数(比如wrapper),而不是原类的方法,很难定位问题。

例子(调试时的问题)

python 复制代码
def singleton(cls):
    instances = {}
    def wrapper(*args):
        if cls not in instances:
            instances[cls] = cls(*args)
        return instances[cls]
    return wrapper

@singleton
class GameConfig:
    def __init__(self):
        self.volume = 50

# 调试时,GameConfig实际是wrapper函数,不是原类
print(GameConfig)  # 输出:<function singleton.<locals>.wrapper at 0x0000021F8A7F7E20>

踩坑点 :用print()或调试工具查看类时,看到的是装饰器的包装函数,而不是原类,容易让人困惑。

缺点 3:可能产生副作用,容易踩坑

通俗解释:一些装饰器(比如单例装饰器)会改变类的原有行为,如果不了解其内部逻辑,很容易出现预期之外的结果。

例子(单例装饰器的副作用)

python 复制代码
@singleton
class GameConfig:
    def __init__(self, volume=50):
        self.volume = volume

# 第一次创建实例:正常
config1 = GameConfig(volume=80)
print(config1.volume)  # 输出:80?不,输出50!

# 原因:单例装饰器的wrapper函数里,第一次创建实例后,后续传参都无效

踩坑点:初学者可能以为第二次传参能修改实例的属性,实际因为单例的特性,参数会被忽略,这就是装饰器的副作用。

缺点 4:特殊情况下会影响类的继承和属性访问

通俗解释 :如果装饰器返回的是函数 (比如单例装饰器的wrapper函数),而不是原类的子类,那么继承这个类时可能会出现问题。

例子(继承问题)

python 复制代码
@singleton
class Parent:
    def show(self):
        print("我是父类")

# 子类继承Parent(实际是继承wrapper函数,不是原类)
class Child(Parent):
    pass

# 报错:TypeError: function() argument 'code' must be code, not str
# 因为wrapper是函数,不能被继承

解决办法:可以优化装饰器,让其返回原类的子类(比如用类装饰器代替函数装饰器),但这会增加装饰器的复杂度。

缺点 5:过度使用会导致代码分散,不利于整体阅读

通俗解释 :如果一个类被多个装饰器装饰,比如@log@cache@validate@singleton,那么类的功能被分散在多个装饰器里,想了解类的完整功能,需要逐个查看装饰器的代码,反而降低了可读性。

例子(过度装饰的问题)

python 复制代码
@log
@cache
@validate
@singleton
@add_version
class User:
    def __init__(self, name):
        self.name = name

踩坑点:初学者容易滥用装饰器,给每个类都加一堆装饰器,导致代码逻辑分散,后期维护时需要来回切换文件查看装饰器,效率低下。


三、类的装饰器的优缺点总结(表格版)

为了方便你记忆,我把核心优缺点整理成表格:

类别 具体点 通俗比喻 核心影响
优点 遵循开放封闭原则 手机套壳不拆机 无侵入扩展,降低修改风险
代码复用性高 一个模具做多个零件 避免重复代码,提升开发效率
功能动态可控 随时换手机壳 灵活适配不同场景
逻辑解耦 衣服和装饰图案分开 代码更清晰,易维护
语法糖简洁易读 用快捷键代替长命令 代码更直观,可读性高
缺点 增加理解成本 叠了多层纸的画,看不清底层 初学者难理解执行流程
调试难度增加 戴了面具的人,认不出本来面目 调试时难定位原类 / 方法
可能有副作用 吃药治病但有副作用 改变类的原有行为,易踩坑
影响继承(特殊情况) 嫁接的树枝长不出原树的果子 继承时可能出现错误
过度使用导致代码分散 把一篇文章拆成多个小片段 不利于整体阅读和维护

四、零基础学习建议:如何正确使用类的装饰器

针对你的情况,我给出几个实用建议,帮你避开缺点,发挥优点:

1. 先学简单的装饰器,再学复杂的

先掌握属性扩展、简单日志 这类基础装饰器,再学单例、缓存、多层装饰器,一步一个脚印,不要急于求成。

2. 避免过度使用,够用就好

一个类最多用 1-2 个装饰器,比如给User类加 "参数校验 + 日志" 就够了,不要加一堆装饰器。

3. 给装饰器写详细注释

比如:

python 复制代码
def singleton(cls):
    """
    单例装饰器:让类只能创建一个实例
    :param cls: 被装饰的类
    :return: 包装函数,返回唯一实例
    """
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper

注释能帮你和其他人理解装饰器的功能,降低理解成本。

4. 调试时用__wrapped__获取原类 / 方法

Python 提供了__wrapped__属性,可以获取被装饰的原类 / 方法,比如:

python 复制代码
@singleton
class GameConfig:
    pass

# 获取原类
original_cls = GameConfig.__wrapped__
print(original_cls)  # 输出:<class '__main__.GameConfig'>
5. 优先用函数装饰器(简单),特殊场景用类装饰器

对于零基础的你,函数装饰器更容易理解和实现,类装饰器(需要__call__方法)可以后期再深入。


最后总结

类的装饰器是 Python 中非常强大的特性,优点远大于缺点,只要你掌握正确的使用方法,就能极大提升开发效率。对于零基础的你来说,不用害怕它的缺点,只要多敲代码、多调试,慢慢就能理解其内部逻辑,做到灵活运用。

作业:复习类和函数的知识点,写下自己过去一个月的学习心得,如对函数和类的理解,对 python这门工具的理解等。

作为零基础学习者,我们不用纠结复杂的专业术语,而是用生活中的具体事物类比,从 "是什么 - 有什么用 - 怎么关联" 的逻辑,把 Python、函数、类的核心讲清楚。

一、先理解 Python:一门 "说人话" 的万能工具箱

如果把编程比作 "解决生活问题",那么 Python 就是你手里的万能工具箱------ 它不像某些工具箱(比如 C++)需要你先学复杂的工具组装规则,而是打开就能用,还自带各种现成的 "配件"。

1. Python 的核心特点:为什么零基础也能学?
(1)语法像 "聊天",不用记复杂符号

比如想让计算机打印 "你好",Python 只需要写:

python 复制代码
print("你好")

就像你跟计算机说 "帮我打出来'你好'" 一样,比其他语言的System.out.println("你好")(Java)、printf("你好\n")(C 语言)简单太多。

(2)自带 "海量配件",不用自己从头造

Python 有一个超丰富的生态库(可以理解为工具箱里的 "预制配件包"):

  • 想爬取网页数据?用requests库(像现成的 "爬墙梯");
  • 想做数据分析?用pandas库(像现成的 "统计表格");
  • 想做自动化脚本(比如自动发邮件)?用smtplib库(像现成的 "邮件发射器")。

这些库就像你买工具箱时,商家直接送了你几百个专用配件,不用你自己敲铁皮做配件。

(3)"万能" 到能解决各种问题

Python 能做的事几乎覆盖了编程的所有场景:

  • 小到写个自动整理文件的脚本、做个计算器;
  • 大到做数据分析、人工智能、网页开发、游戏开发。

就像万能工具箱既能拧螺丝、钉钉子,也能修家电、装家具。

(4)跨平台 "通用",在哪都能用

你在 Windows 电脑上写的 Python 代码,复制到 Mac 或 Linux 电脑上,几乎不用改就能运行 ------ 就像你的工具箱在客厅、卧室、车库都能用来干活。

2. Python 的设计思想:简单就是美

Python 的作者一直强调 "用一种方法做一件事",不像有些语言有多种写法。这对零基础的你来说特别友好:不用纠结 "哪种写法更好",只要学一种最直接的方式就行。


二、再理解函数:工具箱里的 "单个专用工具"

当你用工具箱干活时,比如想打孔,你不会每次都 "找电线→接电源→拿钻头→旋转钻孔",而是直接用电钻这个专用工具 ------ 函数就是 Python 里的 "电钻"。

1. 函数的本质:封装重复的操作,一次造工具,多次用
生活例子:做蛋炒饭

如果你每天都要做蛋炒饭,步骤是:

  1. 打鸡蛋、搅拌;
  2. 热油、炒鸡蛋;
  3. 加米饭、翻炒;
  4. 加盐、出锅。

你不会每天都在脑子里重复想这 4 步,而是把它记成 "蛋炒饭流程"------ 函数就是把这 4 步封装成一个 "工具",需要时直接喊 "做蛋炒饭" 就行。

代码例子:用函数实现 "蛋炒饭"
python 复制代码
# 定义函数:造"做蛋炒饭"的工具(参数是"加多少盐")
def make_fried_rice(salt_amount):
    print("1. 打鸡蛋、搅拌")
    print("2. 热油、炒鸡蛋")
    print("3. 加米饭、翻炒")
    print(f"4. 加{salt_amount}勺盐,出锅")
    return "香喷喷的蛋炒饭"  # 返回值:工具的"成品"

# 调用函数:用工具做蛋炒饭(传参数"1勺盐")
rice = make_fried_rice(1)
print(rice)  # 输出:香喷喷的蛋炒饭

# 再调用一次:换个参数,做淡一点的蛋炒饭
rice2 = make_fried_rice(0.5)
2. 函数的核心要素:造工具的 4 个关键
要素 生活类比(电钻) 代码对应
定义 工厂造电钻的过程 def 函数名():
参数 电钻的钻头、打孔深度 函数括号里的变量
调用 拿起电钻按开关 函数名(参数)
返回值 打好的孔 / 打孔成功的结果 return 后的内容
3. 函数的价值:为什么必须学?
  • 避免重复劳动 :比如你需要在 10 个地方打印 "欢迎语",写一次函数,调用 10 次就行,不用写 10 遍print("欢迎")
  • 拆分复杂任务:比如做一个电商网站,把 "用户登录""商品结算""物流查询" 拆成不同函数,代码就不会乱成一团;
  • 方便修改:如果想改 "蛋炒饭" 的做法,只需要改函数里的代码,所有调用的地方都会同步更新。

三、最后理解类:工具箱里的 "物品模板 + 套装工具"

函数是 "单个工具"(比如电钻),而类是 **"物品的模板"+"套装工具"**------ 比如 "汽车" 这个类,既是造汽车的模板,也包含了 "启动、加速、刹车" 等一套行为工具。

1. 类的本质:模拟现实世界的事物,把 "特征" 和 "行为" 打包

现实世界的任何事物都有特征(属性)行为(方法)

  • 比如 "狗":特征是名字、颜色、品种;行为是叫、跑、摇尾巴。
  • 比如 "手机":特征是品牌、型号、电量;行为是打电话、发微信、拍照。

类就是把这些特征和行为打包成一个模板,用模板可以造出无数个具体的 "实例"(比如一只叫 "旺财" 的金毛,一部苹果 15 手机)。

生活例子:汽车模板(类)与具体汽车(实例)
类(汽车模板) 实例(你家的白色特斯拉)
特征(类属性):4 个轮子、有方向盘 特征(实例属性):白色、型号 Model 3、电量 80%
行为(方法):启动、加速、刹车 行为:你的特斯拉启动后,能开到 60 码
代码例子:用类实现 "狗"
python 复制代码
# 定义类:造"狗"的模板
class Dog:
    # 类属性:所有狗都有的特征(共享)
    leg_num = 4

    # 初始化方法:创建实例时,给实例加专属特征(名字、颜色)
    def __init__(self, name, color):
        self.name = name  # 实例属性:专属名字
        self.color = color  # 实例属性:专属颜色

    # 方法:狗的行为(叫)
    def bark(self):
        print(f"{self.name}({self.color})汪汪叫!")

# 用模板造实例:具体的狗
wangcai = Dog("旺财", "黄色")
xiaobai = Dog("小白", "白色")

# 访问特征
print(wangcai.leg_num)  # 输出:4(类属性,所有狗共享)
print(wangcai.name)     # 输出:旺财(实例属性,专属)

# 调用行为
wangcai.bark()  # 输出:旺财(黄色)汪汪叫!
xiaobai.bark()  # 输出:小白(白色)汪汪叫!
2. 类的核心要素:模板的 3 个关键
(1)类与实例:模板和具体物品
  • 类:是抽象的模板(比如 "狗"),不占内存;
  • 实例:是具体的物品(比如旺财),占内存,有专属特征。

就像建筑图纸(类)和用图纸盖的房子(实例):图纸只是设计,房子才是能住的具体建筑。

(2)属性:事物的特征(分两种)
  • 类属性:所有实例共享的特征(比如所有狗都有 4 条腿);
  • 实例属性:每个实例的专属特征(比如旺财是黄色,小白是白色)。
(3)方法:事物的行为(分三种)
  • 实例方法 :专属实例的行为(比如旺财叫),第一个参数是self(代表实例本身);
  • 类方法 :操作类属性的行为(比如修改所有狗的腿数),用@classmethod装饰,第一个参数是cls(代表类本身);
  • 静态方法 :和类、实例无关的通用行为(比如狗的通用规则 "不能吃巧克力"),用@staticmethod装饰,无默认参数。
3. 类的三大特性:让模板更灵活(用生活例子讲透)
(1)封装:把细节藏起来,只用表面的按钮

比如你用手机打电话,只需要按号码、点拨打,不用知道手机内部的信号怎么传输、芯片怎么工作 ------ 类的封装就是把 "内部的复杂逻辑" 藏起来,外部只需要调用方法就行。

(2)继承:儿子继承父亲的财产,还能自己赚新钱

比如 "金毛" 是 "狗" 的子类,它继承了狗的所有特征(4 条腿、会叫),还能加自己的特征(比如 "会游泳")。

代码例子:

python 复制代码
# 父类:狗
class Dog:
    def bark(self):
        print("汪汪叫")

# 子类:金毛(继承狗)
class GoldenRetriever(Dog):
    def swim(self):
        print("会游泳")

# 实例:金毛狗
jinmao = GoldenRetriever()
jinmao.bark()  # 继承的行为:汪汪叫
jinmao.swim()  # 自己的行为:会游泳
(3)多态:同一行为,不同事物有不同表现

比如 "跑" 这个行为:

  • 狗跑是用四条腿;
  • 人跑是用两条腿;
  • 汽车跑是用轮子。

在代码里,不同的类可以有同名的方法,执行时会根据实例的类型,表现出不同的效果。

4. 类的价值:为什么比函数更强大?
  • 模拟现实 :能把现实世界的事物直接转化为代码,比如做一个宠物管理系统,用Dog类、Cat类就能清晰表示不同宠物;
  • 组织大型项目:当代码量上万行时,用类把相关的函数和属性打包,比一堆零散的函数更易维护;
  • 代码复用:通过继承,子类能复用父类的代码,不用重复写。

四、函数与类的关联:从 "单个工具" 到 "套装工具"

很多初学者会问:"有了函数,为什么还要学类?" 其实它们是互补的

  • 小任务用函数:比如写一个计算圆面积的工具,用函数就够了;
  • 大任务用类:比如做一个游戏里的 "角色系统"(有战士、法师、牧师),每个角色有自己的属性和技能,用类打包会更清晰。

简单说:函数是 "单打独斗" 的工具,类是 "团队协作" 的工具包


五、文字版思维导图:核心知识框架梳理

为了方便你整体记忆,我把所有内容整理成层级清晰的思维导图:

六、零基础学习小贴士

  1. 先练函数,再学类:函数是基础,先把函数的参数、返回值练熟,再学类的概念,难度会低很多;
  2. 用生活例子类比:遇到不懂的概念,先想生活中对应的事物(比如类 = 模板,实例 = 具体物品),再看代码;
  3. 多敲代码少死记 :比如学类时,自己写一个Student类、Teacher类,运行后看效果,比背概念更有效。

总结来说,Python 是让你轻松解决问题的 "万能工具箱",函数是里面的 "单个工具",类是里面的 "套装工具包"------ 掌握了它们,你就能用 Python 解决大部分实际问题了。

浙大疏锦行

相关推荐
song5012 小时前
鸿蒙 Flutter CI/CD 进阶:Jenkins + 鸿蒙打包自动化流程
分布式·python·flutter·3d·ci/cd·分类
炽烈小老头2 小时前
【每天学习一点算法 2025/12/10】反转链表
学习·算法·链表
EXtreme352 小时前
【数据结构】算法艺术:如何用两个栈(LIFO)优雅地模拟队列(FIFO)?
c语言·数据结构·算法·设计模式·栈与队列·摊还分析·算法艺术
IT·小灰灰2 小时前
AI成为精确的执行导演:Runway Gen-4.5如何用控制美学重塑社媒视频工业
大数据·图像处理·人工智能·python·数据分析·音视频
艾莉丝努力练剑2 小时前
【Python基础:语法第五课】Python字典高效使用指南:避开KeyError,掌握遍历与增删改查精髓
大数据·运维·人工智能·python·安全·pycharm
适应规律2 小时前
hook来获取模型每层的激活值输出
pytorch·python·深度学习
软件开发技术深度爱好者2 小时前
Python + Ursina设计一个有趣的3D小游戏
开发语言·python·3d
CodeByV2 小时前
【算法题】滑动窗口(一)
算法
AuroraWanderll2 小时前
C++面向对象与类和对象(一)----C++重要基础入门知识
c语言·数据结构·c++·算法·stl