)
前言面向对象编程Object-Oriented ProgrammingOOP的教科书通常会列出三大特性封装、继承、多态。但把这套从 Java、C 课本里搬来的说法直接套到 Python 上很容易写出别扭的代码因为 Python 的 OOP 有自己的脾气。最大的误解是Python 的私有属性用双下划线实现。实际上 Python 没有强制访问控制双下划线只是名字改写name mangling。第二个误解是多态必须靠继承实现——Python 是靠鸭子类型duck typing实现多态的只要对象有需要的方法它就能用跟继承关系无关。作为系列第一篇本文讲清类与实例、self的含义、封装、继承和多态在 Python 里的真实形态并给出可以直接运行的示例。一、从类到实例类是模板实例是具体对象。创建实例的过程叫实例化会调用__init__做初始化。# 适用于 Python 3.8class Cat:species 猫科 # 类属性def __init__(self, name, age):self.name name # 实例属性self.age agedef meow(self):return f{self.name}: 喵def birthday(self):self.age 1return self.agec Cat(咪咪, 2)print(c.meow()) # 咪咪: 喵print(c.birthday()) # 3print(c.species) # 猫科print(Cat.species) # 猫科print(c.__dict__) # {name: 咪咪, age: 3}self不是关键字只是第一个参数的习惯命名它指向调用方法的那个实例。写成def meow(this)也能跑但没人这么写。二、封装约定优于强制Python 的封装靠的是命名约定和接口设计而不是访问修饰符。约定有三档写法约定含义是否被语言强制name公开否_name内部使用请勿依赖否__name触发名字改写_类名__name仅防子类重名__name__特殊方法/属性由解释器使用# 适用于 Python 3.8class BankAccount:def __init__(self, owner, balance0):self.owner ownerself._balance balance # 内部使用def deposit(self, amount):if amount 0:raise ValueError(金额必须为正)self._balance amountpropertydef balance(self):return self._balance # 只读视图acc BankAccount(Ada, 100)acc.deposit(50)print(acc.balance) # 150try:acc.balance 0 # 只读会报错except AttributeError as e:print(AttributeError:, e)property把balance变成只读属性这是 Python 里表达对外只读的正规做法比到处写_balance更清晰。三、继承复用与扩展继承用class Sub(Base):。子类获得父类的属性和方法并可以重写。# 适用于 Python 3.8class Employee:def __init__(self, name, salary):self.name nameself.salary salarydef annual(self):return self.salary * 12def describe(self):return f{self.name}: 年薪 {self.annual()}class Manager(Employee):def __init__(self, name, salary, bonus):super().__init__(name, salary)self.bonus bonusdef annual(self):return super().annual() self.bonusm Manager(Bob, 10000, 50000)print(m.describe()) # Bob: 年薪 170000print(isinstance(m, Employee)) # Truem.describe()调用的是父类的方法但内部self.annual()走的是子类的重写版本。这就是多态的基础父类代码无需知道子类细节也能调用到子类的实现。四、多态与鸭子类型Python 的多态不要求共享基类。只要对象看起来像鸭子、叫起来像鸭子就能当鸭子用# 适用于 Python 3.8class Duck:def speak(self):return 嘎嘎class Robot:def speak(self):return 哔哔def make_speak(obj):return obj.speak() # 不检查类型只要求有 speakfor thing in (Duck(), Robot()):print(make_speak(thing))上面make_speak既不检查类型也不要求它们有共同父类。这种风格叫鸭子类型duck typing是 Python 动态类型系统的直接体现。如果确实需要必须是某类可以用isinstance()或抽象基类来显式约束。多态实现方式依赖关系适用场景继承 重写共享基类有明确层次结构的领域模型鸭子类型只依赖方法名通用工具函数、协议式接口抽象基类显式接口契约库的公开扩展点协议typing.Protocol结构化类型提示静态检查、类型注解五、特殊方法让对象融入语言实现了特殊方法dunder method你的对象就能像内置类型一样工作支持len()、、for、in等。# 适用于 Python 3.8class Playlist:def __init__(self, songsNone):self._songs list(songs) if songs is not None else []def add(self, song):self._songs.append(song)def __len__(self):return len(self._songs)def __getitem__(self, index):return self._songs[index]def __contains__(self, song):return song in self._songsdef __repr__(self):return fPlaylist({self._songs!r})p Playlist([a, b])p.add(c)print(len(p)) # 3print(p[0]) # aprint(b in p) # Trueprint(p) # Playlist([a, b, c])六、综合示例一个简单的形状体系# 适用于 Python 3.8class Shape:def area(self):raise NotImplementedErrordef describe(self):return f{type(self).__name__}: 面积 {self.area()}class Rect(Shape):def __init__(self, w, h):self.w, self.h w, hdef area(self):return self.w * self.hclass Square(Rect):def __init__(self, side):super().__init__(side, side)for s in (Rect(2, 3), Square(4)):print(s.describe())type(self).__name__让父类方法拿到实际子类的名字这比写死字符串更稳。常见坑点把双下划线当私有❌python # 适用于 Python 3.8 class A: def __init__(self): self.__v 1 # 以为外部完全访问不到✅python # 适用于 Python 3.8 class A: def __init__(self): self._v 1 # 约定式内部属性子类忘记调super().__init__()❌python # 适用于 Python 3.8 class Manager(Employee): def __init__(self, name, salary, bonus): self.bonus bonus # name、salary 都没设✅python # 适用于 Python 3.8 class Manager(Employee): def __init__(self, name, salary, bonus): super().__init__(name, salary) self.bonus bonus用type(x) T做多态分发❌python # 适用于 Python 3.8 if type(x) Employee: # 子类匹配不上 ...✅python # 适用于 Python 3.8 if isinstance(x, Employee): # 含子类 ...可变默认参数❌python # 适用于 Python 3.8 class A: def __init__(self, items[]): self.items items✅python # 适用于 Python 3.8 class A: def __init__(self, itemsNone): self.items [] if items is None else list(items)父类方法里硬编码父类名导致子类行为失效❌python # 适用于 Python 3.8 class Shape: def describe(self): return f{Shape.__name__}: {self.area()}✅python # 适用于 Python 3.8 class Shape: def describe(self): return f{type(self).__name__}: {self.area()}接口只写在文档里忘了用NotImplementedError兜底❌python # 适用于 Python 3.8 class Shape: def area(self): pass # 子类忘了实现也不会报错✅python # 适用于 Python 3.8 class Shape: def area(self): raise NotImplementedError鸭子类型忘了做属性检查❌python # 适用于 Python 3.8 def make_speak(obj): return obj.speak() # 传错对象时是 AttributeError信息不明确✅python # 适用于 Python 3.8 def make_speak(obj): if not hasattr(obj, speak): raise TypeError(对象必须提供 speak()) return obj.speak()总结特性Python 的实现方式封装命名约定 property非强制继承class Sub(Base)super()沿 MRO多态鸭子类型为主继承/抽象基类为辅特殊方法让对象融入len、、for等语法私有双下划线仅做名字改写Python 的 OOP 是约定 鸭子类型的组合封装靠命名和接口多态靠方法名匹配而非继承树。写类时先把方法和属性想清楚再考虑要不要继承能用鸭子类型解决的不必强加继承层次。