python从入门到精通-第4章: 面向对象 — 相似的表象,不同的灵魂

1 阅读36分钟

第4章: 面向对象 — 相似的表象,不同的灵魂

Java/Kotlin 开发者看到 Python 的 class 语法会觉得"这不就是一样的东西吗?"——然后掉进坑里。Python 的类系统表面上是经典的 OOP,底层却融合了元编程、描述符协议、MRO 线性化等 JVM 世界不存在的机制。类属性共享、self 显式传递、new/init 分离、多继承 MRO,每一个都是踩坑高发区。本章逐个拆解。


4.1 类定义与 self: 显式接收者

Java/Kotlin 对比

// Java: this 是隐式的,编译器自动传入
public class User {
    private String name;

    public User(String name) {
        this.name = name;  // this 隐式指向当前实例
    }

    public void greet() {
        System.out.println("Hi, " + this.name);
        // this 可省略,编译器自动解析
        System.out.println("Hi, " + name);
    }
}

// 调用: user.greet() → 编译器内部变成 User.greet(user)
// Kotlin: 和 Java 类似,this 隐式传递
class User(val name: String) {
    fun greet() {
        println("Hi, $name")      // this 可省略
        println("Hi, ${this.name}") // this 显式写
    }
}

Python 实现

class User:
    def __init__(self, name: str):
        self.name = name  # self 必须显式写!

    def greet(self):
        print(f"Hi, {self.name}")
        # 没有 self. 前缀的 name 会查找全局作用域,不会自动指向实例属性


# === self 的本质:它就是一个普通参数 ===
u = User("Alice")
u.greet()           # 常规调用
User.greet(u)       # 完全等价!self 就是 u

# 你甚至可以给它传别的对象(虽然通常不该这么做)
class Dog:
    pass

d = Dog()
d.name = "Rex"
User.greet(d)       # 输出: Hi, Rex — 因为 greet 只需要一个有 .name 属性的对象


# === self 不是关键字,只是约定 ===
class BadStyle:
    # 这能运行,但所有 Python 工具和开发者都会骂你
    def __init__(me, name):
        me.name = name

    def greet(me):
        print(f"Hi, {me.name}")

# PEP 8 规定用 self,cls 用于类方法。但解释器不强制。


# === 绑定方法 vs 未绑定方法 ===
u = User("Alice")
print(u.greet)       # <bound method User.greet of <User object>>
print(User.greet)    # <function User.greet at 0x...>  — 就是个普通函数

# bound method 自动把实例作为第一个参数
# unbound function 需要你手动传

核心差异

维度Java/KotlinPython
接收者this 隐式,编译器自动传入self 显式,必须手动声明
self/this 性质语言关键字普通参数名,只是约定
方法本质属于类的一部分类中定义的函数,调用时绑定实例
能否用其他名字不行能,但极其不推荐

常见陷阱

# 陷阱1: 忘记写 self
class Point:
    def __init__(self, x, y):
        self.x = x
        y = y          # 这只是创建了局部变量 y,不是实例属性!

    def get_y(self):
        return y        # NameError! y 不在作用域中


# 陷阱2: 类中调用方法忘记 self
class Calculator:
    def __init__(self, value):
        self.value = value

    def add(self, delta):
        self.value += delta

    def add_and_double(self, delta):
        add(delta)       # NameError! 必须写 self.add(delta)
        self.value *= 2

何时使用

  • 永远用 self 作为实例方法的第一个参数名
  • 永远用 cls 作为类方法的第一个参数名
  • 理解 obj.method(args) 等价于 Class.method(obj, args) 是理解 Python OOP 的一切基础

4.2 init vs 构造函数

Java/Kotlin 对比

// Java: 构造函数 = 分配内存 + 初始化,一步完成
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;   // 此时内存已分配,this 已可用
        this.age = age;
    }

    // 构造函数可以调用另一个构造函数
    public User(String name) {
        this(name, 0);  // this(...) 必须在第一行
    }
}
// Kotlin: 主构造函数 + init 块
class User(val name: String, val age: Int = 0) {
    // 主构造函数的参数可以直接声明为属性
    init {
        require(age >= 0) { "Age must be non-negative" }
    }
}

// 次构造函数
class User2 {
    val name: String
    val age: Int

    constructor(name: String) : this(name, 0)

    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

Python 实现

# === __new__ 和 __init__ 的分工 ===
class User:
    def __new__(cls, name, age=0):
        """真正的构造器:负责创建实例(分配内存)"""
        print(f"__new__ 被调用: cls={cls}")
        instance = super().__new__(cls)  # 调用 object.__new__ 分配内存
        return instance                  # 必须返回实例

    def __init__(self, name, age=0):
        """初始化器:负责填充实例属性"""
        print(f"__init__ 被调用: self={self}")
        self.name = name
        self.age = age


u = User("Alice", 30)
# 输出:
# __new__ 被调用: cls=<class 'User'>
# __init__ 被调用: self=<__main__.User object>


# === __new__ 返回其他类的实例时,__init__ 不会被调用 ===
class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print("创建新实例")
            cls._instance = super().__new__(cls)
        else:
            print("返回已有实例")
        return cls._instance

    def __init__(self):
        print("__init__ 被调用")
        self.data = []


a = Singleton()  # 输出: 创建新实例 → __init__ 被调用
b = Singleton()  # 输出: 返回已有实例 → __init__ 被调用(注意!__init__ 仍会被调用)
print(a is b)    # True


# === 更安全的单例模式 ===
class SafeSingleton:
    _instance = None
    _initialized = False

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        if not SafeSingleton._initialized:
            print("初始化")
            self.data = []
            SafeSingleton._initialized = True
        else:
            print("跳过重复初始化")


a = SafeSingleton()  # 初始化
b = SafeSingleton()  # 跳过重复初始化
print(a is b)        # True


# === __new__ 的实际用途:子类化不可变类型 ===
class UpperStr(str):
    """所有字符串自动转大写的 str 子类"""

    def __new__(cls, value=""):
        return super().__new__(cls, value.upper())

    # 不需要 __init__,str 是不可变的,属性在 __new__ 中设定


s = UpperStr("hello")
print(s)           # HELLO
print(type(s))     # <class '__main__.UpperStr'>
print(isinstance(s, str))  # True

核心差异

维度Java/KotlinPython
构造过程构造函数一步完成__new__(创建)+ __init__(初始化)两步
__new__ 等价物无(JVM 构造函数 = 两者合一)object.__new__(cls) 分配内存
构造函数链this() / super()__new__ 返回实例后自动调 __init__
单例实现多种设计模式__new__ 控制实例创建

常见陷阱

# 陷阱: __new__ 忘记返回实例
class Broken:
    def __new__(cls):
        super().__new__(cls)  # 忘记 return!
        # 返回 None → __init__ 收到 None → AttributeError

# 陷阱: __new__ 中返回非 cls 实例时 __init__ 行为
class A:
    def __new__(cls):
        return 42  # 返回 int

    def __init__(self):
        print("这会被调用吗?")  # 不会!因为返回的不是 cls 的实例

a = A()
print(type(a))  # <class 'int'>,a 就是 42

何时使用

