Skip to content

第10章 类与对象

学习目标

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

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

10.1 面向对象基础

10.1.1 类的定义与实例化

python
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 实例属性

python
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
# 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 字符串表示

python
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 比较运算符

python
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 算术运算符

python
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 容器方法

python
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 可调用对象

python
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

python
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 只读属性与延迟计算

python
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 三种方法对比

python
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基础

python
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 高级选项

python
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对象模型

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 描述符协议

python
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 属性查找机制

python
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 对象内存布局

python
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)

python
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

python
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 结构化模式匹配与类

python
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 性能优化技术

python
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 常见陷阱与规避

python
# 陷阱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章 继承与多态

青少年创意编程 - 高中Python组 - 江苏省宿城中等专业学校