Python 面向对象:__init__ 之外你需要知道的

Python OOP 不只是 class + __init__,魔术方法、描述符、元类才是真正的武器。

$1.8k 字/约 11 min👁— views

Python 面向对象:init 之外你需要知道的

Python OOP 不只是 class + __init__,魔术方法、描述符、元类才是真正的武器。这篇从基础到进阶,系统梳理 Python OOP 的全貌。


类的基础:五个必知魔术方法

class Vector:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __repr__(self) -> str:
        # 开发者用,调试时显示,eval() 可还原
        return f"Vector({self.x!r}, {self.y!r})"

    def __str__(self) -> str:
        # 用户友好,print() 时调用;未定义则退化到 __repr__
        return f"({self.x}, {self.y})"

    def __eq__(self, other) -> bool:
        # 定义 == 的行为
        if not isinstance(other, Vector):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __hash__(self) -> int:
        # 定义 hash(),使对象可作 dict key/set 元素
        # 注意:定义了 __eq__ 后必须显式定义 __hash__,否则不可哈希
        return hash((self.x, self.y))

v1 = Vector(1, 2)
v2 = Vector(1, 2)
print(repr(v1))   # Vector(1, 2)
print(str(v1))    # (1, 2)
print(v1 == v2)   # True
d = {v1: "origin"}  # 可以,因为有 __hash__

继承与 MRO

单继承

class Animal:
    def __init__(self, name: str):
        self.name = name

    def speak(self) -> str:
        raise NotImplementedError

class Dog(Animal):
    def speak(self) -> str:
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self) -> str:
        return f"{self.name} says Meow!"

多继承与 MRO(C3 线性化)

class A:
    def greet(self): return "A"

class B(A):
    def greet(self): return "B -> " + super().greet()

class C(A):
    def greet(self): return "C -> " + super().greet()

class D(B, C):
    def greet(self): return "D -> " + super().greet()

print(D().greet())   # D -> B -> C -> A
print(D.__mro__)
# (<class D>, <class B>, <class C>, <class A>, <class object>)

MRO 规则:C3 算法保证:

  1. 子类在父类前面
  2. 多个父类按声明顺序排列
  3. 单调性(父类的相对顺序保持不变)

super() 不是"调用父类",是"按 MRO 顺序调用下一个类",这在多继承中至关重要。


三种方法类型

class Circle:
    PI = 3.14159

    def __init__(self, radius: float):
        self.radius = radius

    # 实例方法:第一个参数是 self(实例)
    def area(self) -> float:
        return self.PI * self.radius ** 2

    # 类方法:第一个参数是 cls(类本身),可通过类或实例调用
    @classmethod
    def from_diameter(cls, diameter: float) -> "Circle":
        return cls(diameter / 2)

    # 静态方法:没有 self 或 cls,只是逻辑上属于这个类的函数
    @staticmethod
    def is_valid_radius(r: float) -> bool:
        return r > 0

c1 = Circle(5)
c2 = Circle.from_diameter(10)  # 类方法
Circle.is_valid_radius(-1)      # False

@property:受控属性访问

class Temperature:
    def __init__(self, celsius: float = 0):
        self._celsius = celsius  # 约定:_ 前缀表示"内部使用"

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float):
        if value < -273.15:
            raise ValueError(f"Temperature {value} below absolute zero!")
        self._celsius = value

    @celsius.deleter
    def celsius(self):
        print("Deleting temperature")
        del self._celsius

    @property
    def fahrenheit(self) -> float:
        # 只读属性(没有 setter)
        return self._celsius * 9/5 + 32

t = Temperature(25)
print(t.celsius)     # 25
print(t.fahrenheit)  # 77.0
t.celsius = 100      # 调用 setter
# t.celsius = -300   # ValueError!
del t.celsius        # 调用 deleter

slots:内存优化

默认情况下,Python 对象把实例属性存在 __dict__(字典)中,灵活但占内存。

import sys

class PointDict:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class PointSlots:
    __slots__ = ("x", "y")   # 声明允许的属性

    def __init__(self, x, y):
        self.x = x
        self.y = y

pd = PointDict(1, 2)
ps = PointSlots(1, 2)

print(sys.getsizeof(pd.__dict__))   # ~232 bytes
print(sys.getsizeof(ps))            # ~56 bytes(小得多)

# __slots__ 的限制:
# ps.z = 3  # AttributeError:不能动态添加属性

适合场景:创建大量小对象(如游戏中的粒子、数据点)时,__slots__ 可节省 30-50% 内存。


容器类魔术方法

class FrozenList:
    # 一个不可修改的列表

    def __init__(self, data):
        self._data = list(data)

    def __len__(self) -> int:
        return len(self._data)

    def __getitem__(self, index):
        return self._data[index]  # 支持切片(因为 list 本身支持切片)

    def __iter__(self):
        return iter(self._data)   # 让对象可迭代

    def __contains__(self, item) -> bool:
        return item in self._data

    def __repr__(self) -> str:
        return f"FrozenList({self._data!r})"

