Python 进阶特性:函数进阶、装饰器、生成器与面向对象

聚焦 Python 进阶主线:函数进阶、装饰器、生成器、面向对象与 dataclass,不再混入过多零散主题。

一、函数进阶

在正式进入装饰器、生成器和面向对象之前,最好先补 3 个直觉:

  • 函数在 Python 里也是对象,可以赋值、传参、返回
  • 函数内部会形成自己的作用域
  • 内层函数可以“记住”外层作用域里的变量,这就是闭包的直觉来源

函数也是对象

def greet(name):
    return f"hello, {name}"

handler = greet
print(handler("alex"))

这也是为什么后面能把函数传给别的函数,或者让装饰器接收一个函数再返回一个新函数。

嵌套函数、作用域与闭包(Closure)

在 Python 中,不仅可以在模块层面定义函数,还可以在一个函数内部定义另一个函数,这就是嵌套函数。内层函数可以访问外层作用域的变量。

当一个内层函数引用了外层函数的局部变量,并且外层函数将这个内层函数作为对象返回时,就形成了一个闭包(Closure)

def outer():
    # 外层函数的局部变量
    message = "from outer"

    # 在 outer 内部定义嵌套函数 inner
    def inner():
        # 内层函数引用了外层作用域的变量
        print(message)

    # 将 inner 函数对象作为返回值返回
    return inner

# 执行 outer(),返回的是 inner 函数对象
func = outer()
# 即使 outer() 已经执行完毕,func() 依然能访问并打印 message
func()

正常情况下,函数执行结束后,其局部变量(如 message)会被销毁。但因为嵌套的 inner 函数内部引用了它,闭包将这个变量的状态保存了下来,延长了其生命周期。 掌握嵌套函数与闭包的运行机制,是后续理解装饰器的基础。

二、高阶函数与匿名函数

什么是高阶函数

高阶函数至少满足下面一个条件:

  • 函数可以接收另一个函数作为参数
  • 函数可以把另一个函数作为返回值返回
def apply_twice(func, value):
    # 把函数当作参数传入,并调用两次
    return func(func(value))

def add_one(number):
    # 简单的加一函数
    return number + 1

# 将 add_one 函数作为参数传递给 apply_twice
print(apply_twice(add_one, 3)) # 5

匿名函数、mapfilter

mapfilter 就是非常典型的高阶函数,它们通常和匿名函数 lambda 配合使用:

numbers = [2, 18, 9, 22, 17, 24, 8, 12, 27]

# filter 用于筛选,保留满足 lambda 表达式条件的元素
divisible_by_three = list(filter(lambda item: item % 3 == 0, numbers))

# map 用于映射,对每个元素执行 lambda 表达式的操作
doubled = list(map(lambda item: item * 2 + 10, numbers))

print(divisible_by_three)
print(doubled)

建议:

  • 简单场景可以用 lambda
  • 一旦逻辑稍复杂,就直接写普通函数,代码会更清晰

三、装饰器的核心思想

装饰器要解决的问题是:不改原函数调用方式,也不直接改原函数源码,但要给函数增加新功能

函数、闭包与装饰器关系图

图示:从“函数是对象”到“闭包保存状态”,再到“装饰器包装函数”的理解路径。

import time

# 定义装饰器函数,接收被装饰的函数作为参数
def timer(func):
    # 定义包装函数,接收任意参数以适配不同的被装饰函数
    def wrapper(*args, **kwargs):
        # 记录调用开始时间
        start_time = time.time()
        # 执行原函数
        result = func(*args, **kwargs)
        # 记录调用结束时间
        end_time = time.time()
        print(f"{func.__name__} 运行耗时: {end_time - start_time:.4f}s")
        # 返回原函数的执行结果
        return result

    return wrapper

# 使用 @ 语法糖应用装饰器
@timer
def slow_add(a, b):
    # 模拟耗时操作
    time.sleep(0.2)
    return a + b

