第10章 类与对象
学习目标
完成本章学习后,读者将能够:
- 理解Python面向对象编程的核心概念:类、实例、属性、方法
- 掌握魔术方法(dunder methods)的自定义与使用
- 运用@property实现受控属性访问与计算属性
- 区分实例方法、类方法与静态方法的使用场景
- 使用dataclass构建简洁的数据类
10.1 面向对象基础
10.1.1 类的定义与实例化
class Person:
species = "Homo sapiens" # 类属性
def __init__(self, name: str, age: int):
self.name = name # 实例属性
self.age = age
def greet(self) -> str: # 实例方法
return f"Hello, I'm {self.name}, {self.age} years old."
p = Person("Alice", 25)
print(p.greet()) # "Hello, I'm Alice, 25 years old."
print(p.name) # "Alice"
print(Person.species) # "Homo sapiens"学术注记:Python中一切皆对象,类本身也是对象(
type的实例)。实例通过__new__创建、__init__初始化。self不是关键字,而是约定俗成的参数名,代表实例本身,等价于其他语言中的this。
10.1.2 类属性 vs 实例属性
class Person:
count = 0 # 类属性:所有实例共享
def __init__(self, name: str):
self.name = name # 实例属性:每个实例独有
Person.count += 1
p1 = Person("Alice")
p2 = Person("Bob")
print(Person.count) # 2
print(p1.name) # "Alice"
# 注意:实例属性会遮蔽同名类属性
p1.count = 100 # 创建了实例属性,不影响类属性
print(Person.count) # 2
print(p1.count) # 100 - 实例属性10.1.3 动态属性与__slots__
# Python允许动态添加属性
p = Person("Alice")
p.email = "alice@example.com" # 动态添加实例属性
# 使用__slots__限制属性(节省内存)
class Point:
__slots__ = ("x", "y") # 只允许x和y属性
def __init__(self, x: float, y: float):
self.x = x
self.y = y
p = Point(3, 4)
# p.z = 5 # AttributeError!工程实践:
__slots__可节省约40-50%内存,适用于创建大量实例的类。但会禁止动态属性添加,且影响某些继承场景。普通类无需使用。
10.2 魔术方法
10.2.1 字符串表示
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __str__(self) -> str: # 面向用户,print()时调用
return f"{self.name} ({self.age})"
def __repr__(self) -> str: # 面向开发者,交互式输出时调用
return f"Person({self.name!r}, {self.age})"
p = Person("Alice", 25)
print(p) # Alice (25) - 调用__str__
print(repr(p)) # Person('Alice', 25) - 调用__repr__工程实践:始终实现
__repr__。当__str__未定义时,Python会回退到__repr__。__repr__应尽量返回能重建对象的合法Python表达式。
10.2.2 比较运算符
from functools import total_ordering
@total_ordering # 只需定义__eq__和__lt__,自动生成其他
class Student:
def __init__(self, name: str, score: float):
self.name = name
self.score = score
def __eq__(self, other) -> bool:
if not isinstance(other, Student):
return NotImplemented
return self.score == other.score
def __lt__(self, other) -> bool:
if not isinstance(other, Student):
return NotImplemented
return self.score < other.score
students = [Student("Bob", 85), Student("Alice", 92), Student("Charlie", 78)]
print([s.name for s in sorted(students)]) # ['Charlie', 'Bob', 'Alice']10.2.3 算术运算符
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar: float) -> "Vector":
return self.__mul__(scalar)
def __abs__(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 * 2) # Vector(6, 8)
print(2 * v1) # Vector(6, 8) - 调用__rmul__
print(abs(v1)) # 5.010.2.4 容器方法
class Deck:
def __init__(self):
self.cards = list(range(1, 53))
def __len__(self) -> int:
return len(self.cards)
def __getitem__(self, index: int) -> int:
return self.cards[index]
def __contains__(self, card: int) -> bool:
return card in self.cards
def __iter__(self):
return iter(self.cards)
deck = Deck()
print(len(deck)) # 52
print(deck[0]) # 1
print(10 in deck) # True
for card in deck[:5]:
print(card)学术注记:实现
__len__和__getitem__即可使对象支持迭代、reversed()、切片等操作,这是Python协议式多态的体现——不需要继承特定接口,只需实现所需方法。
10.2.5 可调用对象
class Multiplier:
def __init__(self, factor: float):
self.factor = factor
def __call__(self, x: float) -> float:
return x * self.factor
double = Multiplier(2)
print(double(5)) # 10
print(callable(double)) # True10.3 属性装饰器
10.3.1 @property
class Circle:
def __init__(self, radius: float):
self.radius = radius # 通过setter设置
@property
def radius(self) -> float:
return self._radius
@radius.setter
def radius(self, value: float):
if value <= 0:
raise ValueError("半径必须为正数")
self._radius = value
@property
def area(self) -> float: # 计算属性(只读)
import math
return math.pi * self._radius ** 2
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.54...
c.radius = 10 # 通过setter验证
# c.area = 100 # AttributeError - 只读工程实践:使用
@property而非公开属性+getter/setter方法。property让API保持属性访问的简洁性,同时保留验证逻辑的能力。但不要过度使用——简单数据直接用公开属性即可。
10.3.2 只读属性与延迟计算
class BankAccount:
def __init__(self, initial_balance: float = 0):
self._balance = initial_balance
self._transactions: list[float] = []
@property
def balance(self) -> float: # 只读属性
return self._balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("存款金额必须为正数")
self._balance += amount
self._transactions.append(amount)
def withdraw(self, amount: float) -> bool:
if 0 < amount <= self._balance:
self._balance -= amount
self._transactions.append(-amount)
return True
return False10.4 类方法与静态方法
10.4.1 三种方法对比
class Date:
def __init__(self, year: int, month: int, day: int):
self.year = year
self.month = month
self.day = day
def display(self) -> str: # 实例方法
return f"{self.year}-{self.month:02d}-{self.day:02d}"
@classmethod
def from_string(cls, date_str: str) -> "Date": # 类方法
y, m, d = map(int, date_str.split("-"))
return cls(y, m, d) # 返回调用类的实例
@staticmethod
def is_leap_year(year: int) -> bool: # 静态方法
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
d = Date.from_string("2026-04-18")
print(d.display()) # "2026-04-18"
print(Date.is_leap_year(2024)) # True| 方法类型 | 第一个参数 | 访问类/实例 | 使用场景 |
|---|---|---|---|
| 实例方法 | self | 实例+类 | 操作实例数据 |
| 类方法 | cls | 类 | 工厂方法、替代构造器 |
| 静态方法 | 无 | 无 | 与类相关但不需类/实例数据的工具函数 |
工程实践:优先使用
@classmethod作为工厂方法(替代构造器),而非@staticmethod。classmethod在继承时能正确返回子类实例。
10.5 数据类
10.5.1 @dataclass基础
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int
city: str = "北京" # 默认值
p1 = Person("Alice", 25)
p2 = Person("Alice", 25)
print(p1) # Person(name='Alice', age=25, city='北京')
print(p1 == p2) # True - 自动生成__eq__10.5.2 高级选项
from dataclasses import dataclass, field
@dataclass(frozen=True) # 不可变数据类
class Color:
red: int
green: int
blue: int
c = Color(255, 128, 0)
# c.red = 100 # FrozenInstanceError
@dataclass(order=True) # 自动生成比较方法
class Student:
score: int # 排序依据
name: str = field(compare=False) # 不参与比较
students = [Student(85, "Alice"), Student(92, "Bob"), Student(78, "Charlie")]
print([s.name for s in sorted(students)]) # ['Charlie', 'Alice', 'Bob']
@dataclass
class Employee:
name: str
department: str
salary: float = 0.0
skills: list[str] = field(default_factory=list) # 可变默认值工程实践:dataclass自动生成
__init__、__repr__、__eq__等方法,大幅减少样板代码。对于简单数据容器,优先使用dataclass而非手写类。
10.6 对象模型深入
10.6.1 Python对象模型
def explore_object_model():
"""探索Python对象模型"""
class MyClass:
class_var = "类变量"
def __init__(self):
self.instance_var = "实例变量"
def method(self):
pass
obj = MyClass()
print("对象类型:")
print(f" type(obj) = {type(obj)}")
print(f" type(MyClass) = {type(MyClass)}")
print(f" type(type) = {type(type)}")
print("\n属性查找顺序 (MRO):")
print(f" MyClass.__mro__ = {MyClass.__mro__}")
print("\n属性访问:")
print(f" obj.__dict__ = {obj.__dict__}")
print(f" MyClass.__dict__.keys() = {list(MyClass.__dict__.keys())[:5]}...")
print("\n特殊属性:")
print(f" obj.__class__ = {obj.__class__}")
print(f" obj.__class__.__name__ = {obj.__class__.__name__}")
explore_object_model()学术注记:Python的类型系统基于元类(metaclass)。
type是所有类型的元类,object是所有类的基类。类定义时,Python先调用元类的__new__创建类对象,再调用__init__初始化。理解元类是深入Python对象模型的关键。
10.6.2 描述符协议
class ValidatedAttribute:
"""验证描述符"""
def __init__(self, name: str, validator: callable):
self.name = name
self.validator = validator
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
if not self.validator(value):
raise ValueError(f"Invalid value for {self.name}: {value}")
setattr(obj, self.private_name, value)
def __delete__(self, obj):
raise AttributeError(f"Cannot delete {self.name}")
class Person:
age = ValidatedAttribute("age", lambda x: isinstance(x, int) and x >= 0)
email = ValidatedAttribute("email", lambda x: "@" in str(x))
def __init__(self, age: int, email: str):
self.age = age
self.email = email
p = Person(25, "alice@example.com")
print(p.age)
# p.age = -1 # ValueError!学术注记:描述符是Python属性访问的底层机制。
@property本质上是描述符的语法糖。描述符协议包括__get__、__set__、__delete__方法,定义了属性访问、赋值、删除的行为。
10.6.3 属性查找机制
def demonstrate_attribute_lookup():
"""演示属性查找顺序"""
class Base:
base_attr = "Base类属性"
def base_method(self):
return "Base方法"
class Derived(Base):
derived_attr = "Derived类属性"
def derived_method(self):
return "Derived方法"
obj = Derived()
print("属性查找顺序 (MRO):")
for cls in Derived.__mro__:
print(f" {cls}")
print("\n属性访问:")
print(f" obj.base_attr = {obj.base_attr}")
print(f" obj.derived_attr = {obj.derived_attr}")
print("\n__dict__内容:")
print(f" obj.__dict__ = {obj.__dict__}")
print(f" Derived.__dict__.keys() = {[k for k in Derived.__dict__.keys() if not k.startswith('_')]}")
demonstrate_attribute_lookup()10.6.4 对象内存布局
import sys
def analyze_object_memory():
"""分析对象内存布局"""
class Simple:
pass
class WithSlots:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
class Normal:
def __init__(self, x, y):
self.x = x
self.y = y
simple = Simple()
with_slots = WithSlots(1, 2)
normal = Normal(1, 2)
print("内存占用对比:")
print(f" 空对象: {sys.getsizeof(simple)} 字节")
print(f" 普通类对象: {sys.getsizeof(normal)} 字节")
print(f" __slots__对象: {sys.getsizeof(with_slots)} 字节")
print("\n__dict__开销:")
print(f" 普通类__dict__: {sys.getsizeof(normal.__dict__)} 字节")
print(f" __slots__类: 无__dict__")
analyze_object_memory()10.7 前沿技术动态
10.7.1 dataclass增强(PEP 681, PEP 727)
from dataclasses import dataclass, field
@dataclass(slots=True, kw_only=True)
class Person:
name: str
age: int
email: str = field(default="")
p = Person(name="Alice", age=30)10.7.2 类型系统与OOP
from typing import Self, TypeGuard
class Node:
def __init__(self, value: int):
self.value = value
self.left: Self | None = None
self.right: Self | None = None
def is_leaf(self) -> TypeGuard[Self]:
return self.left is None and self.right is None10.7.3 结构化模式匹配与类
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
def describe(point: Point) -> str:
match point:
case Point(x=0, y=0):
return "Origin"
case Point(x=0):
return "On Y-axis"
case Point(y=0):
return "On X-axis"
case _:
return "General point"10.7.4 性能优化技术
from dataclasses import dataclass
@dataclass(slots=True, frozen=True)
class ImmutablePoint:
x: float
y: float
def __hash__(self) -> int:
return hash((self.x, self.y))10.8 本章小结
本章系统介绍了Python类与对象的核心体系:
- 类与实例:类属性共享、实例属性独有、
__slots__限制 - 魔术方法:
__str__/__repr__、比较运算符、算术运算符、容器协议、__call__ - 属性装饰器:
@property实现受控访问与计算属性 - 方法类型:实例方法操作数据、类方法作工厂、静态方法作工具
- 数据类:
@dataclass减少样板代码,支持frozen、order等选项 - 对象模型:元类、描述符协议、属性查找机制、内存布局
10.8.1 OOP设计原则
| 原则 | 说明 | Python实现 |
|---|---|---|
| 封装 | 隐藏内部实现细节 | _private约定、@property |
| 抽象 | 定义接口规范 | ABC抽象基类、Protocol |
| 组合优于继承 | 灵活组装功能 | has-a关系、依赖注入 |
| 单一职责 | 类只做一件事 | 小类、高内聚 |
| 开闭原则 | 对扩展开放、对修改关闭 | 继承、多态、装饰器 |
10.8.2 常见陷阱与规避
# 陷阱1:可变类属性共享
class Bad:
items = [] # 所有实例共享!
a = Bad()
a.items.append(1)
b = Bad()
print(b.items) # [1] - 被污染!
# 正确做法
class Good:
def __init__(self):
self.items = [] # 每个实例独立
# 陷阱2:property循环调用
class Bad:
@property
def name(self):
return self.name # 无限递归!
# 正确做法
class Good:
@property
def name(self):
return self._name
# 陷阱3:忘记返回NotImplemented
class Bad:
def __eq__(self, other):
return False # 与任何类型比较都返回False
# 正确做法
class Good:
def __eq__(self, other):
if not isinstance(other, Good):
return NotImplemented
return self.value == other.value10.9 练习题
基础题
创建Rectangle类,包含宽高属性及面积、周长方法,使用@property确保宽高为正数。
创建BankAccount类,实现存款、取款、查询余额,使用只读property保护余额。
使用@property实现温度转换器,可在摄氏度和华氏度之间自动转换。
进阶题
实现Vector类,支持加法、减法、标量乘法、点积和模长。
创建自定义容器类Matrix,支持索引、切片和矩阵乘法。
使用dataclass创建Student类,支持按成绩排序和平均分计算。
项目实践
- 图书管理系统:编写一个程序,要求:
- 使用dataclass定义Book(ISBN、书名、作者、价格、库存)
- 使用@property实现价格验证和库存管理
- 实现BookCatalog类,支持添加、删除、搜索、按价格排序
- 实现
__contains__、__len__、__iter__容器协议 - 使用类方法实现从CSV文件导入数据
思考题
__str__和__repr__有什么区别?为什么应该始终实现__repr__?Python的协议式多态与Java的接口机制有何本质区别?
@property的过度使用会带来什么问题?什么情况下应直接使用公开属性?
10.10 延伸阅读
10.10.1 面向对象理论
- 《设计模式》 (GoF) — 23种经典设计模式
- 《面向对象分析与设计》 (Grady Booch) — OOP方法论
- 《敏捷软件开发》 (Robert C. Martin) — OOP原则与实践
- SOLID原则 — 面向对象设计的五大原则
10.10.2 Python对象模型
- 《Fluent Python》 (Luciano Ramalho) — Python对象模型深度解析
- PEP 3115 — Metaclasses in Python 3000 (https://peps.python.org/pep-3115/)
- PEP 557 — Dataclasses (https://peps.python.org/pep-0557/)
- Python Data Model文档 (https://docs.python.org/3/reference/datamodel.html)
10.10.3 高级OOP技术
- 《Python Cookbook》 (David Beazley) — 高级类设计技巧
- descriptors模块文档 (https://docs.python.org/3/howto/descriptor.html) — 描述符指南
- abc模块 (https://docs.python.org/3/library/abc.html) — 抽象基类
10.10.4 设计模式Python实现
- 《Python设计模式》 (Dusty Phillips) — Python设计模式实践
- Python Patterns (https://github.com/faif/python-patterns) — 开源设计模式集合
- Refactoring Guru (https://refactoring.guru/design-patterns/python) — 设计模式教程
下一章:第11章 继承与多态