第10章 类与对象

学习目标

完成本章学习后,读者将能够:

  • 理解Python面向对象编程的核心概念:类、实例、属性、方法
  • 掌握魔术方法(dunder methods)的自定义与使用
  • 运用@property实现受控属性访问与计算属性
  • 区分实例方法、类方法与静态方法的使用场景
  • 使用dataclass构建简洁的数据类

10.1 面向对象基础

10.1.1 类的定义与实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 字符串表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 比较运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 算术运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.0

10.2.4 容器方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 可调用对象

1
2
3
4
5
6
7
8
9
10
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)) # True

10.3 属性装饰器

10.3.1 @property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 只读属性与延迟计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 False

10.4 类方法与静态方法

10.4.1 三种方法对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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基础

1
2
3
4
5
6
7
8
9
10
11
12
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 高级选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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对象模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 描述符协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 属性查找机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 对象内存布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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)

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
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 None

10.7.3 结构化模式匹配与类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 性能优化技术

1
2
3
4
5
6
7
8
9
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类与对象的核心体系:

  1. 类与实例:类属性共享、实例属性独有、__slots__限制
  2. 魔术方法__str__/__repr__、比较运算符、算术运算符、容器协议、__call__
  3. 属性装饰器@property实现受控访问与计算属性
  4. 方法类型:实例方法操作数据、类方法作工厂、静态方法作工具
  5. 数据类@dataclass减少样板代码,支持frozen、order等选项
  6. 对象模型:元类、描述符协议、属性查找机制、内存布局

10.8.1 OOP设计原则

原则说明Python实现
封装隐藏内部实现细节_private约定、@property
抽象定义接口规范ABC抽象基类、Protocol
组合优于继承灵活组装功能has-a关系、依赖注入
单一职责类只做一件事小类、高内聚
开闭原则对扩展开放、对修改关闭继承、多态、装饰器

10.8.2 常见陷阱与规避

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 陷阱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.value

10.9 练习题

基础题

  1. 创建Rectangle类,包含宽高属性及面积、周长方法,使用@property确保宽高为正数。

  2. 创建BankAccount类,实现存款、取款、查询余额,使用只读property保护余额。

  3. 使用@property实现温度转换器,可在摄氏度和华氏度之间自动转换。

进阶题

  1. 实现Vector类,支持加法、减法、标量乘法、点积和模长。

  2. 创建自定义容器类Matrix,支持索引、切片和矩阵乘法。

  3. 使用dataclass创建Student类,支持按成绩排序和平均分计算。

项目实践

  1. 图书管理系统:编写一个程序,要求:
    • 使用dataclass定义Book(ISBN、书名、作者、价格、库存)
    • 使用@property实现价格验证和库存管理
    • 实现BookCatalog类,支持添加、删除、搜索、按价格排序
    • 实现__contains____len____iter__容器协议
    • 使用类方法实现从CSV文件导入数据

思考题

  1. __str____repr__有什么区别?为什么应该始终实现__repr__

  2. Python的协议式多态与Java的接口机制有何本质区别?

  3. @property的过度使用会带来什么问题?什么情况下应直接使用公开属性?

10.10 延伸阅读

10.10.1 面向对象理论

  • 《设计模式》 (GoF) — 23种经典设计模式
  • 《面向对象分析与设计》 (Grady Booch) — OOP方法论
  • 《敏捷软件开发》 (Robert C. Martin) — OOP原则与实践
  • SOLID原则 — 面向对象设计的五大原则

10.10.2 Python对象模型

10.10.3 高级OOP技术

10.10.4 设计模式Python实现


下一章:第11章 继承与多态