print(slow_add(1, 2))

带参数的装饰器

如果我们需要根据不同场景应用不同的装饰逻辑,就需要给装饰器传参。比如,同样是认证逻辑,有些接口使用本地密码认证("local"),有些接口使用 token 认证("jwt")。

带参数的装饰器,本质上是在普通装饰器外面再套一层函数,用来专门接收这些参数:

# 最外层函数:专门用来接收装饰器自己的参数(如 "local" 或 "jwt")
def auth(auth_type):
    # 中间层函数:这才是真正的装饰器,接收被装饰的函数 func
    def outer_wrapper(func):
        # 最内层函数:替换原函数的新函数,接收原函数的调用参数
        def wrapper(*args, **kwargs):
            # 这里可以使用最外层传进来的 auth_type 变量(利用了闭包特性)
            # 这就是传参的意义:根据参数不同,执行不同的增强逻辑
            if auth_type == "local":
                print("--> 拦截:执行本地账号密码校验...")
            elif auth_type == "jwt":
                print("--> 拦截:执行 JWT Token 校验...")
            else:
                print("--> 拦截:未知认证方式,拒绝访问!")
                return

            # 校验通过后,执行原函数
            return func(*args, **kwargs)

        return wrapper

    return outer_wrapper

# @auth("local") 的执行过程分为两步:
# 1. 先执行 auth("local"),此时 auth_type="local",函数返回 outer_wrapper
# 2. 再执行 @outer_wrapper,把 home 函数传进去,最终把 home 替换成了 wrapper
@auth("local")
def home():
    print("welcome to home page")

@auth("jwt")
def dashboard():
    print("welcome to dashboard")

home()
# 输出:
# --> 拦截:执行本地账号密码校验...
# welcome to home page

dashboard()
# 输出:
# --> 拦截:执行 JWT Token 校验...
# welcome to dashboard

记忆方式:

  • 最外层 auth(auth_type):接收装饰器参数。也就是 @auth("local") 里的 "local",它相当于一个配置项,决定了内部走哪种校验分支。
  • 中间层 outer_wrapper(func):接收被装饰函数(即 homedashboard)。
  • 最内层 wrapper(*args, **kwargs):接收原函数运行时的实际参数,并真正执行增强逻辑(如校验权限)和原函数逻辑。

四、列表推导式、迭代器与生成器

推导式、迭代器与生成器关系图

图示:推导式偏向一次性得到结果,迭代器和生成器偏向按需取值。

列表推导式

列表推导式就是“一边遍历,一边加工,顺手把结果装进列表”的简写。

最常见的例子:

# 使用列表推导式快速生成列表,包含 0 到 4 的两倍
numbers = [i * 2 for i in range(5)]
print(numbers)

它等价于下面这段普通写法:

numbers = []

for i in range(5):
    numbers.append(i * 2)

print(numbers)
  • range(5) 负责提供 04
  • for i in range(5) 负责依次取值
  • i * 2 负责把每个值加工后放进新列表

最基本的语法模板是:

[表达式 for 变量 in 可迭代对象]

如果还要筛选条件,可以写成:

# 取出 0 到 9 里的偶数
even_numbers = [i for i in range(10) if i % 2 == 0]
print(even_numbers)
[表达式 for 变量 in 可迭代对象 if 条件]

注意:

  • 它本质上就是 for 循环加 append() 的简写
  • 适合写简单、规律明显的列表生成逻辑

字典推导式

字典推导式可以理解成:一边遍历,一边生成键和值,顺手装进字典。

scores = {"tom": 80, "alex": 90, "rose": 85}
new_scores = {name: score + 5 for name, score in scores.items()}
print(new_scores)

最基本的语法模板是:

{键表达式: 值表达式 for 变量 in 可迭代对象}

如果还要加条件,可以写成:

passed = {name: score for name, score in scores.items() if score >= 85}
print(passed)

生成器表达式

