4Fluent Python(第2版)

面向对象

Pythonic OOP

描述符属性魔术方法抽象基类

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第4章:面向对象

一、导读

1.1 本章学习目标

第4章讲解 Python 的面向对象编程:描述符、属性、魔术方法、抽象基类。

本章核心学习目标:

  • 掌握描述符协议:__get__、__set__、__delete__
  • 理解 @property 的实现原理
  • 掌握抽象基类(ABC):强制子类实现特定方法
  • 理解 Python 的方法解析顺序(MRO)

1.2 为什么本章如此重要

Python 的 OOP 比大多数语言更灵活。描述符和 ABC 是构建框架和库的核心工具。

1.3 核心问题引导

@property 底层是如何实现的?

描述符和 @property 有什么区别?

ABC 如何强制子类实现接口?


二、核心概念详解

2.1 描述符

python
class Validated:
    """描述符: 验证属性值"""
    def __init__(self, validator=None):
        self.validator = validator
    
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)
    
    def __set__(self, instance, value):
        if self.validator:
            value = self.validator(value)
        instance.__dict__[self.name] = value

def not_empty(value):
    if not value:
        raise ValueError("Value cannot be empty")
    return value

def positive(value):
    if value <= 0:
        raise ValueError("Value must be positive")
    return value

class Product:
    name = Validated(not_empty)
    price = Validated(positive)
    quantity = Validated(positive)
    
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

p = Product("Widget", 9.99, 100)
# p.price = -1  # ValueError: Value must be positive

2.2 @property

python
class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Too cold!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5/9

t = Temperature(100)
print(t.fahrenheit)  # 212.0
t.fahrenheit = 32
print(t.celsius)     # 0.0

2.3 抽象基类

python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        """计算面积"""
        pass
    
    @abstractmethod
    def perimeter(self) -> float:
        """计算周长"""
        pass
    
    @classmethod
    @abstractmethod
    def name(cls) -> str:
        """返回形状名称"""
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        import math
        return math.pi * self.radius ** 2
    
    def perimeter(self):
        import math
        return 2 * math.pi * self.radius
    
    @classmethod
    def name(cls):
        return "Circle"

# c = Shape()  # TypeError: Can't instantiate abstract class
c = Circle(5)
print(c.area())  # 78.539...

三、实战案例

3.1 案例一:类型验证描述符

python
class Typed:
    """类型验证描述符"""
    def __init__(self, expected_type):
        self.expected_type = expected_type
    
    def __set_name__(self, owner, name):
        self.name = name
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)
    
    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"{self.name} must be {self.expected_type.__name__}")
        instance.__dict__[self.name] = value

class Employee:
    name = Typed(str)
    age = Typed(int)
    salary = Typed(float)
    
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary

e = Employee("Alice", 30, 50000.0)
# e.age = "thirty"  # TypeError: age must be int

四、本章小结

描述符是 @property 的底层实现,提供更灵活的属性管理

@property 将方法调用伪装成属性访问

ABC 强制子类实现特定接口


五、思考与练习

描述符的 __get__ 在类访问和实例访问时有什么区别?

如何用 ABC 定义一个可迭代的接口?


4/8面向对象
0%