核心篇 · Core
lv.2 核心
kp-017
面向对象基础(class)
1. 一句话定义
Python 的类是动态的命名空间:class 语句创建类对象,__init__ 初始化实例,方法只是"绑定到实例的普通函数",属性方法都存在字典里、随时可改。
2. 为什么重要
Python 中一切皆对象——连类本身也是对象(type 的实例)。理解"类是对象、方法是函数"这个朴素事实,能消除 OOP 的大部分神秘感,也是读懂框架源码的前提。
3. 前置知识
kp-010(dict——对象属性就是它)、kp-015(异常)。
4. 核心概念
python
class Book:
shelf = "A-1" # 类属性:所有实例共享
def __init__(self, title, price):
self.title = title # 实例属性:每个实例一份
self.price = price
def describe(self): # 实例方法:自动接收 self
return f"《{self.title}》 ¥{self.price}"
@classmethod
def from_json(cls, data): # 类方法:接收 cls,常做替代构造器
return cls(data["title"], data["price"])
@staticmethod
def is_valid_price(p): # 静态方法:不接 self/cls 的工具函数
return p >= 0- 三种方法:实例方法(self)、类方法(cls)、静态方法(无隐式参数)。
- 属性即 dict:
b.__dict__存实例属性;Book.__dict__存类成员。 - 约定而非强制:
_x内部约定、__x名字改写(name mangling),没有真正的 private。
5. 原理与机制
属性查找顺序:b.title → 先查 b.__dict__ → 再查 Book.__dict__ → 沿 MRO 上溯(kp-018);找不到 → __getattr__(若定义,kp-027)。
方法绑定:b.describe() 本质是 Book.describe(b)——函数通过描述符协议(kp-027)在访问时自动绑定第一个参数。理解这一点,self 不再是咒语。
6. 关键事实(模型/图示)
text
Book() 调用:
__call__(type 级)──► __new__(Book) 创建实例 ──► __init__(实例) 填充
属性读:实例 __dict__ → 类 __dict__(沿 MRO)→ __getattr__
属性写:直接进实例 __dict__(可变对象天生开放)7. 直观类比
类是图纸,实例是按图纸造出的产品;类属性是图纸上印的统一规格(所有产品一样),实例属性是每台机器出厂时填的序列号(各不相同)。Python 的特殊在于:图纸和产品都是实物(对象),你可以当场在图纸上加一行规格、在产品上贴一张新标签。
8. 实例与案例
python
# 类属性共享陷阱(kp-007 可变默认值的翻版)
class Team:
members = [] # ✗ 所有 Team 实例共享同一个列表!
t1, t2 = Team(), Team()
t1.members.append("a") # t2.members 也多了 "a"修复:在 __init__ 里 self.members = []。
9. 常见误区
- 把可变对象设为类属性 —— 上例;需要每实例独立的进
__init__。 - 以为
__x是 private —— 只是名字改写为_ClassName__x,外部仍可访问;这是"防止子类意外覆盖"的设施,不是访问控制。 - 方法里忘写 self 或调用时传 self —— 定义带 self、调用不传;
b.describe()不要写成b.describe(b)。 - 什么都塞进类 —— Python 支持"一个模块 + 一堆函数"的扁平风格;没有状态就别硬造类(YAGNI)。
10. 自测题
Book.from_json为什么要用@classmethod而不是普通方法?b = Book("x", 1),b.describe()的等价调用形式?- 类属性与实例属性查找顺序?
参考答案
- 类方法接收 cls,子类调用时自动传子类,替代构造器能正确返回子类实例。
Book.describe(b)。- 先实例
__dict__,再类(沿 MRO);都未命中则触发__getattr__。
11. 与其他知识点的关系
12. 延伸阅读
- Python 官方教程 §9 Classes