如果把列表推导式的中括号改成小括号,就会得到生成器表达式。

可以先把它理解成一句话:写法很像列表推导式,但它不会一次性把结果全放进内存,而是需要一个值就生成一个值。

先看对比:

numbers = [i * 2 for i in range(5)]   # 列表推导式
generator = (i * 2 for i in range(5)) # 生成器表达式

两者写法很像,但结果不同:

  • 列表推导式会立刻得到一个完整列表
  • 生成器表达式得到的是一个生成器对象
  • 生成器适合处理数据量较大、没必要一次全部加载的场景
# 使用小括号创建生成器表达式,按需生成数据,节省内存
numbers = (i * 2 for i in range(5))
# 使用 next() 获取生成器的下一个值
print(next(numbers))
print(next(numbers))

这段代码不会直接得到 [0, 2, 4, 6, 8],而是先创建一个“等待取值”的生成器。

每调用一次 next(numbers),它才会继续往下算一个结果:

  • 第一次 next(numbers) 得到 0
  • 第二次 next(numbers) 得到 2

如果你继续调用下去,会依次得到 468
所有值取完后,再继续 next() 就会抛出 StopIteration

除了手动用 next() 取值,更常见的写法是直接配合 for 循环:

numbers = (i * 2 for i in range(5))

for value in numbers:
    print(value)

这样会依次输出 0、2、4、6、8,而且不需要自己处理 StopIteration

可以先这样区分:

  • 列表推导式:一次性得到完整结果,适合数据量不大、后面要反复使用的场景
  • 生成器表达式:按需生成结果,适合顺序读取、节省内存的场景

使用 yield 创建生成器

斐波那契示例很典型,重点要理解 yield 会“返回一个值,并暂停函数”。

def fib(max_count):
    n, a, b = 0, 0, 1
    while n < max_count:
        # yield 返回当前值,并暂停函数执行,保留执行现场
        yield b
        a, b = b, a + b
        n += 1

# 使用 for 循环遍历生成器,自动处理 StopIteration 异常
for value in fib(10):
    print(value)

迭代器和可迭代对象

  • 可以被 for 遍历的对象,通常称为可迭代对象
  • 可以被 next() 持续取值的对象,称为迭代器
values = [1, 2, 3]
# 使用 iter() 将可迭代对象转换为迭代器
iterator = iter(values)

# 使用 next() 从迭代器中取值
print(next(iterator))
print(next(iterator))

在实际开发里,不需要经常手写 next(),更多时候直接交给 for 循环即可。

五、面向对象基础与对象模型

这一节把原来分散在基础篇和进阶篇里的面向对象知识点收拢到一起。 阅读顺序建议是:

面向对象与 dataclass 关系图

图示:先理解类与对象,再掌握封装、继承、多态,最后理解 dataclass 的适用场景。

  1. 先理解类、对象、属性、方法和 self
  2. 再理解实例变量、类变量、初始化和对象创建
  3. 然后学习封装、继承、多态这三大核心特性
  4. 最后再看 dataclass、组合、类方法和属性方法

面向过程和面向对象的区别

同样是在解决问题,两种思路关注点不一样:

  • 面向过程更关注“步骤怎么走”
  • 面向对象更关注“谁来做这件事”

比如“报名学习”这件事:

  • 按面向过程的想法,会先拆步骤,再按顺序执行
  • 按面向对象的想法,会先识别出学员、班级、课程这些实体,再决定它们各自负责什么动作

可以先记一句话:面向对象是在用对象来组织数据和行为。

类与对象

class Dog:
    # 初始化实例属性,self 代表当前实例本身
    def __init__(self, name: str) -> None:
        self.name = name

    # 定义实例方法
    def eat(self, food: str) -> None:
        print(f"{self.name} is eating {food}")

# 创建 Dog 类的实例
dog = Dog("Lucky")
# 调用实例方法
dog.eat("bone")

