Skip to content

Python 类中的装饰器:从"属性门禁"到"类方法" ​

引言:装饰器,类的"多功能工具箱" ​

在 Python 里,@ 开头的那一行,我们叫它装饰器。它不会凭空变出魔法,而是在不修改原有代码结构的前提下,给函数或方法"额外加装功能"。

类里面最常用的装饰器,就像一台多功能工具箱里摆着的几件趁手工具:

装饰器一句话作用生活化类比
@classmethod让方法绑定"类",而不是"实例"全家的公共事务
@staticmethod让方法变成"类里的普通函数"门口的公用工具箱
@property把方法伪装成属性,读取/赋值时偷偷把关店里的智能门禁
@dataclass自动生成 __init__、__repr__ 等方法模板化的一键生成器
@abstractmethod只声明"必须有什么方法",强制子类实现公司定的"上岗必会技能清单"
@wraps自定义装饰器时,保留原函数的"身份信息"给代理配一张"原主身份证"

本文会按照从常用到小众、从基础到进阶的顺序,逐个拆开讲清楚。先从最常见、也最容易搞混的 @classmethod 和 @staticmethod 讲起。


一、@classmethod:让方法绑定"类" ​

1.1 核心定义 ​

普通方法的第一个参数是 self,代表"当前这个实例";而 @classmethod 修饰的方法,第一个参数是 cls,代表"当前这个类"。

一句话:@classmethod 让方法属于"类",不依赖某个具体的实例,但知道自己是哪个类。

1.2 语法格式与参数要求 ​

python
class Student(object):
    @classmethod
    def from_string(cls, text):   # 第一个参数固定是 cls(类本身)
        # cls 是约定俗成的名字,类似 self,可以改名但强烈不建议
        pass

参数要点:

  • 第一个参数必须是 cls,它由 Python 自动传入,代表"类对象";
  • 调用时不需要手动传 cls,Python 会自动补上;
  • 方法内部可以用 cls 来访问类属性,或用 cls(...) 来创建实例。

1.3 极简示例 ​

示例 1:用类方法做"另类构造函数"

python
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    @classmethod
    def from_string(cls, text):
        # 从 "小明-95" 这样的字符串里拆出名字和成绩
        name, score = text.split('-')
        return cls(name, int(score))   # 用 cls 创建实例,而不是写死 Student

# 调用方式一:通过"类名"调用(推荐)
s = Student.from_string('小明-95')
print(s.name)    # 小明
print(s.score)   # 95

# 调用方式二:通过"实例"调用,也能正常工作
s2 = s.from_string('小红-88')
print(s2.name)   # 小红

为什么用 cls 而不是写死 Student? 因为当子类继承并调用这个方法时,cls 会自动指向子类,这样能创建出"正确的那个类"的实例。

示例 2:统计"这个类"一共创建了多少实例

python
class Animal(object):
    count = 0   # 类属性,所有实例共享

    def __init__(self):
        Animal.count += 1

    @classmethod
    def get_count(cls):
        # 用 cls.count 而不是 Animal.count,好处是子类也能复用
        return cls.count

a = Animal()
b = Animal()
print(Animal.get_count())   # 2

1.4 适用场景 ​

场景说明
提供多种创建实例的方式如 from_string、from_json、from_dict
访问或修改类属性如统计实例数量、修改全局配置
工厂方法 / 单例模式用类方法控制"怎么创建、是否复用实例"

二、@staticmethod:类里的"普通函数" ​

2.1 核心定义 ​

@staticmethod 修饰的方法,没有 self,也没有 cls。它本质上就是一个"恰好写在类里面的普通函数",和类、实例都没有绑定关系。

一句话:@staticmethod 只是把函数"寄放"在类里,方便按类名分类调用,逻辑上它谁都不依赖。

2.2 语法格式与参数要求 ​

python
class MathHelper(object):
    @staticmethod
    def is_even(n):   # 没有 self,也没有 cls
        return n % 2 == 0

参数要点:

  • 没有任何强制默认参数,需要什么参数就写什么参数;
  • 调用时传几个参数,方法就接收几个参数,Python 不会偷偷帮你塞 self 或 cls;
  • 方法内部无法通过 self 访问实例属性,也不依赖 cls。

