Python 的 OOP 比 Java 松弛得多:没有接口关键字、没有访问修饰符(下划线靠自觉)、方法就是普通的函数对象。本章讲 Python OOP 的骨架(class、self、继承)和灵魂(鸭子类型),以及「Pythonic 的 OOP」和 Java 式 OOP 的哲学差异。Java 课第 7、8 章的概念在此都有对照物,差异处才是重点。
class BankAccount:
def __init__(self, owner, balance=0): # 构造器(Java 的 constructor)
self.owner = owner # 实例属性
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
acc = BankAccount("思源", 100) # 调用类 = 构造(没有 new!)
acc.deposit(50) # 方法调用,self 自动传
self 是 Java 的 this 的显式版:每个方法的第一个参数就是对象自己,Python 不藏(acc.deposit(50) 实为 BankAccount.deposit(acc, 50),计组课第 12 章「C 结构体函数指针手动传 &v」的同款机制,Python 只是语法糖更甜)。__init__ 是初始化钩子(new 分配之后调用);没有 Java 式的「重载构造器」——用默认参数(第 3 章)替代。
属性随写随有(动态):acc.currency = "CNY" 直接给实例添字段。纪律:在 __init__ 里把全部属性声明齐(读者一眼看全;漏声明是「用到才炸」的隐性炸弹)。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal): # 单继承:括号里写父类
def speak(self): # 重写(没有 @Override 注解)
return f"{self.name}: 汪!"
class Puppy(Dog):
def __init__(self, name):
super().__init__(name) # super() 不用写参数(3.0+)
def speak(self):
return super().speak() + "(奶声)"
for a in [Dog("旺财"), Puppy("小旺")]:
print(a.speak()) # 多态:调各自的重写
语法全对齐 Java(extends→括号、@Override 免了、super() 更短);多态靠运行时查找(方法在类里找不到就沿父类链找,Python 3 的 MRO 线性化处理多继承顺序,数据结构课的拓扑排序思想在类图上跑)。Python 支持多继承(Java 不许),威力大坑也大(菱形继承,MRO 解决但不建议靠它设计);经验:组合优先,继承最多一层(第 5 章软工的设计模式同款结论)。
「伪私有」约定:属性前加双下划线 self.__secret 触发名字改写(_ClassName__secret),防子类误撞;单下划线 _x 是纯约定「内部使用」。全靠自觉,不像 Java 的 private 由编译器把关——Python 的信任哲学:我们都是成年人。
「如果它走起来像鸭子、叫起来像鸭子,那它就是鸭子。」Python 不检查「你是什么类」,只检查「你能不能做这件事」:
class Duck:
def quack(self): return "嘎"
class Robot: # 和 Duck 无任何继承关系!
def quack(self): return "电子嘎"
for thing in [Duck(), Robot()]:
print(thing.quack()) # 都能 quack,都算鸭子
对比 Java 课第 8 章:想多态必须 implements 接口;Python 看方法名不看血统。len(x) 对任何实现了 __len__ 的对象都工作,file-like 对象(任何有 read() 的东西)能互相替换(第 5 章「OpenAI 的响应对象换成 StringIO」的工程自由)。鸭子类型的红利:少写一堆接口层,灵活换实现;代价:契约靠文档和测试(鸭子类型 + 类型注解(第 7 章)+ mypy 是现代 Python 的组合拳)。
Python 官方的「协议」(protocol)是鸭子类型的形式化:__len__、__iter__、__enter__ 这些双下划线方法(魔法方法,第 9 章)就是语言级的「接口」——你的对象实现了它们,就自动获得了对应的能力(可求长、可迭代、可用于 with)。
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(3, 4) # 自动生成 __init__
print(p) # Point(x=3, y=4):自动 __repr__
p == Point(3, 4) # True:自动 __eq__
@dataclass(装饰器,第 9 章)一行生成 __init__/__repr__/__eq__,消灭「字段 + 构造 + getter + repr」的样板(Java 课第 7 章 getter/setter 惯例的 Python 解法:直接不写,或用 dataclass)。配合类型注解,这是现代 Python 写「数据类」的标准姿势(Java 14 的 record 是同款思想,两门语言在互相学习)。
class MathUtils:
@staticmethod
def add(a, b): return a + b # 静态:和类无关的工具
@classmethod
def from_string(cls, s): # 类方法:cls 是类本身(工厂模式)
x, y = map(float, s.split(","))
return cls(x, y)
@staticmethod = Java 静态方法;@classmethod 收 cls 能造自己的子类实例(工厂);实例方法占 95%。最后一课:Python 不强求 OOP——函数 + 模块就能组织得很好(第 7 章),「一个类只有一个方法且只实例化一次」的 Java 习惯(为了「万物皆对象」)在 Python 里是噪音。该用类的时候(有状态要维护、有多个相关操作、需要多态),用;不需要的时候,函数伺候。
def deposit(amount) 少了 self,调用时参数错位报「takes 1 argument but 2 given」;报错信息里数一数参数个数。class C: items = [] 是类级共享(第 3 章可变默认参数的类版);实例属性在 init 里赋。def total_area(shapes) 只要求元素有 area() 方法;造两个毫不相干的类(正常 Shape 和一个「恶作剧类」)都传进去,验证都工作。