  • 99% 的情况:只写 __init__,不用碰 __new__
  • 需要 __new__ 的场景
    • 单例模式
    • 子类化不可变类型(str, int, tuple, frozenset
    • 元类配合(高级用法)
    • 控制实例创建逻辑(如对象池、缓存)

4.3 实例属性 vs 类属性

Java/Kotlin 对比

// Java: static 字段属于类,实例字段属于实例,泾渭分明
public class Counter {
    private static int totalCount = 0;  // 类变量
    private int count = 0;              // 实例变量

    public Counter() {
        totalCount++;
        count++;
    }

    public static int getTotalCount() { return totalCount; }
    public int getCount() { return count; }
}
// Kotlin: companion object 存放类级别成员
class Counter {
    companion object {
        var totalCount: Int = 0  // 类变量
    }

    var count: Int = 0           // 实例变量

    constructor() {
        totalCount++
        count++
    }
}

Python 实现

# === 类属性 vs 实例属性 ===
class Dog:
    # 类属性:定义在类体中,方法之外
    species = "Canis familiaris"

    def __init__(self, name):
        # 实例属性:通过 self.xxx 赋值
        self.name = name


d1 = Dog("Rex")
d2 = Dog("Buddy")

print(d1.species)   # Canis familiaris — 通过实例访问类属性
print(d2.species)   # Canis familiaris
print(Dog.species)  # Canis familiaris — 通过类直接访问

d1.name = "Max"     # 只修改 d1 的实例属性
print(d1.name)      # Max
print(d2.name)      # Buddy — 互不影响


# === 陷阱:通过实例修改类属性会创建实例属性! ===
class TrickyCounter:
    count = 0  # 类属性

    def __init__(self):
        self.count += 1  # 这不是 += 类属性!


# 上面等价于:
# def __init__(self):
#     self.count = self.count + 1
# 步骤: 1. 读取 self.count → 找不到实例属性 → 找到类属性 count=0
#       2. 计算 0 + 1 = 1
#       3. 赋值给 self.count → 创建实例属性!类属性不变!

c1 = TrickyCounter()
c2 = TrickyCounter()
print(c1.count)       # 1(实例属性)
print(c2.count)       # 1(实例属性)
print(TrickyCounter.count)  # 0(类属性,从未被修改!)
print(c1.count is TrickyCounter.count)  # False


# === 正确做法:用类名修改类属性 ===
class CorrectCounter:
    count = 0

    def __init__(self):
        CorrectCounter.count += 1  # 通过类名修改类属性

    @classmethod
    def get_count(cls):
        return cls.count


c1 = CorrectCounter()
c2 = CorrectCounter()
print(CorrectCounter.count)  # 2


# === 属性查找链: 实例 → 类 → 基类 ===
class Animal:
    category = "animal"

class Pet(Animal):
    species = "pet"

class Dog(Pet):
    pass


d = Dog()
print(d.category)  # animal — 实例没有 → Dog 没有 → Pet 没有 → Animal 有
print(d.species)   # pet    — 实例没有 → Dog 没有 → Pet 有

# 通过实例赋值会创建实例属性,遮蔽类属性
d.category = "my pet"
print(d.category)  # my pet — 实例属性遮蔽了类属性
print(Dog.category)  # animal — 类属性不受影响
del d.category
print(d.category)  # animal — 删除实例属性后,查找链继续向上


# === 可变类属性的共享陷阱 ===
class Team:
    members = []  # 可变类属性!所有实例共享同一个列表

    def add_member(self, name):
        self.members.append(name)  # 修改的是共享的类属性


t1 = Team()
t2 = Team()
t1.add_member("Alice")
t2.add_member("Bob")
print(t1.members)  # ['Alice', 'Bob'] — t1 也看到了 Bob!
print(t1.members is t2.members)  # True — 同一个对象


# 正确做法:在 __init__ 中初始化可变属性
class FixedTeam:
    def __init__(self):
        self.members = []  # 每个实例有自己的列表

    def add_member(self, name):
        self.members.append(name)


t1 = FixedTeam()
t2 = FixedTeam()
t1.add_member("Alice")
t2.add_member("Bob")
print(t1.members)  # ['Alice']
print(t2.members)  # ['Bob']

核心差异

维度Java/KotlinPython
类属性声明static 关键字显式标记直接在类体中赋值,无关键字
属性查找编译时确定(静态绑定)运行时沿继承链查找(动态)
实例遮蔽类属性不可能(static 和实例字段是不同的命名空间)实例属性同名时遮蔽类属性
可变共享static 字段天然共享类属性中的可变对象天然共享,但容易忘记

常见陷阱

# 陷阱1: 默认参数是可变对象(和类属性共享是同一个问题)
def append_to(element, target=[]):
    target.append(element)
    return target

print(append_to(1))  # [1]
print(append_to(2))  # [1, 2] — 不是 [2]!默认列表在函数定义时创建,所有调用共享

# 正确做法
def append_to(element, target=None):
    if target is None:
        target = []
    target.append(element)
    return target

何时使用

  • 类属性:常量、所有实例共享的不可变数据、计数器
  • 实例属性(在 __init__ 中创建):每个实例独立的数据、可变对象
  • 铁律:可变对象(list, dict, set)永远在 __init__ 中初始化,不要作为类属性

4.4 _ 和 __ 访问控制

Java/Kotlin 对比

// Java: 严格的访问控制
public class User {
    public String name;         // 任何地方可访问
    protected int age;          // 同包 + 子类
    private String secret;      // 仅本类

    // getter/setter 控制访问
    private int _score;
    public int getScore() { return _score; }
    public void setScore(int score) {
        if (score >= 0) this._score = score;
    }
}
// Kotlin: 默认 public,private 严格隐藏
class User(val name: String) {
    protected var age: Int = 0
    private var secret: String = ""

    var score: Int = 0
        set(value) {
            if (value >= 0) field = value  // field 是 Kotlin 的 backing field
        }
}

Python 实现

# === Python 没有真正的 private,只有约定 ===
class User:
    def __init__(self, name, age, secret):
        self.name = name        # 公开属性
        self._age = age         # 约定私有:单下划线
        self.__secret = secret  # 名称改写:双下划线


u = User("Alice", 30, "password123")

# 公开属性:随意访问
print(u.name)          # Alice

# 单下划线:约定私有,但可以访问
print(u._age)          # 30 — 能访问,只是 IDE 和 linter 会警告

# 双下划线:名称改写(name mangling)
# print(u.__secret)   # AttributeError! — 但不是因为 private,而是名字被改了
print(u._User__secret)  # password123 — 改写后的名字,仍然可以访问


# === Name Mangling 详解 ===
class Parent:
    def __init__(self):
        self.__value = "parent"

    def __method(self):
        print("parent method")

    def public_method(self):
        self.__method()  # 内部调用会被自动改写为 _Parent__method


class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__value = "child"  # 会被改写为 _Child__value,不是 _Parent__value

    def __method(self):
        print("child method")


c = Child()
c.public_method()       # parent method — Parent 的 __method 是 _Parent__method
print(c._Parent__value)  # parent
print(c._Child__value)   # child
# 两个 __value 是不同的属性!mangling 用类名做命名空间隔离


# === @property: Pythonic 的 getter/setter ===
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius  # 约定私有

    @property
    def celsius(self):
        """getter — 像属性一样访问"""
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """setter — 像属性一样赋值,但可以加验证"""
        if value < -273.15:
            raise ValueError("温度不能低于绝对零度")
        self._celsius = value

    @property
    def fahrenheit(self):
        """只读计算属性 — 没有 setter"""
        return self._celsius * 9 / 5 + 32

    @property
    def kelvin(self):
        """只读计算属性"""
        return self._celsius + 273.15


t = Temperature(100)
print(t.celsius)      # 100 — 调用 getter
print(t.fahrenheit)   # 212.0 — 只读计算属性

t.celsius = 0         # 调用 setter
print(t.fahrenheit)   # 32.0

# t.fahrenheit = 100  # AttributeError: can't set attribute — 只读
# t.celsius = -300    # ValueError: 温度不能低于绝对零度


# === @property 的高级用法:延迟计算 ===
class Circle:
    def __init__(self, radius):
        self.radius = radius
        self._area = None

    @property
    def area(self):
        if self._area is None:
            print("计算面积...")
            self._area = 3.14159 * self.radius ** 2
        return self._area


c = Circle(5)
print(c.area)   # 计算面积... → 78.53975
print(c.area)   # 78.53975 — 缓存命中,不再计算
c.radius = 10
# 注意:修改 radius 不会自动失效缓存!需要更复杂的实现

核心差异

维度Java/KotlinPython
访问控制语言强制(private, protected, public无强制,只有约定(_, __
_ 单下划线无等价物约定私有,工具可警告但不阻止
__ 双下划线无等价物Name mangling,防止子类意外覆盖
Getter/Setter必须写方法@property 让属性访问和方法的验证逻辑共存
设计哲学"我们都是成年人" → 编译器强制"我们都是成年人" → 信任开发者

常见陷阱

# 陷阱: __ 双下划线不是真正的 private
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

a = BankAccount(1000)
# a.__balance = 0    # AttributeError — 但这只是创建了新属性
a.__balance = 0      # 实际上创建了 a.__balance,不是修改原来的
print(a.__balance)   # 0 — 这是新创建的属性
print(a._BankAccount__balance)  # 1000 — 原来的值没变!

# 正确修改:
a._BankAccount__balance = 0
print(a._BankAccount__balance)  # 0

何时使用

  • 无前缀:公开 API,外部应该使用的属性
  • _ 单下划线:内部实现细节,外部不应该使用(但可以)
  • __ 双下划线:仅当需要防止子类命名冲突时使用(不常见)
  • @property:需要验证逻辑、计算属性、或未来可能需要加验证时

4.5 多继承与 MRO (C3 线性化)

Java/Kotlin 对比

// Java: 只支持单继承 + 接口
public interface Flyable {
    void fly();
}

public interface Swimmable {
    void swim();
}

// 可以实现多个接口
public class Duck implements Flyable, Swimmable {
    @Override
    public void fly() { System.out.println("Flying"); }

    @Override
    public void swim() { System.out.println("Swimming"); }
}

// 不能继承多个类
// public class Duck extends Bird, Fish { }  // 编译错误!
// Kotlin: 同样单继承 + 多接口
interface Flyable { fun fly() }
interface Swimmable { fun swim() }

class Duck : Flyable, Swimmable {
    override fun fly() = println("Flying")
    override fun swim() = println("Swimming")
}

Python 实现

# === Python 支持多继承 ===
class Flyable:
    def fly(self):
        print(f"{self.__class__.__name__} is flying")

class Swimmable:
    def swim(self):
        print(f"{self.__class__.__name__} is swimming")

class Duck(Flyable, Swimmable):
    pass


d = Duck()
d.fly()   # Duck is flying
d.swim()  # Duck is swimming


# === 菱形继承问题 ===
class A:
    def greet(self):
        print("A")

class B(A):
    def greet(self):
        print("B")
        super().greet()

class C(A):
    def greet(self):
        print("C")
        super().greet()

class D(B, C):
    def greet(self):
        print("D")
        super().greet()


d = D()
d.greet()
# 输出: D → B → C → A
# MRO 保证每个类只被调用一次!

print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)


# === MRO (C3 线性化) 算法详解 ===
# 规则:
# 1. 子类在父类之前
# 2. 多个父类按声明顺序排列
# 3. 如果一个类在多个父类中都出现,只保留第一次出现的(从左到右)

class X: pass
class Y: pass
class A(X, Y): pass
class B(Y, X): pass

# A.__mro__ = (A, X, Y, object)
# B.__mro__ = (B, Y, X, object)

# 不能创建 C(A, B) — MRO 无法满足一致性!
# class C(A, B): pass  # TypeError: Cannot create a consistent method resolution order
# 原因: A 要求 X 在 Y 之前,B 要求 Y 在 X 之前,矛盾!


# === super() 无参调用 vs Java super ===
# Java: super 明确指向直接父类
# Python: super() 遵循 MRO,调用链中的下一个类

class Base:
    def __init__(self):
        print("Base.__init__")
        super().__init__()  # 调用 MRO 中下一个类的 __init__(即 object)

class Left(Base):
    def __init__(self):
        print("Left.__init__")
        super().__init__()  # 不是调用 Base!是调用 MRO 中下一个

class Right(Base):
    def __init__(self):
        print("Right.__init__")
        super().__init__()

class Child(Left, Right):
    def __init__(self):
        print("Child.__init__")
        super().__init__()  # 沿 MRO 链调用


print(Child.__mro__)
# (Child, Left, Right, Base, object)

c = Child()
# 输出:
# Child.__init__
# Left.__init__
# Right.__init__
# Base.__init__
# 每个类只被调用一次!这就是 super() + MRO 的威力


# === super() 的正确用法:协作式多继承 ===
class LoggerMixin:
    """混入类:给任何类添加日志能力"""
    def __init__(self, *args, **kwargs):
        print(f"[LOG] 初始化 {self.__class__.__name__}")
        super().__init__(*args, **kwargs)

class ValidatorMixin:
    """混入类:给任何类添加验证能力"""
    def validate(self, data):
        print(f"[VALIDATE] 验证 {data}")
        return True

class BaseModel(LoggerMixin):
    def __init__(self, **kwargs):
        super().__init__()
        for k, v in kwargs.items():
            setattr(self, k, v)

class UserModel(BaseModel, ValidatorMixin):
    pass


u = UserModel(name="Alice", age=30)
# [LOG] 初始化 UserModel
# [LOG] 初始化 BaseModel
u.validate({"name": "Bob"})  # [VALIDATE] 验证 {'name': 'Bob'}

核心差异

维度Java/KotlinPython
多继承禁止(只能多接口)完全支持
菱形继承不存在(单继承)MRO (C3 线性化) 解决
super指向直接父类沿 MRO 链调用下一个
接口interface 关键字用无实现的类或 ABC 模拟
Mixin无原生支持天然支持,通过多继承实现

常见陷阱

# 陷阱: super().__init__() 不写会导致基类不被初始化
class Base:
    def __init__(self):
        self.value = 42

class Child(Base):
    def __init__(self):
        pass  # 忘记调用 super().__init__()

c = Child()
# print(c.value)  # AttributeError! Base.__init__ 没被调用

# 陷阱: 多继承时 super().__init__() 的参数传递
class A:
    def __init__(self, a):
        self.a = a

class B:
    def __init__(self, b):
        self.b = b

# class C(A, B):
#     def __init__(self, a, b):
#         super().__init__(a)  # 只传了 a,B.__init__ 需要 b 但没收到

# 正确做法:用 *args, **kwargs
class C(A, B):
    def __init__(self, a, b):
        super().__init__(a, b)  # 或者分开调用

何时使用

  • 单继承:大多数情况,和 Java 一样
  • Mixin:添加可复用的功能(日志、序列化、验证等)
  • 避免:复杂的多继承层次(超过 2 层)
  • super():在多继承中必须用无参 super(),不要写 super(ParentClass, self)

4.6 魔术方法 (Dunder Methods)

Java/Kotlin 对比

// Java: Object 类的方法,用 @Override 重写
public class User {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return age == user.age && name.equals(user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
// Kotlin: data class 自动生成 toString/equals/hashCode/copy
data class User(val name: String, val age: Int)
// 自动获得: toString(), equals(), hashCode(), copy(), componentN()

Python 实现

# === __str__ vs toString() ===
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        """面向用户的字符串表示 → Java toString()"""
        return f"User(name={self.name!r}, age={self.age})"

    def __repr__(self):
        """面向开发者的字符串表示 → 用于调试"""
        return f"User({self.name!r}, {self.age})"


u = User("Alice", 30)
print(str(u))    # User(name='Alice', age=30) — 用户友好
print(repr(u))   # User('Alice', 30)           — 开发者友好,理想情况下 eval(repr(u)) == u
print(u)         # User(name='Alice', age=30) — print 默认调用 __str__

# 在容器中显示用 __repr__
print([u])       # [User('Alice', 30)] — list 调用的是 __repr__,不是 __str__


# === __eq__ 和 __hash__ vs equals/hashCode ===
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        """等价于 Java equals()"""
        if not isinstance(other, Point):
            return NotImplemented  # 注意:不是 False!
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        """等价于 Java hashCode()"""
        return hash((self.x, self.y))


p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2)   # True  — __eq__ 被调用
print(p1 == p3)   # False
print(p1 != p3)   # True  — != 自动取反

# hash 一致性契约(和 Java 相同):
# 1. a == b → hash(a) == hash(b)  (相等对象必须有相同哈希)
# 2. hash 值在对象生命周期内不变

# 可以放入 set 和作为 dict 的 key
points = {p1, p2, p3}
print(len(points))  # 2 — p1 和 p2 相等,set 去重

point_map = {p1: "origin"}
print(point_map[p2])  # origin — p2 和 p1 哈希相同且相等


# === 自定义对象的默认行为 ===
class DefaultUser:
    def __init__(self, name):
        self.name = name

u1 = DefaultUser("Alice")
u2 = DefaultUser("Alice")
print(u1 == u2)    # False — 默认比较 id(内存地址),和 Java 没重写 equals 一样
print(u1)          # <__main__.DefaultUser object at 0x...> — 默认 __repr__


# === __bool__ 和 __len__ ===
class Score:
    def __init__(self, value):
        self.value = value

    def __bool__(self):
        """真值判断 → Java 没有,Kotlin 的 operator fun Boolean"""
        return self.value > 60

    def __len__(self):
        """len() 函数 → Java size()"""
        return abs(self.value)


s = Score(80)
if s:               # 调用 __bool__
    print("及格")    # 及格
print(len(s))       # 80

s = Score(40)
if not s:           # 调用 __bool__
    print("不及格")  # 不及格


# === __iter__ 和 __next__: 迭代器协议 ===
class Countdown:
    """Java: 实现 Iterator<E> 接口
       Python: 实现 __iter__ 和 __next__"""

    def __init__(self, start):
        self.current = start

    def __iter__(self):
        """返回迭代器对象(通常是 self)"""
        return self

    def __next__(self):
        """返回下一个值,没有更多元素时抛 StopIteration"""
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1


for i in Countdown(5):
    print(i, end=" ")  # 1 2 3 4 5

# 也可以用 iter() 和 next()
c = Countdown(3)
it = iter(c)  # 调用 __iter__
print(next(it))  # 1 — 调用 __next__
print(next(it))  # 2
print(next(it))  # 3
# next(it)       # StopIteration


# 更 Pythonic 的方式:生成器
def countdown(start):
    while start > 0:
        yield start
        start -= 1

for i in countdown(5):
    print(i, end=" ")  # 1 2 3 4 5


# === __call__: 可调用对象 ===
class Multiplier:
    """让实例像函数一样调用 — Java/Kotlin 没有等价物"""

    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return x * self.factor


double = Multiplier(2)
triple = Multiplier(3)

print(double(5))   # 10 — 实例像函数一样调用
print(triple(5))   # 15
print(callable(double))  # True


# 实际应用:装饰器类
class Timer:
    """用 __call__ 实现装饰器"""
    import time

    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        import time
        start = time.perf_counter()
        result = self.func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{self.func.__name__} 耗时 {elapsed:.4f}s")
        return result


@Timer
def slow_function():
    import time
    time.sleep(0.1)
    return "done"

print(slow_function())  # slow_function 耗时 0.10xxs → done

核心差异

维度Java/KotlinPython
字符串表示toString()__str__(用户)+ __repr__(开发者)
相等比较equals() + hashCode()__eq__ + __hash__
真值判断无(必须显式判断)__bool__ / __len__
迭代Iterator<E> 接口__iter__ + __next__
可调用对象不支持(Lambda 是函数不是对象)__call__ 让实例可调用
默认行为equals 比较引用__eq__ 默认比较 id

常见陷阱

# 陷阱: 定义了 __eq__ 但没定义 __hash__
class Broken:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        return isinstance(other, Broken) and self.value == other.value

# b = Broken(1)
# {b}  # TypeError: unhashable type: 'Broken'
# 定义 __eq__ 后,__hash__ 被自动设为 None!

# 如果对象需要可哈希,必须同时定义 __hash__
# 如果对象不需要可哈希(比如可变对象),显式设 __hash__ = None

# 陷阱: __eq__ 返回 False 而不是 NotImplemented
class BadEq:
    def __eq__(self, other):
        if not isinstance(other, BadEq):
            return False  # 错误!应该返回 NotImplemented

# 返回 False 会阻止 Python 尝试反射操作 (other.__eq__(self))
# 返回 NotImplemented 让 Python 继续尝试其他比较方式

何时使用

  • __repr__:永远实现,用于调试
  • __str__:需要面向用户的字符串表示时
  • __eq__ + __hash__:对象需要比较或放入 set/dict 时
  • __bool__:对象需要在 if 语句中有语义时
  • __iter__ + __next__:需要自定义迭代行为时(但优先考虑生成器)
  • __call__:需要"有状态的函数"时(装饰器、策略模式、回调)

4.7 运算符重载

Java/Kotlin 对比

// Java: 不支持运算符重载
// String 的 + 是语言内置的特殊处理,不是运算符重载
String s = "a" + "b";  // 编译器特殊处理,不是运算符重载
// BigInteger a = ...; a.add(b)  — 只能用方法
// Kotlin: 支持 operator 关键字
data class Vec2(val x: Double, val y: Double) {
    operator fun plus(other: Vec2) = Vec2(x + other.x, y + other.y)
    operator fun minus(other: Vec2) = Vec2(x - other.x, y - other.y)
    operator fun times(scalar: Double) = Vec2(x * scalar, y * scalar)
    operator fun unaryMinus() = Vec2(-x, -y)
}

Python 实现

# === 算术运算符重载 ===
class Vec2:
    """二维向量 — 和 Kotlin 的 Vec2 对比"""

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __add__(self, other):
        """+ 运算符"""
        if isinstance(other, Vec2):
            return Vec2(self.x + other.x, self.y + other.y)
        return NotImplemented

    def __sub__(self, other):
        """- 运算符"""
        if isinstance(other, Vec2):
            return Vec2(self.x - other.x, self.y - other.y)
        return NotImplemented

    def __mul__(self, scalar):
        """* 运算符(向量 * 标量)"""
        if isinstance(scalar, (int, float)):
            return Vec2(self.x * scalar, self.y * scalar)
        return NotImplemented

    def __rmul__(self, scalar):
        """反射乘法(标量 * 向量)"""
        return self.__mul__(scalar)

    def __neg__(self):
        """一元 - 运算符"""
        return Vec2(-self.x, -self.y)

    def __abs__(self):
        """abs() 内置函数"""
        return (self.x ** 2 + self.y ** 2) ** 0.5

    def __eq__(self, other):
        if isinstance(other, Vec2):
            return self.x == other.x and self.y == other.y
        return NotImplemented

    def __repr__(self):
        return f"Vec2({self.x}, {self.y})"


v1 = Vec2(1, 2)
v2 = Vec2(3, 4)

print(v1 + v2)       # Vec2(4.0, 6.0)
print(v1 - v2)       # Vec2(-2.0, -2.0)
print(v1 * 3)        # Vec2(3.0, 6.0)
print(3 * v1)        # Vec2(3.0, 6.0) — __rmul__ 被调用
print(-v1)           # Vec2(-1.0, -2.0)
print(abs(v1))       # 2.23606797749979


# === __getitem__ 和 __setitem__: 索引访问 ===
class Matrix:
    """简化版矩阵 — 支持 m[i][j] 语法"""

    def __init__(self, rows, cols, default=0):
        self._data = [[default] * cols for _ in range(rows)]
        self.rows = rows
        self.cols = cols

    def __getitem__(self, key):
        """m[i] 返回第 i 行"""
        return self._data[key]

    def __setitem__(self, key, value):
        """m[i] = [...] 设置第 i 行"""
        self._data[key] = value

    def __len__(self):
        """len(m) 返回行数"""
        return self.rows

    def __repr__(self):
        return f"Matrix({self._data})"


m = Matrix(2, 3)
m[0] = [1, 2, 3]
m[1] = [4, 5, 6]
print(m[0])    # [1, 2, 3]
print(m[1][2]) # 6
print(len(m))  # 2


# === __contains__: in 运算符 ===
class IntSet:
    def __init__(self):
        self._items = set()

    def add(self, value):
        self._items.add(value)

    def __contains__(self, value):
        """'value in s' 语法"""
        return value in self._items


s = IntSet()
s.add(1)
s.add(2)
print(1 in s)   # True
print(3 in s)   # False


# === __enter__ 和 __exit__: 上下文管理器 ===
# Java: try-with-resources + AutoCloseable
# Python: with 语句 + __enter__/__exit__

class Timer:
    """计时上下文管理器"""
    import time

    def __enter__(self):
        import time
        self._start = time.perf_counter()
        return self  # as 变量绑定的对象

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.perf_counter() - self._start
        print(f"耗时 {elapsed:.4f}s")
        # 返回 True 会抑制异常,返回 False 或 None 异常正常传播
        return False


with Timer():
    import time
    time.sleep(0.1)
# 输出: 耗时 0.10xxs


# 更实用的例子:数据库连接模拟
class DBConnection:
    def __init__(self, url):
        self.url = url
        self.connected = False

    def connect(self):
        print(f"连接到 {self.url}")
        self.connected = True

    def disconnect(self):
        print("断开连接")
        self.connected = False

    def query(self, sql):
        if not self.connected:
            raise RuntimeError("未连接")
        print(f"执行: {sql}")

    def __enter__(self):
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.disconnect()
        if exc_type is not None:
            print(f"发生异常: {exc_val}")
        return False  # 不抑制异常


with DBConnection("postgresql://localhost/mydb") as conn:
    conn.query("SELECT * FROM users")
# 连接到 postgresql://localhost/mydb
# 执行: SELECT * FROM users
# 断开连接

# contextlib 简化写法(更 Pythonic)
from contextlib import contextmanager

@contextmanager
def db_connection(url):
    conn = DBConnection(url)
    conn.connect()
    try:
        yield conn  # yield 的值就是 as 变量
    finally:
        conn.disconnect()

with db_connection("postgresql://localhost/mydb") as conn:
    conn.query("SELECT 1")

核心差异

维度Java/KotlinPython
运算符重载Java 不支持;Kotlin 用 operatorPython 用 dunder 方法
反射运算符不需要__radd__, __rmul__ 等处理左操作数不支持的情况
索引访问不支持(用 get()/set()__getitem__/__setitem__
上下文管理AutoCloseable + try-with-resources__enter__/__exit__ + with
资源清理finallywith 语句(更优雅)

常见陷阱

# 陷阱: __getitem__ 的 key 类型
class MyDict:
    def __getitem__(self, key):
        return self._data[key]

# m = MyDict()
# m[1:3]  # key 是 slice(1, 3, None),不是 tuple!
# m[1, 2] # key 是 tuple (1, 2)

# 陷阱: 运算符重载不改变运算符优先级
# 你无法让 * 比 + 优先级低

# 陷阱: __exit__ 中返回 True 会吞掉异常
class Dangerous:
    def __enter__(self): return self
    def __exit__(self, *args): return True  # 所有异常都被吞掉!

with Dangerous():
    1 / 0  # 不会抛异常!
print("这行会执行")  # 异常被 __exit__ 吞掉了

何时使用

  • 算术运算符:数学类型(向量、矩阵、复数、分数)
  • __getitem__/__setitem__:自定义容器类型
  • __contains__:自定义集合类型
  • __enter__/__exit__:任何需要资源管理的场景(文件、连接、锁)
  • contextmanager:简单的上下文逻辑,比写类更简洁

4.8 @property: Pythonic 的 getter/setter

Java/Kotlin 对比

// Java: 标准的 getter/setter 模式
public class Rectangle {
    private double width;
    private double height;

    public double getWidth() { return width; }
    public void setWidth(double width) {
        if (width <= 0) throw new IllegalArgumentException("宽度必须为正");
        this.width = width;
    }

    public double getHeight() { return height; }
    public void setHeight(double height) {
        if (height <= 0) throw new IllegalArgumentException("高度必须为正");
        this.height = height;
    }

    public double getArea() { return width * height; }  // 计算属性
}
// Kotlin: property 语法更简洁
class Rectangle(var width: Double, var height: Double) {
    var area: Double
        get() = width * height  // 计算属性

    init {
        require(width > 0) { "宽度必须为正" }
        require(height > 0) { "高度必须为正" }
    }
}

Python 实现

# === 基础 property 用法 ===
class Rectangle:
    def __init__(self, width, height):
        self.width = width   # 调用 setter
        self.height = height

    @property
    def width(self):
        """getter"""
        return self._width

    @width.setter
    def width(self, value):
        """setter — 带验证"""
        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 area(self):
        """只读计算属性 — 没有 setter"""
        return self._width * self._height

    @property
    def perimeter(self):
        """只读计算属性"""
        return 2 * (self._width + self._height)


r = Rectangle(3, 4)
print(r.width)     # 3 — 调用 getter
print(r.area)      # 12 — 计算属性
print(r.perimeter) # 14

r.width = 5        # 调用 setter
print(r.area)      # 20

# r.area = 100     # AttributeError: can't set attribute — 只读
# r.width = -1     # ValueError: 宽度必须为正


# === property 的优势:从公开属性平滑迁移 ===
# 版本1: 简单的公开属性
class UserV1:
    def __init__(self, name):
        self.name = name  # 公开属性

u1 = UserV1("Alice")
print(u1.name)  # Alice

# 版本2: 需要加验证,但不破坏已有代码
class UserV2:
    def __init__(self, name):
        self.name = name  # 外部代码不变!

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value or not value.strip():
            raise ValueError("名字不能为空")
        self._name = value.strip()

u2 = UserV2("Alice")
print(u2.name)  # Alice — 外部代码完全不变!
# u2.name = ""  # ValueError


# === 只读属性 ===
class Config:
    def __init__(self, db_url):
        self._db_url = db_url

    @property
    def db_url(self):
        """只读 — 外部无法修改"""
        return self._db_url


config = Config("postgresql://localhost/mydb")
print(config.db_url)  # postgresql://localhost/mydb
# config.db_url = "..."  # AttributeError — 没有 setter


# === property 的 deleter ===
class CachedData:
    def __init__(self):
        self._cache = {}

    @property
    def cache(self):
        return self._cache

    @cache.deleter
    def cache(self):
        """del obj.cache 时调用"""
        print("清除缓存")
        self._cache.clear()


data = CachedData()
data.cache["key"] = "value"
print(data.cache)  # {'key': 'value'}
del data.cache     # 清除缓存
print(data.cache)  # {}


# === 用 property 实现懒加载 ===
class HeavyResource:
    def __init__(self):
        self._data = None

    @property
    def data(self):
        if self._data is None:
            print("加载重量级数据...")
            self._data = [i ** 2 for i in range(100000)]
        return self._data


resource = HeavyResource()
print("资源已创建,数据尚未加载")
print(len(resource.data))  # 加载重量级数据... → 100000
print(len(resource.data))  # 100000 — 缓存命中

核心差异

维度JavaKotlinPython
Getter/Setter必须写方法property 语法糖@property 装饰器
访问方式getWidth().width.width
只读属性只写 getterval / 无 setter只写 @property 不写 setter
计算属性getArea() 方法val area get() = ...@property 无 setter
从公开属性迁移必须改调用方不需要(本来就是属性)不需要(@property 兼容属性访问)

常见陷阱

# 陷阱: __init__ 中 self.name = name 会调用 setter
class User:
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("名字不能为空")
        self._name = value

    def __init__(self, name):
        self.name = name  # 调用 setter!如果 name="" 会抛异常

# 陷阱: property 不能和同名的实例属性共存
class Broken:
    @property
    def x(self):
        return self._x

    def __init__(self):
        self.x = 10  # 这会覆盖 property!self.x 现在是 int 10
        # 之后 Broken.x 就不再是 property 了(实例属性遮蔽了类属性)

何时使用

  • 需要验证:setter 中加校验逻辑
  • 计算属性:依赖其他属性动态计算
  • 只读属性:只提供 getter 不提供 setter
  • 懒加载:首次访问时计算并缓存
  • 向后兼容:从公开属性迁移到受控访问,不破坏调用方

4.9 slots: 内存优化与属性锁定

Java/Kotlin 对比

// Java: 没有直接等价物
// JVM 的对象内存布局由 JVM 实现,开发者无法控制
// 对象头 + 实例字段 + 对齐填充
// Java 对象本身不支持动态添加字段(反射除外)

public class Point {
    private int x;
    private int y;
    // 不能在运行时添加新字段
}
// Kotlin: 同样没有等价物
// 但 data class 有固定属性,天然不支持动态属性
data class Point(val x: Int, val y: Int)

Python 实现

# === 默认情况下,Python 对象使用 __dict__ 存储属性 ===
class NormalPoint:
    def __init__(self, x, y):
        self.x = x
        self.y = y


p = NormalPoint(1, 2)
print(p.__dict__)          # {'x': 1, 'y': 2}
p.z = 3                    # 可以动态添加属性!
print(p.__dict__)          # {'x': 1, 'y': 2, 'z': 3}
print(hasattr(p, '__dict__'))  # True


# === __slots__ 禁用 __dict__,固定属性集合 ===
class SlottedPoint:
    __slots__ = ('x', 'y')  # 声明允许的属性名

    def __init__(self, x, y):
        self.x = x
        self.y = y


p = SlottedPoint(1, 2)
print(p.x, p.y)  # 1 2

# p.z = 3  # AttributeError: 'SlottedPoint' object has no attribute 'z'
# 没有 __dict__,不能添加动态属性
print(hasattr(p, '__dict__'))  # False


# === 内存对比 ===
import sys

class Normal:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

class Slotted:
    __slots__ = ('x', 'y', 'z')
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

n = Normal(1, 2, 3)
s = Slotted(1, 2, 3)

print(f"Normal 对象大小: {sys.getsizeof(n)} bytes")
print(f"Slotted 对象大小: {sys.getsizeof(s)} bytes")
# 典型输出:
# Normal 对象大小: 56 bytes
# Slotted 对象大小: 48 bytes
# 单个对象差异不大,但 __slots__ 还省去了 __dict__ 的开销

# 大量对象时差异更明显
normal_objects = [Normal(i, i+1, i+2) for i in range(100000)]
slotted_objects = [Slotted(i, i+1, i+2) for i in range(100000)]

import tracemalloc
tracemalloc.start()

normal_objects = [Normal(i, i+1, i+2) for i in range(100000)]
_, normal_size = tracemalloc.get_traced_memory()
tracemalloc.stop()

tracemalloc.start()
slotted_objects = [Slotted(i, i+1, i+2) for i in range(100000)]
_, slotted_size = tracemalloc.get_traced_memory()
tracemalloc.stop()

print(f"Normal 100K 对象: {normal_size / 1024 / 1024:.1f} MB")
print(f"Slotted 100K 对象: {slotted_size / 1024 / 1024:.1f} MB")
print(f"节省: {(1 - slotted_size / normal_size) * 100:.1f}%")
# 典型输出: 节省 40-60%


# === __slots__ 的继承行为 ===
class Base:
    __slots__ = ('x',)

class Child(Base):
    __slots__ = ('y',)  # 子类声明自己的 slots

c = Child()
c.x = 1
c.y = 2
# c.z = 3  # AttributeError

# 如果子类不声明 __slots__,会自动获得 __dict__
class LooseChild(Base):
    pass  # 没有 __slots__

lc = LooseChild()
lc.x = 1
lc.z = 99  # 可以!因为 LooseChild 有 __dict__

核心差异

维度Java/KotlinPython (默认)Python (slots)
动态属性不支持支持(__dict__不支持
内存开销固定较大(__dict__ 哈希表)较小(描述符数组)
属性访问速度中等(哈希查找)快(描述符查找)
序列化框架处理__dict__ 直接序列化需要显式处理

常见陷阱

# 陷阱: __slots__ 和默认值
class Broken:
    __slots__ = ('x', 'y')
    z = 0  # 这是类属性,不是实例属性!所有实例共享

# 陷阱: __slots__ 和弱引用
import weakref
class Slotted:
    __slots__ = ('x',)

s = Slotted()
# weakref.ref(s)  # TypeError: cannot create weak reference to 'Slotted' object
# 解决: 在 __slots__ 中加 '__weakref__'

class WeakSlotted:
    __slots__ = ('x', '__weakref__')  # 允许弱引用

s = WeakSlotted()
ref = weakref.ref(s)  # OK

何时使用

  • 大量对象(数万以上):显著减少内存
  • 属性固定:不需要动态添加属性的场景
  • 性能敏感:属性访问更快
  • 不适用:需要动态属性、或与依赖 __dict__ 的库(如某些 ORM)配合时

4.10 抽象基类 ABC vs 接口/抽象类

Java/Kotlin 对比

// Java: interface + 抽象类
public interface Drawable {
    void draw();                    // 抽象方法
    default void resize(double f) { // 默认方法 (Java 8+)
        System.out.println("默认缩放");
    }
}

public abstract class Shape implements Drawable {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // 抽象方法:子类必须实现
    public abstract double area();

    // 具体方法:子类可以直接使用
    public String getColor() { return color; }
}

public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public void draw() { System.out.println("画圆"); }

    @Override
    public double area() { return Math.PI * radius * radius; }
}
// Kotlin: interface + abstract class
interface Drawable {
    fun draw()
    fun resize(f: Double) { println("默认缩放") }  // 默认实现
}

abstract class Shape(val color: String) : Drawable {
    abstract fun area(): Double
}

class Circle(color: String, val radius: Double) : Shape(color) {
    override fun draw() = println("画圆")
    override fun area() = Math.PI * radius * radius
}

Python 实现

from abc import ABC, abstractmethod


# === 抽象基类 ===
class Drawable(ABC):
    """类似 Java interface"""

    @abstractmethod
    def draw(self):
        """抽象方法:子类必须实现"""
        pass

    def resize(self, factor):
        """具体方法:子类可以直接使用(类似 Java default method)"""
        print(f"默认缩放 {factor}x")


class Shape(Drawable, ABC):
    """抽象类:有构造函数和状态"""

    def __init__(self, color):
        self.color = color

    @abstractmethod
    def area(self):
        """抽象方法"""
        pass


class Circle(Shape):
    """具体类"""

    def __init__(self, color, radius):
        super().__init__(color)
        self.radius = radius

    def draw(self):
        print(f"画一个 {self.color} 的圆")

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


# === 抽象类的实例化检查 ===
# d = Drawable()  # TypeError: Can't instantiate abstract class Drawable with abstract method draw
# s = Shape("red") # TypeError: Can't instantiate abstract class Shape with abstract method area

c = Circle("red", 5)
c.draw()        # 画一个 red 的圆
print(c.area()) # 78.53981633974483
c.resize(2)     # 默认缩放 2x


# === 抽象属性 ===
class Base(ABC):
    @property
    @abstractmethod
    def name(self):
        """抽象只读属性"""
        pass

    @name.setter
    @abstractmethod
    def name(self, value):
        """抽象可写属性"""
        pass

    @abstractmethod
    def greet(self):
        pass


class Impl(Base):
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    def greet(self):
        print(f"Hello, {self._name}")


# === 抽象类方法 ===
class Plugin(ABC):
    @classmethod
    @abstractmethod
    def from_config(cls, config):
        """抽象类方法:子类必须实现"""
        pass


class MyPlugin(Plugin):
    @classmethod
    def from_config(cls, config):
        return cls(config.get("name"))


# === 注册具体类 ===
class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

    @classmethod
    def register(cls, subclass):
        """手动注册虚拟子类"""
        print(f"注册: {subclass.__name__}")

# 不继承也能注册(用于无法修改第三方类的情况)
class Robot:
    def speak(self):
        print("Beep boop")

Animal.register(Robot)
print(isinstance(Robot(), Animal))  # True — 虚拟子类

核心差异

维度JavaKotlinPython
接口interfaceinterfaceABC + @abstractmethod
抽象类abstract classabstract classABC 子类
默认实现default 方法直接实现普通方法
抽象属性@property + @abstractmethod
多继承多接口多接口多 ABC
实例化检查编译时编译时运行时(TypeError
虚拟子类register()

常见陷阱

# 陷阱: 抽象方法在子类中必须实现,即使子类也是抽象的
# (如果子类不实现,它仍然是抽象的,不能实例化)

# 陷阱: @abstractmethod 和其他装饰器的顺序
class Base(ABC):
    @property
    @abstractmethod  # @abstractmethod 必须在最内层
    def x(self):
        pass

    # 错误写法:
    # @abstractmethod
    # @property  # 顺序反了,不会生效
    # def x(self):
    #     pass

何时使用

  • ABC + @abstractmethod:需要强制子类实现某些方法时
  • 抽象属性:需要强制子类提供某些属性时
  • register():需要让第三方类被视为子类时(鸭子类型 + 类型检查)
  • vs Protocol:需要运行时实例化检查用 ABC;只需要静态类型检查用 Protocol

4.11 Protocol (3.8+): 结构化子类型

Java/Kotlin 对比

// Java: 必须显式 implements
public interface Drawable {
    void draw();
}

public class Circle {
    public void draw() { System.out.println("画圆"); }
    // Circle 没有声明 implements Drawable
    // 不能当作 Drawable 使用!
}

// void render(Drawable d) { d.draw(); }
// render(new Circle());  // 编译错误!
// Kotlin: 同样必须显式实现
interface Drawable {
    fun draw()
}

class Circle {
    fun draw() = println("画圆")
    // 没有 : Drawable,不能当作 Drawable
}

Python 实现

from typing import Protocol, runtime_checkable


# === 基础 Protocol ===
class Drawable(Protocol):
    """结构化子类型:只要类有 draw() 方法,就是 Drawable
       不需要显式继承或声明!"""

    def draw(self) -> None:
        ...


class Circle:
    def draw(self):
        print("画圆")

class Square:
    def draw(self):
        print("画正方形")

class Text:
    def render(self):
        print("渲染文本")


def render(obj: Drawable):
    """接受任何有 draw() 方法的对象"""
    obj.draw()


render(Circle())   # 画圆 — Circle 没有 extends Drawable,但可以传入!
render(Square())   # 画正方形
# render(Text())   # 类型检查器会报错,但运行时不会(除非用 runtime_checkable)


# === @runtime_checkable: 运行时检查 ===
@runtime_checkable
class Closeable(Protocol):
    def close(self) -> None:
        ...


class File:
    def close(self):
        print("关闭文件")

class Socket:
    def close(self):
        print("关闭连接")

class NotCloseable:
    pass


print(isinstance(File(), Closeable))     # True
print(isinstance(Socket(), Closeable))   # True
print(isinstance(NotCloseable(), Closeable))  # False

# 注意: @runtime_checkable 只检查方法是否存在,不检查签名!
class BadClose:
    def close(self, x, y):  # 签名不匹配
        pass

print(isinstance(BadClose(), Closeable))  # True — 只检查名字,不检查参数


# === Protocol 的属性检查 ===
class Named(Protocol):
    name: str  # 只需要有一个 name 属性


class User:
    def __init__(self, name):
        self.name = name


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


def get_name(obj: Named) -> str:
    return obj.name

print(get_name(User("Alice")))   # Alice
print(get_name(Product("Book", 10)))  # Book


# === Protocol vs ABC 对比 ===
from abc import ABC, abstractmethod

class DrawableABC(ABC):
    @abstractmethod
    def draw(self):
        pass

class DrawableProtocol(Protocol):
    def draw(self):
        ...

class MyCircle:
    def draw(self):
        print("画圆")

# ABC: 必须显式继承
# isinstance(MyCircle(), DrawableABC)  # False

# Protocol: 结构匹配
isinstance(MyCircle(), DrawableProtocol)  # True(如果 @runtime_checkable)


# === 组合 Protocol ===
class Readable(Protocol):
    def read(self, size: int = -1) -> bytes:
        ...

class Writable(Protocol):
    def write(self, data: bytes) -> int:
        ...

class ReadWrite(Protocol):
    """组合 Protocol"""
    def read(self, size: int = -1) -> bytes:
        ...
    def write(self, data: bytes) -> int:
        ...


class Buffer:
    def read(self, size=-1):
        return b"data"

    def write(self, data):
        return len(data)


def process(rw: ReadWrite):
    rw.write(rw.read())

process(Buffer())  # OK — Buffer 结构匹配 ReadWrite

核心差异

维度Java/KotlinPython ABCPython Protocol
子类型关系名义子类型(显式声明)名义子类型结构化子类型(隐式)
检查时机编译时运行时(实例化)静态类型检查 + 可选运行时
第三方类必须修改源码或继承register()自动匹配
设计哲学"声明你是什么""声明你是什么""你有什么能力"

常见陷阱

# 陷阱: @runtime_checkable 的 isinstance 检查是近似的
# 它只检查方法/属性是否存在,不检查类型签名

# 陷阱: Protocol 不能有运行时副作用
class Bad(Protocol):
    def method(self):
        print("这行不会被执行")  # Protocol 的方法体被忽略

# 陷阱: Protocol 的类属性会被检查
@runtime_checkable
class HasName(Protocol):
    name: str  # 这是类属性,不是实例属性

class NoName:
    def __init__(self):
        self.name = "test"  # 实例属性

# isinstance(NoName(), HasName)  # 可能是 False,因为检查的是类属性

何时使用

  • Protocol:定义接口但不要求显式继承,特别是处理第三方库的类时
  • @runtime_checkable:需要在运行时做 isinstance 检查时
  • ABC:需要强制子类实现方法,且需要运行时实例化保护时
  • 经验法则:优先用 Protocol(更灵活),需要运行时保护时用 ABC

4.12 init_subclass 与类钩子

Java/Kotlin 对比

// Java: 没有直接等价物
// 最接近的是注解处理器 (Annotation Processor)
// 但那是在编译时工作的,不是运行时

// Java 中实现插件注册通常需要:
// 1. 反射扫描包
// 2. ServiceLoader (SPI)
// 3. 或手动注册

// ServiceLoader 方式:
// META-INF/services/com.example.Plugin 文件中列出实现类
public interface Plugin {
    String name();
    void execute();
}
// Kotlin: 同样没有直接等价物
// 可以用注解 + KSP (Kotlin Symbol Processing) 实现类似效果

Python 实现

# === __init_subclass__: 父类在子类定义时自动执行钩子 ===
class PluginBase:
    """插件基类:自动注册所有子类"""

    # 类属性:存储所有注册的插件
    _registry: dict[str, type] = {}

    def __init_subclass__(cls, **kwargs):
        """每当有类继承 PluginBase 时,这个方法会被自动调用"""
        super().__init_subclass__(**kwargs)
        # 自动注册子类
        PluginBase._registry[cls.__name__] = cls
        print(f"[注册] 插件 {cls.__name__}")

    @classmethod
    def get_plugin(cls, name: str):
        """按名称获取插件类"""
        plugin_cls = cls._registry.get(name)
        if plugin_cls is None:
            raise KeyError(f"未知插件: {name}")
        return plugin_cls

    @classmethod
    def list_plugins(cls):
        """列出所有已注册插件"""
        return list(cls._registry.keys())


# 定义子类时自动注册
class EmailPlugin(PluginBase):
    def send(self, to, message):
        print(f"发送邮件给 {to}: {message}")

class SMSPlugin(PluginBase):
    def send(self, to, message):
        print(f"发送短信给 {to}: {message}")

class PushPlugin(PluginBase):
    def send(self, to, message):
        print(f"发送推送给 {to}: {message}")

# 输出:
# [注册] 插件 EmailPlugin
# [注册] 插件 SMSPlugin
# [注册] 插件 PushPlugin

print(PluginBase.list_plugins())
# ['EmailPlugin', 'SMSPlugin', 'PushPlugin']

# 通过名称获取并使用
plugin_cls = PluginBase.get_plugin("SMSPlugin")
plugin = plugin_cls()
plugin.send("13800138000", "验证码: 1234")
# 发送短信给 13800138000: 验证码: 1234


# === __init_subclass__ 带参数 ===
class Validator:
    """验证器基类:子类声明自己验证的字段"""

    def __init_subclass__(cls, field: str = None, **kwargs):
        super().__init_subclass__(**kwargs)
        if field:
            cls._validated_field = field
            print(f"验证器 {cls.__name__} 监控字段: {field}")

    def validate(self, data):
        field = getattr(self, '_validated_field', None)
        if field and field not in data:
            raise ValueError(f"缺少必填字段: {field}")
        return True


class UserValidator(Validator, field="username"):
    pass

class EmailValidator(Validator, field="email"):
    pass

# 输出:
# 验证器 UserValidator 监控字段: username
# 验证器 EmailValidator 监控字段: email

uv = UserValidator()
uv.validate({"username": "alice"})   # True
# uv.validate({"email": "a@b.com"})  # ValueError: 缺少必填字段: username


# === 实战:事件处理系统 ===
class EventEmitter:
    """事件发射器:子类自动注册事件处理器"""

    _handlers: dict[str, dict[str, callable]] = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        # 扫描子类中所有以 on_ 开头的方法,自动注册为事件处理器
        for attr_name in dir(cls):
            if attr_name.startswith("on_"):
                event_name = attr_name[3:]  # 去掉 on_ 前缀
                handler = getattr(cls, attr_name)
                if callable(handler):
                    if event_name not in EventEmitter._handlers:
                        EventEmitter._handlers[event_name] = {}
                    EventEmitter._handlers[event_name][cls.__name__] = handler
                    print(f"[事件] {cls.__name__}.on_{event_name} 已注册")

    @classmethod
    def emit(cls, event_name: str, *args, **kwargs):
        """触发事件"""
        handlers = cls._handlers.get(event_name, {})
        for name, handler in handlers.items():
            handler(None, *args, **kwargs)  # None 是占位实例


class UserEvents(EventEmitter):
    def on_login(self, user_id):
        print(f"[{self.__class__.__name__}] 用户 {user_id} 登录")

    def on_logout(self, user_id):
        print(f"[{self.__class__.__name__}] 用户 {user_id} 登出")

class AdminEvents(EventEmitter):
    def on_login(self, user_id):
        print(f"[{self.__class__.__name__}] 管理员 {user_id} 登录(需审计)")

# 输出:
# [事件] UserEvents.on_login 已注册
# [事件] UserEvents.on_logout 已注册
# [事件] AdminEvents.on_login 已注册

EventEmitter.emit("login", 1001)
# [UserEvents] 用户 1001 登录
# [AdminEvents] 管理员 1001 登录(需审计)

EventEmitter.emit("logout", 1001)
# [UserEvents] 用户 1001 登出


# === __init_subclass__ vs 元类 ===
# __init_subclass__ 是 Python 3.6+ 引入的,比元类更简单
# 适合: 子类注册、自动配置、约束检查
# 元类适合: 更复杂的类创建控制(如 Django ORM 的 ModelBase)

# 简单判断标准:
# 如果能用 __init_subclass__ 解决,就不要用元类

核心差异

维度Java/KotlinPython
类定义钩子无(注解处理器是编译时)__init_subclass__(运行时)
插件注册SPI / 反射扫描 / 手动注册自动注册,子类定义即注册
元编程注解处理器 / KSP__init_subclass__ / 元类 / 描述符
复杂度高(需要额外工具链)低(纯 Python,无需额外工具)

常见陷阱

# 陷阱: __init_subclass__ 在类定义时执行,不是实例化时
class Base:
    def __init_subclass__(cls):
        print(f"定义子类: {cls.__name__}")

# print("开始定义子类")  # 这行先执行
# class Child(Base):     # 然后这行触发 __init_subclass__
#     pass
# print("子类定义完毕")

# 陷阱: 忘记调用 super().__init_subclass__(**kwargs)
# 如果有多层继承,不调用 super 会中断钩子链
class Middle(Base):
    def __init_subclass__(cls, **kwargs):
        # 忘记 super().__init_subclass__(**kwargs)
        # Base 的钩子不会被调用!
        pass

何时使用

  • 插件系统:自动发现和注册插件
  • 框架开发:Django REST Framework 的序列化器、Pydantic 的模型验证
  • 事件系统:自动注册事件处理器
  • 约束检查:确保子类满足某些条件
  • 经验法则:能用 __init_subclass__ 就不用元类

本章总结

知识点Java/Kotlin 等价物Python 关键区别
selfthis(隐式)显式参数,不是关键字
__init__构造函数只是初始化器,__new__ 才是构造器
类属性static 字段可被实例属性遮蔽,可变对象共享陷阱
_ / __private / protected只是约定,__ 触发 name mangling
多继承禁止支持,MRO (C3) 解决菱形继承
super()指向父类沿 MRO 链调用下一个
魔术方法toString()/equals()更丰富,__call__/__iter__
运算符重载Java 不支持/Kotlin operatordunder 方法,更灵活
@propertygetter/setter属性访问语法 + 验证逻辑
__slots__无等价物内存优化 + 属性锁定
ABCinterface/abstract class@abstractmethod,运行时检查
Protocol无等价物结构化子类型,鸭子类型的静态版本
__init_subclass__无等价物类定义钩子,插件系统的优雅实现

一句话总结:Python 的类系统表面看起来和 Java/Kotlin 类似,但底层机制完全不同。理解 self 的本质、__new__/__init__ 的分离、MRO 线性化、以及描述符协议,是避免踩坑的关键。