2.3 极简示例 ​

示例 1:工具函数归类

python
class MathHelper(object):
    @staticmethod
    def is_even(n):
        return n % 2 == 0

    @staticmethod
    def add(a, b):
        return a + b

# 通过"类名"调用(推荐)
print(MathHelper.is_even(4))   # True
print(MathHelper.add(3, 5))    # 8

# 通过"实例"调用,同样可以
m = MathHelper()
print(m.is_even(7))            # False

示例 2:与实例无关的通用校验

python
class Order(object):
    def __init__(self, amount):
        self.amount = amount

    @staticmethod
    def is_valid_amount(value):
        # 校验逻辑和"某个订单"无关,谁都能用
        return isinstance(value, (int, float)) and value > 0

print(Order.is_valid_amount(100))   # True
print(Order.is_valid_amount(-5))    # False

2.4 适用场景 ​

场景说明
与实例、类都无关的工具函数如校验、格式化、纯计算
逻辑上属于该类,但不碰数据想用类名做"命名空间"来归组函数
保持接口整洁让调用方知道"这个函数和订单相关"

三、核心差异:实例方法 vs 类方法 vs 静态方法 ​

3.1 生活化类比(记住这个就不混了) ​

想象一个三口之家:

  • 实例方法(self):你自己回家开门、做饭——需要知道"我"是谁,只能处理自己的私事;
  • 类方法(cls):全家一起商量"装修风格""家族姓氏"——需要知道"我们家",但不需要某个具体的人;
  • 静态方法(无参数):门口的公用工具箱——扳手螺丝刀谁都能用,不依赖任何人,放哪都能用。

一句话总结:实例方法关心"我",类方法关心"我们这类",静态方法谁也不关心。

3.2 对比表格 ​

维度实例方法类方法 @classmethod静态方法 @staticmethod
第一个参数self(实例)cls(类)无强制默认参数
能否访问实例属性✅ 能❌ 不能(没有实例)❌ 不能(没有实例)
能否访问类属性✅ 能(通过类)✅ 能(通过 cls)✅ 能(但需写死类名)
典型调用方式实例.方法()类.方法()类.方法()
本质绑定实例绑定类普通函数

3.3 一个类里同时出现三种方法 ​

python
class Student(object):
    school = '清华附中'   # 类属性

    def __init__(self, name):
        self.name = name   # 实例属性

    def introduce(self):            # 实例方法:需要 self
        return f'我是 {self.name}'

    @classmethod
    def get_school(cls):            # 类方法:需要 cls
        return cls.school

    @staticmethod
    def is_valid_name(name):        # 静态方法:谁都不需要
        return len(name) > 0

s = Student('小明')
print(s.introduce())          # 我是 小明
print(Student.get_school())   # 清华附中
print(Student.is_valid_name('小明'))   # True

四、@property:把方法伪装成"属性门禁" ​

4.1 直接暴露属性的危险 ​

python
class Student(object):
    pass

s = Student()
s.score = 9999   # 成绩改成 9999?程序不报错,但逻辑完全错误

生活化理解:仓库大门敞开,有人把标价 100 的东西改成 10000——系统不拦,但生意没法做了。

4.2 传统方案:getter/setter 方法 ​

python
class Student(object):
    def get_score(self):
        return self._score

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

使用:

python
s = Student()
s.set_score(60)    # ✅ 正常
print(s.get_score())   # 60
s.set_score(9999)  # ❌ ValueError: score must between 0 ~ 100!

问题:每次都要写 set_score() / get_score(),太啰嗦,不像属性那样直观。

4.3 @property 基本用法:鱼与熊掌兼得 ​

python
class Student(object):
    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

使用:

python
s = Student()
s.score = 60       # ✅ 像属性一样赋值,实际调用 setter
print(s.score)     # 60,像属性一样读取,实际调用 getter
s.score = 9999     # ❌ ValueError,被拦截了!

生活化理解:顾客按门铃(s.score = 60),门禁系统自动检查(setter 里的验证),没问题才开门。顾客感觉像直接开门,实际背后有全套安检。