fl = FrozenList([1, 2, 3, 4, 5])
print(len(fl))          # 5
print(fl[1:3])          # [2, 3](切片)
print(3 in fl)          # True
for x in fl: print(x)  # 可迭代

运算符重载

class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __add__(self, other):         # v1 + v2
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):         # v1 - v2
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):        # v * 3
        return Vector(self.x * scalar, self.y * scalar)

    def __rmul__(self, scalar):       # 3 * v(反向)
        return self.__mul__(scalar)

    def __neg__(self):                # -v
        return Vector(-self.x, -self.y)

    def __abs__(self):                # abs(v)
        return (self.x**2 + self.y**2) ** 0.5

    def __lt__(self, other):          # v1 < v2(按模长)
        return abs(self) < abs(other)

    def __bool__(self):               # bool(v)(零向量为 False)
        return self.x != 0 or self.y != 0

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)   # Vector(4, 6)
print(2 * v1)    # Vector(2, 4)
print(abs(v2))   # 5.0

上下文管理器:enter / exit

class DatabaseConnection:
    def __init__(self, url: str):
        self.url = url
        self.conn = None

    def __enter__(self):
        print(f"Connecting to {self.url}")
        self.conn = {"connected": True}  # 模拟连接
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Closing connection")
        self.conn = None
        # 返回 True 表示吞掉异常;返回 False/None 表示异常继续传播
        return False

with DatabaseConnection("postgresql://localhost/db") as conn:
    print(f"Got connection: {conn}")

描述符协议

描述符是 Python 属性访问机制的底层实现,@property@classmethod@staticmethod 都是描述符。

class Validator:
    # 可复用的属性验证描述符

    def __set_name__(self, owner, name):
        self.name = name
        self.private_name = "_" + 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):
        self.validate(value)
        setattr(obj, self.private_name, value)

    def validate(self, value):
        pass  # 子类实现

class PositiveNumber(Validator):
    def validate(self, value):
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError(f"{self.name} must be positive, got {value!r}")

class Circle:
    radius = PositiveNumber()

    def __init__(self, radius):
        self.radius = radius  # 触发 PositiveNumber.__set__

c = Circle(5)    # OK
# Circle(-1)    # ValueError!

抽象类(ABC)

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass  # 子类必须实现

    @abstractmethod
    def perimeter(self) -> float:
        pass  # 子类必须实现

    def describe(self) -> str:
        return f"Shape with area={self.area():.2f}, perimeter={self.perimeter():.2f}"

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w, self.h = w, h

    def area(self) -> float:
        return self.w * self.h

    def perimeter(self) -> float:
        return 2 * (self.w + self.h)

# Shape()  # TypeError:不能实例化抽象类
rect = Rectangle(3, 4)
print(rect.describe())  # Shape with area=12.00, perimeter=14.00

dataclass:告别样板代码

from dataclasses import dataclass, field
from typing import List

@dataclass
class Point:
    x: float
    y: float

    def distance_to_origin(self) -> float:
        return (self.x**2 + self.y**2) ** 0.5

@dataclass(frozen=True)   # 不可变(自动生成 __hash__)
class ImmutablePoint:
    x: float
    y: float

@dataclass
class Team:
    name: str
    members: List[str] = field(default_factory=list)   # 可变默认值必须用 field
    captain: str = ""

    def __post_init__(self):
        # 初始化后处理
        if self.captain and self.captain not in self.members:
            self.members.append(self.captain)

# dataclass 自动生成:__init__、__repr__、__eq__
p = Point(3, 4)
print(p)               # Point(x=3, y=4)
print(p.distance_to_origin())  # 5.0

综合示例:自定义链表容器

from typing import Iterator, TypeVar, Generic, Optional

T = TypeVar("T")

class LinkedList(Generic[T]):
    # 链表:可迭代、支持 with 语句、支持 + 运算

    class Node:
        __slots__ = ("value", "next")
        def __init__(self, value):
            self.value = value
            self.next = None

    def __init__(self):
        self._head = None
        self._size = 0

    def append(self, value) -> None:
        node = self.Node(value)
        if not self._head:
            self._head = node
        else:
            cur = self._head
            while cur.next:
                cur = cur.next
            cur.next = node
        self._size += 1

    def __len__(self) -> int:
        return self._size

    def __iter__(self):
        cur = self._head
        while cur:
            yield cur.value
            cur = cur.next

    def __contains__(self, value) -> bool:
        return any(v == value for v in self)

    def __add__(self, other):
        result = LinkedList()
        for v in self: result.append(v)
        for v in other: result.append(v)
        return result

    def __repr__(self) -> str:
        return f"LinkedList([{', '.join(repr(v) for v in self)}])"

    def __enter__(self):
        return self

    def __exit__(self, *args) -> bool:
        self._head = None
        self._size = 0
        return False

# 使用
with LinkedList() as ll:
    ll.append(1)
    ll.append(2)
    ll.append(3)
    print(ll)        # LinkedList([1, 2, 3])
    print(2 in ll)   # True
# 退出 with:链表自动清空