这里最重要的理解是:

  • 类是模板
  • 对象是实例
  • self 代表当前实例本身

属性、方法和 self

对象通常由两部分组成:

  • 属性:对象身上保存的数据
  • 方法:对象能够执行的动作
class Person:
    def introduce(self) -> None:
        print(f"我叫{self.name},今年{self.age}岁")


p1 = Person()
p1.name = "老王"
p1.age = 23
p1.introduce()

这里的 self 可以先这样理解:

  • self 不是关键字,只是约定俗成的名字
  • 在实例方法里,self 代表当前调用这个方法的对象
  • 谁调用方法,self 就代表谁

self、实例属性和初始化

继续往前一步,创建对象时通常会通过 __init__() 初始化属性:

class Account:
    def __init__(self, owner: str, balance: float) -> None:
        self.owner = owner
        self.balance = balance

    def deposit(self, amount: float) -> None:
        self.balance += amount


account = Account("alex", 100.0)
account.deposit(50.0)
print(account.balance)

这段代码里最关键的是:

  • self.ownerself.balance 是实例属性
  • 每个对象都有自己独立的实例属性
  • deposit() 修改的是“当前对象自己的状态”

也可以把 __init__() 记成一条最小公式:

self.属性名 = 传入参数

它表示把外部传进来的值,保存到当前对象自己的属性上。

__new__():真正创建对象

__init__() 不同,__new__() 负责的是“先把对象创建出来”。

初学时先记住这组关系:

  • __new__():创建对象
  • __init__():初始化对象
  • 创建对象时,通常会先执行 __new__(),再执行 __init__()
class Demo:
    def __new__(cls, *args, **kwargs):
        print("__new__ called")
        return super().__new__(cls)

    def __init__(self, name: str) -> None:
        print("__init__ called")
        self.name = name


demo = Demo("alex")
print(demo.name)

可以先把它记成一句话:__new__() 负责“造对象”,__init__() 负责“填数据”。

大多数日常业务代码里,你更常写的是 __init__();只有在你要控制对象创建过程时,比如单例、继承不可变类型,__new__() 才更常出现。

__str__():控制对象打印出来的样子

默认情况下,直接 print(对象) 时,你看到的通常是一段不太直观的内存地址信息。
如果你希望对象打印出来更像“给人看的描述”,就可以实现 __str__()

class Student:
    def __init__(self, name: str, score: int) -> None:
        self.name = name
        self.score = score

    def __str__(self) -> str:
        return f"Student(name={self.name}, score={self.score})"


student = Student("alex", 95)
print(student)

输出会更容易理解:

Student(name=alex, score=95)

可以先记住两点:

  • __str__() 的作用是定义对象的“可读字符串表示”
  • 它最常在 print(obj)str(obj) 这类场景下被自动调用

__del__():析构函数与对象销毁

当一个对象的引用计数降为 0(即没有任何变量再指向它)时,Python 的垃圾回收机制会准备销毁该对象,此时会自动调用 __del__() 方法。

class Resource:
    def __init__(self, name: str) -> None:
        self.name = name
        print(f"Resource {self.name} allocated.")

    def __del__(self) -> None:
        print(f"Resource {self.name} released.")

# 创建对象,分配资源
res = Resource("A")

# 删除对象的引用,触发垃圾回收,自动调用 __del__
del res
print("End of program.")

实战避坑建议: 虽然 __del__() 看起来很像用来清理资源(比如关闭文件、断开数据库连接)的好地方,但在现代 Python 代码中极不推荐依赖 __del__() 来做关键的资源管理。因为 Python 的垃圾回收时机是不可预测的,可能会导致资源迟迟未被释放。 对于资源管理,现代 Python 的标准做法是使用 with 语句(上下文管理器)或显式的 close() 方法。

实例变量与类变量

class User:
    role = "member"  # 类变量,所有实例共享

    def __init__(self, name: str) -> None:
        self.name = name  # 实例变量,每个对象独立