4.4 装饰器背后的原理(简化版) ​

@property 装饰 score() 方法
    ↓
创建另一个装饰器 @score.setter
    ↓
s.score = 60  →  自动转成 s.score(60) 调用 setter
s.score       →  自动转成 s.score() 调用 getter

你写 s.score = 60,Python 偷偷帮你调用了 set_score 逻辑——语法简洁,功能完整。

4.5 只读属性:只给看,不给改 ​

python
class Student(object):
    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2025 - self._birth   # 根据 birth 自动计算

使用:

python
s = Student()
s.birth = 2000     # ✅ 可写
print(s.age)       # 25,自动算出来

s.age = 30         # ❌ AttributeError: can't set attribute

生活化理解:birth 是出生日期登记处(可改),age 是自动计算的年龄显示牌(只读)——你改不了年龄,只能改出生日期,年龄自动跟着变。

只读属性的适用场景:

场景原因
数据由其他字段计算得出如 age = 当前年 - birth,手动改 age 会导致数据不一致
敏感数据只供查看如订单号、创建时间,防止误修改
简化接口调用方不需要知道这是计算出来的,直接用就行

4.6 致命陷阱:方法名与实例变量重名 ​

错误示范:

python
class Student(object):
    @property
    def birth(self):
        return self.birth   # ❌ 危险!

发生了什么?

s.birth          → 调用 birth() 方法
    ↓
return self.birth → 又访问 s.birth
    ↓
又调用 birth() 方法
    ↓
无限递归 → RecursionError: maximum recursion depth exceeded

生活化理解:你问前台"我的房间号是多少",前台说"请报房间号查询"——你报不出,前台再问,死循环了。

正确做法:内部变量加下划线

python
class Student(object):
    @property
    def birth(self):
        return self._birth   # ✅ 用 _birth 存实际数据

    @birth.setter
    def birth(self, value):
        self._birth = value

规则:@property 方法名用 birth,内部存数据的变量用 _birth(单下划线开头,表示"内部使用")。

4.7 实际应用案例 ​

案例 1:温度转换器——只读属性的典型场景

python
class Temperature(object):
    def __init__(self, celsius):
        self.celsius = celsius   # 触发 setter

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError('温度不能低于绝对零度!')
        self._celsius = value

    @property
    def fahrenheit(self):
        """华氏度,只读,自动计算"""
        return self._celsius * 9 / 5 + 32

# 使用
t = Temperature(25)
print(t.celsius)      # 25
print(t.fahrenheit)   # 77.0,自动转换

t.celsius = 30        # ✅ 修改摄氏度
print(t.fahrenheit)   # 86.0,华氏度自动更新

t.fahrenheit = 100    # ❌ AttributeError,只读!

生活化理解:温度计上摄氏度是主刻度(可调),华氏度是副刻度(自动换算显示)——你调不了副刻度,只能调主刻度,副刻度跟着变。

案例 2:电商订单——金额计算与验证

python
class Order(object):
    def __init__(self, price, quantity):
        self.price = price
        self.quantity = quantity

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError('价格不能为负数!')
        self._price = value

    @property
    def quantity(self):
        return self._quantity

    @quantity.setter
    def quantity(self, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError('数量必须是正整数!')
        self._quantity = value

    @property
    def total(self):
        """订单总额,只读,自动计算"""
        return self._price * self._quantity

    @property
    def discount_total(self):
        """满100打9折,只读"""
        if self.total >= 100:
            return self.total * 0.9
        return self.total

# 使用
order = Order(50, 3)
print(order.total)           # 150
print(order.discount_total)  # 135.0,自动打折

order.quantity = 1           # 修改数量
print(order.total)           # 50,自动重算
print(order.discount_total)  # 50,不满足打折条件

order.price = -10            # ❌ ValueError: 价格不能为负数!

生活化理解:收银台自动算总价和折扣——你只输入单价和数量,总额和折后价自动显示,改任何一个数字,结果立刻刷新。


五、@dataclass:数据类,省掉一堆"样板代码" ​

5.1 核心作用 ​

写一个"存数据"的类,通常要手写 __init__、__repr__、__eq__ 这些样板代码。@dataclass 会自动帮你生成它们,让你只关注"字段是什么"。

一句话:@dataclass 是"数据容器类"的一键生成器——声明字段,剩下的交给它。

5.2 极简示例 ​

不用 @dataclass 之前:

python
class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):          # 手动写打印格式
        return f'Point(x={self.x}, y={self.y})'

    def __eq__(self, other):     # 手动写相等判断
        return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)            # Point(x=1, y=2)
