Python 面向对象进阶
面向对象进阶机制:抽象基类、dataclass、封装、组合、多态、slots 与元类/描述符入门。
从”会写类”到”会设计类”
基础篇解决了”怎么定义类、怎么继承”;进阶篇回答的是工程里真正高频的四个问题:如何强制子类实现约定(抽象基类)、如何少写样板代码(dataclass)、如何让对象职责单一可测试(封装与组合)、如何在对象数量巨大时省内存(__slots__)。元类与描述符在这里只需建立直觉——它们是”类的类”和”属性访问的钩子”,是理解 property、dataclass、ORM 字段背后机制的钥匙,深入展开见 元类 与 描述符。
抽象基类
换行写法:定义抽象基类
from abc import ABC, abstractmethod
class <类名>(ABC):
@abstractmethod
def <方法名>(self): <语句>
# 定义抽象基类
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
基本写法:实现抽象基类
class <子类>(<抽象基类>): def <抽象方法>(self): <语句>
# 实现抽象基类:漏实现任何抽象方法,实例化时立刻报 TypeError
class Dog(Animal):
def speak(self):
return "Woof!"
# 抽象基类自身不能实例化
# Animal() # TypeError: Can't instantiate abstract class Animal
数据类
换行写法:使用 dataclass
from dataclasses import dataclass
@dataclass
class <类名>:
<字段1>: <类型>
<字段2>: <类型>
# 使用 dataclass 装饰器
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
换行写法:带默认值的 dataclass
@dataclass
class <类名>:
<字段1>: <类型>
<字段2>: <类型> = <默认值>
# 带默认值的 dataclass
@dataclass
class User:
name: str
age: int = 18
active: bool = True
换行写法:使用 field() 设置默认值
from dataclasses import dataclass, field
@dataclass
class <类名>:
<字段>: <类型> = field(default_factory=<工厂>)
# 使用 field() 设置可变默认值
# 注意:可变默认值(list/dict/set)必须走 default_factory,
# 直接写 grades: list[int] = [] 会让所有实例共享同一个列表
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
grades: list[int] = field(default_factory=list)
封装与访问控制
基本写法:使用单下划线表示受保护
self._<属性> = <值>
# 使用单下划线表示受保护属性
class BankAccount:
def __init__(self, balance):
self._balance = balance
基本写法:使用双下划线表示私有
self.__<属性> = <值>
# 使用双下划线表示私有属性(名称重整)
class BankAccount:
def __init__(self, balance):
self.__balance = balance
基本写法:提供公共访问方法
def get_<属性>(self): return self.__<属性>
# 提供公共方法访问私有属性
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
基本写法:提供公共修改方法
def set_<属性>(self, <值>): self.__<属性> = <值>
# 提供公共方法修改私有属性
class BankAccount:
def set_balance(self, balance):
if balance < 0:
raise ValueError("Balance cannot be negative")
self.__balance = balance
组合与聚合
换行写法:使用组合
class <类名>:
def __init__(self):
self.<组件> = <其他类>()
# 使用组合关系
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine()
def start(self):
return self.engine.start()
多态
基本写法:多态实现
def <函数>(<参数>: <类型>): <参数>.<方法>()
# 多态实现(不同类调用相同方法)
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
def animal_speak(animal):
return animal.speak()
元类
换行写法:使用 type() 动态创建类
<类名> = type("<类名>", (<父类>,), {<属性>: <值>})
# 使用 type() 动态创建类
Dog = type("Dog", (), {"bark": lambda self: "Woof!"})
dog = Dog()
换行写法:自定义元类
class <元类名>(type):
def __new__(mcs, name, bases, namespace): <语句>
# 自定义元类
class MyMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
return cls
class MyClass(metaclass=MyMeta):
pass
描述符
换行写法:自定义描述符
class <描述符类>:
def __get__(self, obj, objtype): <语句>
def __set__(self, obj, value): <语句>
# 自定义描述符
class ValidatedAttribute:
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype):
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not isinstance(value, int):
raise TypeError("Must be integer")
obj.__dict__[self.name] = value
类装饰器
换行写法:使用类装饰器
def <装饰器名>(cls): <修改类> return <类>
# 使用类装饰器添加方法
def add_method(cls):
cls.new_method = lambda self: "New method"
return cls
@add_method
class MyClass:
pass
slots 优化
基本写法:使用 slots 限制属性
class <类名>: __slots__ = [<属性1>, <属性2>]
# 使用 __slots__ 限制实例属性
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
# 收益实测(百万级对象场景):
# 普通类每个实例带一个 __dict__,slots 版省内存约 40%-60%,属性访问也更快
# 代价:不能再动态添加 x/y 之外的属性,且与多重继承中的多_slots_父类易冲突
枚举类
换行写法:定义枚举类
from enum import Enum
class <枚举类>(Enum):
<成员1> = <值>
<成员2> = <值>
# 定义枚举类
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
基本写法:访问枚举成员
<枚举类>.<成员>
# 访问枚举成员
print(Color.RED)
print(Color.RED.value)
基本写法:通过值获取枚举成员
<枚举类>(<值>)
# 通过值获取枚举成员
print(Color(1))
基本写法:遍历枚举
for <变量> in <枚举类>: <语句>
# 遍历枚举的所有成员
for color in Color:
print(color.name, color.value)
常见陷阱与最佳实践
- 抽象基类忘写 @abstractmethod:基类方法体给了默认实现但不加装饰器,子类不实现也不会报错。需要”强制约定”的方法必须加
@abstractmethod;只想提供默认行为则不必。 - 可变默认值直接写
= []:dataclass 与普通类一样会在类创建时求值一次,所有实例共享同一个列表。一律用field(default_factory=list)。 - 滥用双下划线私有:
__name触发名称重整(变成_类名__name),继承时容易造成”属性消失”的灵异现象。约定是:公开属性不加前缀,内部实现用单下划线_x,双下划线只用于确需避免子类命名冲突的场合。 - 继承优先于组合的反模式:
Car(Engine)让汽车”是一种引擎”,显然错误;“有一个”用组合(self.engine = Engine())。判断口诀:is-a 继承,has-a 组合。 - 多态靠 isinstance 分支实现:在函数里写一串
isinstance(x, Dog)分支,等于放弃多态。给每个类实现同名方法,让调用方只管调用。
# 错误:类型分支堆积
# def speak(animal):
# if isinstance(animal, Dog): return "Woof!"
# elif isinstance(animal, Cat): return "Meow!"
# 正确:同名方法自动分发
def speak(animal):
return animal.speak()
本篇小结
- 抽象基类用
ABC + @abstractmethod把”子类必须实现什么”变成实例化期的硬约束,是插件/驱动类架构的标配。 - dataclass 消灭
__init__/__repr__/__eq__样板;可变默认值走default_factory,配合frozen=True/slots=True可获得不可变与省内存变体。 - 封装约定三层:公开、单下划线内部、双下划线重整;多数场景单下划线即可。
- 组合优于继承是默认答案,
__slots__是海量小对象的内存开关;元类与描述符先建立”类的类”与”属性钩子”直觉,深入见专门篇目。
动手实践
- 用抽象基类定义
Exporter(export(data: dict) -> str),实现JSONExporter与CSVExporter两个子类,验证漏实现时实例化报错。 - 给一个
Orderdataclass 加上items: list[str]与frozen=False,再改造出frozen=True+slots=True的版本,用sys.getsizeof粗对比两者实例内存。 - 把一段
if isinstance分支代码重构成多态调用,体会调用方代码量的变化。