user = User("alex")
print(user.name)
print(user.role)

可以先这样区分:

  • 实例变量属于某个具体对象
  • 类变量属于整个类,通常被所有实例共享

六、面向对象的三大特性:封装、继承、多态

1. 封装(Encapsulation)

封装的核心思想是:隐藏对象内部的复杂性,只暴露出简单的接口供外部使用。 在 Python 中,通常通过约定俗成的命名来表示私有属性或方法:

  • 单下划线 _ 开头:表示“受保护的”,暗示外部不应该直接访问,但语法上并不限制。
  • 双下划线 __ 开头:表示“私有的”,Python 会进行名称改写(Name Mangling),外部难以直接访问。
from decimal import Decimal

class BankAccount:
    def __init__(self, balance: Decimal) -> None:
        self.__balance = balance  # 私有属性

    # 对外提供公开的方法来操作私有数据
    def deposit(self, amount: Decimal) -> None:
        if amount > 0:
            self.__balance += amount

    def get_balance(self) -> Decimal:
        return self.__balance

# 涉及金额计算,强烈建议传入字符串以避免 float 转换时的精度污染
account = BankAccount(Decimal('100.00'))
account.deposit(Decimal('50.55'))
print(account.get_balance())  # 150.55

# 外部直接访问 __balance 会抛出 AttributeError
# print(account.__balance)  

# 但 Python 并没有真正的私有,它只是把名字改写成了 _类名__属性名
# print(account._BankAccount__balance) # 强制访问依然可以拿到(极不推荐)

实战避坑:到底用单下划线还是双下划线?

很多教程为了讲解“私有”概念会演示双下划线 __。但在真实的 Python 业务代码中,绝大多数情况只用单下划线 _(如 self._balance)。

  • 单下划线 _:是一种“君子协议”。语法上无任何限制,只是告诉调用方“这是内部状态,请不要直接修改”。这是最 Pythonic 的做法。
  • 双下划线 __:它的真实目的是为了防止子类意外覆盖父类的同名属性。因为它只是做了名称改写(Name Mangling),并不是严格的安全隔离(如上面的强行访问示例)。如果没有复杂的继承防冲突需求,不要滥用双下划线。

2. 继承(Inheritance)与 super()

继承的核心思想是:子类复用父类的代码和结构。

class People:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

# Man 类继承自 People 类
class Man(People):
    def __init__(self, name: str, age: int, money: float) -> None:
        # 使用 super() 调用父类初始化逻辑,避免重复代码
        super().__init__(name, age)
        self.money = money

obj = Man("alex", 18, 1000)
print(obj.name, obj.age, obj.money)

现代 Python 里优先使用 super(),它比直接写父类名更灵活,也更适合多继承场景。

扩展:多继承与 MRO (方法解析顺序)

与 其他语言 仅支持单继承(通过接口实现多重特性)不同,Python 是原生支持多继承的。 当一个子类有多个父类时,就可能出现经典的“菱形继承”问题。Python 内部通过 MRO(Method Resolution Order,方法解析顺序) 算法(C3 线性化)来决定方法的调用链。

class Animal:
    def __init__(self):
        print("Animal init")

class Flyer(Animal):
    def __init__(self):
        super().__init__()
        print("Flyer init")
        
    def jiao(self):
        print("Flyer jiao")

class Swimmer(Animal):
    def __init__(self):
        super().__init__()
        print("Swimmer init")
        
    def jiao(self):
        print("Swimmer jiao")

# 鸭子同时继承了飞行者和游泳者(菱形继承)
class Duck(Flyer, Swimmer):
    def __init__(self):
        super().__init__()
        print("Duck init")
        
    def jiao(self):
        print("duck jiao")
        super().jiao()

duck = Duck()
duck.jiao()
# 输出: 
# Animal init
# Swimmer init
# Flyer init
# Duck init
# duck jiao
# Flyer jiao