print(p1 == p2)      # True

用 @dataclass 之后:

python
from dataclasses import dataclass

@dataclass
class Point:
    x: int   # 类型注解,同时声明字段
    y: int

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1)            # Point(x=1, y=2)   —— 自动生成 __repr__
print(p1 == p2)      # True              —— 自动生成 __eq__
print(p1.x, p1.y)    # 1 2               —— 自动生成 __init__

三行代码顶掉了原来十几行样板代码。

5.3 常用参数与场景 ​

python
from dataclasses import dataclass, field

@dataclass
class Config:
    host: str = 'localhost'   # 有默认值
    port: int = 8080
    tags: list = field(default_factory=list)   # 可变默认值要这样写(见误区)
场景说明
配置文件、DTO、返回值模型纯数据,需要打印和比较
需要"冻结"的数据用 @dataclass(frozen=True) 变成只读
大量重复样板代码减少手写 __init__/__repr__ 的负担

六、@abstractmethod:抽象方法,给子类立"规矩" ​

6.1 核心作用 ​

@abstractmethod 用来声明一个"必须被实现,但父类不实现"的方法。配合 ABC(抽象基类),可以强制所有子类都实现某些方法,否则子类无法实例化。

一句话:@abstractmethod 是公司定的"上岗必会技能清单"——不掌握这些技能,就不给你发工牌(不能创建实例)。

6.2 极简示例 ​

python
from abc import ABC, abstractmethod

class Animal(ABC):            # 继承 ABC,表示这是一个抽象基类
    @abstractmethod
    def speak(self):          # 只声明"必须有 speak",不写实现
        pass

# a = Animal()   # ❌ TypeError: Can't instantiate abstract class Animal

class Dog(Animal):
    def speak(self):          # ✅ 实现了抽象方法
        print('汪汪')

class Cat(Animal):
    def speak(self):
        print('喵喵')

d = Dog()
d.speak()   # 汪汪

如果某个子类忘了实现 speak,创建实例时就会直接报错:

python
class Fish(Animal):
    pass

# f = Fish()   # ❌ TypeError: Can't instantiate abstract class Fish

这样就把"漏实现方法"的错误,从"运行到一半才崩"提前到了"定义类时立刻发现"。

6.3 适用场景 ​

场景说明
定义统一接口/协议各种动物都要会 speak,但叫声不同
插件/驱动架构规定插件必须实现 load、run 等方法
框架的基类Django、SQLAlchemy 等大量使用抽象基类

七、@wraps:自定义装饰器时,保留函数的"身份" ​

7.1 核心作用 ​

当你自己写一个装饰器去包装类里的方法时,被包装的方法会丢失原本的名字和文档字符串(__name__、__doc__ 等元信息)。@wraps 就是用来"把身份信息搬回来"的。

一句话:@wraps 是给"代理"配一张"原主身份证",让别人问"你是谁"时,报的是原函数的名字。

7.2 极简示例 ​

不写 @wraps 会怎样:

python
def log(func):
    def wrapper(*args, **kwargs):
        print(f'调用中...')
        return func(*args, **kwargs)
    return wrapper

class Calculator(object):
    @log
    def add(self, a, b):
        '加法运算'
        return a + b

print(Calculator.add.__name__)   # wrapper  ❌ 名字丢了
print(Calculator.add.__doc__)    # None      ❌ 文档丢了

加上 @wraps 修复:

python
from functools import wraps

def log(func):
    @wraps(func)                # 关键:保留 func 的名字、文档等元信息
    def wrapper(*args, **kwargs):
        print(f'调用中...')
        return func(*args, **kwargs)
    return wrapper