# 查看 Duck 的方法解析顺序
print(Duck.__mro__) 
# (<class '__main__.Duck'>, <class '__main__.Flyer'>, <class '__main__.Swimmer'>, <class '__main__.Animal'>, <class 'object'>)

深度剖析:super() 的真实面目

很多初学者误以为 super() 就是“直接调用父类”。但在多继承中,super() 的真实语义是:严格按照 MRO 链条寻找当前类的下一个类并调用。

注意看上面的输出和 MRO 顺序:当代码执行到 Flyer 里的 super().__init__() 时,它并没有去调用它的父类 Animal,而是按照 MRO 链条找了下一个类 Swimmer!这就保证了处于共同顶端的 Animal 类只会被初始化一次,完美解决了多次初始化的问题。

3. 多态(Polymorphism)与方法重写

多态的核心思想是:不同的对象对同一方法有不同的响应。 在 Python 中,多态往往和“鸭子类型”结合在一起。子类可以重写父类的方法,根据自己的语义实现特定的逻辑。

class Animal:
    def sound(self) -> str:
        return "generic animal sound"

class Dog(Animal):
    def sound(self) -> str:
        return "wang wang"

class Cat(Animal):
    def sound(self) -> str:
        return "miao miao"

# --- 鸭子类型(Duck Typing)的体现 ---
class ToyDuck:
    # 注意:ToyDuck 并没有继承 Animal!
    def sound(self) -> str:
        return "zizi (squeak)"

# 这是一个多态和鸭子类型的应用:
# 只要传入的对象有 sound() 方法,就能被正常调用。这与其他语言必须实现接口或继承基类不同。
def make_sound(entity) -> None:
    print(entity.sound())

make_sound(Dog())       # wang wang
make_sound(Cat())       # miao miao
make_sound(ToyDuck())   # zizi (squeak) - 完美运行!

这里真正重要的不是术语本身,而是理解 Python 与 Java/C++ 等静态语言在多态上的核心区别:

  • 在 Java 中:要实现多态,DogCat 必须继承 Animal 或实现特定的接口。make_sound 必须声明参数类型为 Animal
  • 在 Python 中(鸭子类型):正如上面的 ToyDuck,它根本不是 Animal 的子类,但只要它“走起来像鸭子,叫起来像鸭子”(只要它有 sound 方法),Python 就会把它当鸭子看。调用方不需要关心对象的具体类型,只要它有这个方法就能调用。这就是所谓的“鸭子类型(Duck Typing)”。

方法重写和“重载”怎么区分

前面 Dog.sound()Cat.sound() 这种写法,属于方法重写(Override),也就是子类重新实现父类已经定义过的方法。

很多初学者还会顺手想到“重载(Overload)”。这里要特别注意:Python 没有 Java / C++ 那种仅靠参数个数或参数类型不同,就定义多个同名方法的传统重载机制。

例如下面这种写法,在 Python 里前面的定义会被后面的覆盖:

class Demo:
    def greet(self, name: str) -> str:
        return f"hello, {name}"

    def greet(self, name: str, age: int) -> str:
        return f"hello, {name}, age={age}"

所以在 Python 里,如果你想表达“同一个方法支持多种调用方式”,更常见的是这些做法:

  • 使用默认参数
  • 使用 *args**kwargs
  • 在函数内部自己判断参数类型或参数数量
class Greeter:
    def greet(self, name: str, age: int | None = None) -> str:
        if age is None:
            return f"hello, {name}"
        return f"hello, {name}, age={age}"

可以先这样区分:

  • 重写:子类改写父类已有方法
  • 重载:同名方法支持多种参数形式,但在 Python 里通常靠默认参数、可变参数等方式实现,而不是像 Java 那样写多个同名方法

__call__():让对象像函数一样调用

如果一个类实现了 __call__(),它的对象就可以像函数一样直接调用。

class Adder:
    def __init__(self, base: int = 0) -> None:
        self.base = base

    def __call__(self, value: int) -> int:
        return self.base + value


add_ten = Adder(10)
print(add_ten(5))

这个特性常见于:

  • 需要保留内部状态的可调用对象
  • 装饰器类
  • 一些框架里的“对象式处理器”

七、dataclass 与轻量数据建模

学完类、实例属性和继承以后,很自然会遇到一个问题:
如果一个类主要只是“装数据”,每次都手写 __init__ 会不会太重复?

这时就可以认识 dataclass

它之所以省力,一个关键前提就是:dataclass 会读取字段上的类型提示。 也就是说,前一篇里讲的 name: strtags: list[str] 这种写法,到这里不只是“说明一下类型”,还会参与类的字段定义。

from dataclasses import dataclass, field

@dataclass
class UserProfile:
    name: str
    age: int
    tags: list[str] = field(default_factory=list)

profile = UserProfile("alex", 18)
profile.tags.append("python")
print(profile)

它适合的场景是:

  • 结构稳定
  • 字段明确
  • 主要职责是承载数据,而不是封装复杂行为

什么时候用普通类,什么时候用 dataclass

  • 主要是业务行为和状态控制:优先普通类
  • 主要是描述一组固定字段:优先 dataclass

可以先把它理解成:dataclass 不是替代面向对象,而是给“数据对象”提供更省力的写法。

八、组合优于继承

很多初学者学到继承以后,容易下意识地“什么都想继承”。
但在日常业务代码里,更常见、更稳妥的思路往往是组合。

class Engine:
    def start(self) -> None:
        print("engine start")

class Car:
    def __init__(self, engine: Engine) -> None:
        self.engine = engine

    def run(self) -> None:
        self.engine.start()
        print("car running")

这里 Car 并没有继承 Engine,而是“拥有一个 Engine 对象”。

组合的好处通常是:

  • 关系更贴近真实业务
  • 耦合更低
  • 后续替换实现更灵活

经验上可以这样判断:

  • 表达“是一个”关系时,再考虑继承
  • 表达“有一个”关系时,优先考虑组合

九、静态方法、类方法、属性方法

静态方法 @staticmethod

class MathTool:
    @staticmethod
    def add(a, b):
        # 静态方法不依赖实例,也不依赖类状态,相当于普通的函数放在了类命名空间里
        return a + b

# 可以直接通过类名调用静态方法
print(MathTool.add(1, 2))

类方法 @classmethod

类方法的第一个参数固定为类本身(命名为 cls),而不是实例(self),这使得它可以直接通过类名调用,无需创建实例。适合处理类变量,或者封装“按某种规则创建对象”的逻辑。

class User:
    default_role = "member"

    @classmethod
    def build_default(cls, name):
        # cls 代表当前类,可以访问类变量
        return {"name": name, "role": cls.default_role}

# 通过类名调用类方法
print(User.build_default("alex"))

这里可以这样理解:

  • self 代表当前实例
  • cls 代表当前类
  • 如果逻辑依赖类变量,或者不需要先创建对象,就更适合写成类方法

classmethodstaticmethod 的区别

  • @classmethod 的第一个参数是类(cls),适合需要访问或修改类状态的场景(如工厂方法)。
  • @staticmethod 是另一个装饰器,它不绑定类或实例,纯粹是逻辑相关的工具函数(无 clsself 参数)。

可以把三种方法这样区分:

  • 实例方法:第一个参数是 self,操作某个具体对象的数据
  • 类方法:第一个参数是 cls,操作类级别的数据,或负责构造对象
  • 静态方法:既不关心 self,也不关心 cls,只是逻辑上放在类里面

属性方法 @property

class Student:
    def __init__(self, score):
        self._score = score

    @property
    def level(self):
        # 把方法伪装成只读属性,便于外部访问,同时隐藏内部计算逻辑
        return "A" if self._score >= 90 else "B"

student = Student(95)
# 像访问属性一样访问 level 方法
print(student.level)