class Calculator(object):
    @log
    def add(self, a, b):
        '加法运算'
        return a + b

print(Calculator.add.__name__)   # add       ✅ 名字回来了
print(Calculator.add.__doc__)    # 加法运算   ✅ 文档回来了

7.3 适用场景 ​

场景说明
自己写日志/计时装饰器打印函数名、读文档时才不会乱
编写框架 / 工具库让 IDE、文档生成器能正确识别方法
调试和反射inspect、help() 依赖这些元信息

八、常见误区与避坑指南 ​

8.1 误区一:混淆 @classmethod 和 @staticmethod ​

python
class Student(object):
    school = '一中'

    @staticmethod
    def get_school_static():
        return Student.school   # 只能写死类名,子类继承后就读不到子类的 school

    @classmethod
    def get_school_class(cls):
        return cls.school       # cls 自动指向正确的类

判断口诀:方法里要不要知道"是哪个类"?

  • 要访问/创建"当前类"(尤其涉及继承)→ 用 @classmethod;
  • 纯粹是个通用工具函数,和类无关 → 用 @staticmethod。

8.2 误区二:把 @staticmethod 当成"不能调用的方法" ​

python
class A(object):
    @staticmethod
    def f(x):
        return x

a = A()
print(a.f(1))      # ✅ 通过实例调用也完全可以
print(A.f(1))      # ✅ 通过类调用也完全可以

@staticmethod 不是"禁止通过实例调用",它只是"不接收 self/cls"。两种方式都能调。

8.3 误区三:@classmethod 里把 cls 当 self 用 ​

python
class A(object):
    name = '类属性'

    @classmethod
    def bad(cls):
        return cls.instance_attr   # ❌ 类方法里根本没有实例属性

    @classmethod
    def good(cls):
        return cls.name            # ✅ 只能访问类属性

cls 是类对象,不是实例,所以访问不到实例属性(那些要靠 self 的东西)。

8.4 误区四:@property 忘记写 setter,却试图赋值 ​

python
class Student(object):
    @property
    def score(self):
        return self._score

s = Student()
s.score = 60   # ❌ AttributeError: can't set attribute

原因:只写了 getter,没写 setter,这是只读属性。修正:补上 @score.setter。

8.5 误区五:@property 的 setter 里又调用 self.score ​

python
@score.setter
def score(self, value):
    self.score = value   # ❌ 又触发 setter,无限递归!

修正:操作内部变量 _score:

python
@score.setter
def score(self, value):
    self._score = value   # ✅

8.6 误区六:在 __init__ 里绕过 setter ​

python
class Student(object):
    def __init__(self, score):
        self._score = score   # ❌ 直接操作内部变量,绕过验证

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if value < 0 or value > 100:
            raise ValueError('invalid')
        self._score = value

s = Student(9999)   # 成功!验证失效了

修正:__init__ 里也要走 setter:

python
def __init__(self, score):
    self.score = score   # ✅ 触发 setter 验证

8.7 误区七:@dataclass 的可变默认值写错 ​

python
from dataclasses import dataclass

@dataclass
class Bad:
    tags: list = []   # ❌ 所有实例共享同一个 list,改一个全乱

修正:用 field(default_factory=list):

python
from dataclasses import dataclass, field

@dataclass
class Good:
    tags: list = field(default_factory=list)   # ✅ 每个实例独立一个 list

8.8 误区八:@abstractmethod 忘记继承 ABC ​

python
from abc import abstractmethod

class Animal(object):          # ❌ 忘了继承 ABC
    @abstractmethod
    def speak(self):
        pass

# a = Animal()   # 居然能创建!抽象方法失效了

修正:必须继承 ABC:

python
from abc import ABC, abstractmethod

class Animal(ABC):             # ✅
    @abstractmethod
    def speak(self):
        pass

8.9 误区九:自定义装饰器不写 @wraps,导致方法名错乱 ​

python
def timer(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

class A(object):
    @timer
    def run(self):
        pass

print(A.run.__name__)   # wrapper  ❌ 本应是 run

修正:在 wrapper 上补 @wraps(func)(见第七节)。


九、知识链条:一张图串起所有类装饰器 ​

普通方法(self):只关心"我"这个实例
    ↓
需要操作类本身、提供另类构造 → @classmethod(cls)
需要放一个无关的通用函数     → @staticmethod(无参数)
    ↓
需要给属性加"门禁"(验证/只读/计算) → @property
    ↓
需要快速生成数据容器类       → @dataclass
    ↓
需要强制子类实现统一接口     → @abstractmethod(配合 ABC)
    ↓
自己写装饰器时保留函数身份   → @wraps

选型口诀:

  • 关心"我" → 实例方法
  • 关心"我们这类" → @classmethod
  • 谁都不关心 → @staticmethod
  • 要给属性把关 → @property
  • 存数据的类 → @dataclass
  • 定接口规矩 → @abstractmethod
  • 写装饰器 → @wraps

十、实战练习 ​

练习 1:用 @classmethod 实现"另类构造" ​

给 Student 类加一个类方法 from_dict,能从一个字典创建实例:

python
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

# 你的代码:实现 from_dict

# 测试
s = Student.from_dict({'name': '小明', 'score': 90})
if s.name == '小明' and s.score == 90:
    print('测试通过!')
else:
    print('测试失败!')
参考答案
python
class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    @classmethod
    def from_dict(cls, d):
        return cls(d['name'], d['score'])   # 用 cls 创建实例

练习 2:用 @property 实现只读属性 ​

给 Screen 对象加上 width、height 属性,以及只读属性 resolution(分辨率 = 宽 × 高):

python
class Screen(object):
    pass

# 测试
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
    print('测试通过!')
else:
    print('测试失败!')
参考答案
python
class Screen(object):
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if value <= 0:
            raise ValueError('宽度必须为正数!')
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        if value <= 0:
            raise ValueError('高度必须为正数!')
        self._height = value

    @property
    def resolution(self):
        """分辨率 = 宽 × 高,只读"""
        return self._width * self._height

练习 3:用 @dataclass 简化数据类 ​

把下面的 Book 类改写成 @dataclass 版本,要求能打印、能比较相等:

python
class Book(object):
    def __init__(self, title, author, price):
        self.title = title
        self.author = author
        self.price = price

# 你的代码:用 @dataclass 改写
参考答案
python
from dataclasses import dataclass

@dataclass
class Book:
    title: str
    author: str
    price: float

b1 = Book('活着', '余华', 28.5)
b2 = Book('活着', '余华', 28.5)
print(b1)           # Book(title='活着', author='余华', price=28.5)
print(b1 == b2)     # True

练习 4:用 @abstractmethod 定接口 ​

定义一个抽象基类 Shape,要求所有图形都必须实现 area 方法,然后让 Circle、Square 实现它:

python
# 你的代码
参考答案
python
from abc import ABC, abstractmethod
import math

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return math.pi * self.r ** 2

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

print(Circle(2).area())   # 约 12.566
print(Square(3).area())   # 9

十一、小结 ​

  1. @classmethod:第一个参数是 cls,绑定"类",用于另类构造、操作类属性;
  2. @staticmethod:没有 self/cls,就是"类里的普通函数",用于无关的通用工具;
  3. 核心区别:实例方法关心"我"、类方法关心"我们这类"、静态方法谁都不关心;
  4. @property:把方法伪装成属性,读取/赋值时偷偷把关,核心价值是控制访问;
  5. @dataclass:自动生成 __init__/__repr__/__eq__,是数据容器类的一键生成器;
  6. @abstractmethod:配合 ABC,强制子类实现统一接口,把错误提前到定义时发现;
  7. @wraps:自定义装饰器时保留原函数的名字、文档等身份信息;
  8. 关键避坑:@property 内部变量用 _xxx 防递归;@dataclass 可变默认值用 field(default_factory=...);@abstractmethod 必须继承 ABC;写装饰器别忘 @wraps。

记住口诀:关心"我"用 self,关心"类"用 cls,谁都不关心用 static;管属性找 property,存数据找 dataclass,定规矩找 abstractmethod,写装饰器别忘 wraps。 掌握这七件工具,Python 类的设计就能又安全、又清晰、又省心。