这是一篇旨在彻底讲透 Python 面向对象编程(OOP)的终极指南。我们将不再局限于基础语法,而是深入到 Python 的对象模型底层,探讨元类、描述符、方法解析顺序 (MRO) 以及内存管理等高级话题。
目录
- 对象模型底层:
__new__vs__init__ - 深入属性系统:
__slots__与 描述符协议 - 继承的奥秘:多重继承、Mixin 与 C3 算法
- 接口与约束:抽象基类 (ABC) 与 协议 (Protocol)
- 元编程 (Metaprogramming):动态创建类与元类
- 魔术方法大全:模拟 Python 内置行为
- 内存管理与垃圾回收
1. 对象模型底层:__new__ vs __init__
很多人认为 __init__ 是构造函数,其实不然。对象的创建过程分为两步:
- 构造 (Construction):
__new__分配内存,创建对象实例。 - 初始化 (Initialization):
__init__给这个已经创建好的实例设置初始值。
1.1 __new__ 方法
__new__ 是一个静态方法(虽然不需要写 @staticmethod),它的第一个参数是 cls。它必须返回一个实例。
应用场景:
- 不可变对象 (Immutable Objects):继承自
str,int,tuple的子类,因为它们一旦创建就无法修改,所以必须在__new__中定制。 - 单例模式 (Singleton):控制只创建一个实例。
- 元类编程。
class UpperStr(str): def __new__(cls, value): # 在对象创建前拦截,强制转换为大写 return super().__new__(cls, value.upper())
s = UpperStr("hello")print(s) # HELLO (str 是不可变的,必须在 __new__ 处理)1.2 单例模式实现
class Singleton: _instance = None
def __new__(cls, *args, **kwargs): if cls._instance is None: # 只有第一次调用时才真正创建对象 cls._instance = super().__new__(cls) return cls._instance
a = Singleton()b = Singleton()print(a is b) # True2. 深入属性系统:__slots__ 与 描述符
2.1 __slots__:内存优化
默认情况下,Python 对象使用字典 (__dict__) 存储属性。这提供了极大的灵活性,但对于创建数百万个小对象的场景,内存消耗巨大。
__slots__ 告诉 Python:“这个类只有这些属性,不要创建 __dict__”。
class Pixel: __slots__ = ('x', 'y') # 锁定属性,禁止动态添加其他属性
def __init__(self, x, y): self.x = x self.y = y
p = Pixel(10, 20)# p.z = 30 # AttributeError: 'Pixel' object has no attribute 'z'副作用:
- 对象不再有
__dict__。 - 无法动态添加新属性。
- 继承时如果不重复定义
__slots__,子类依然会有__dict__。
2.2 描述符 (Descriptors)
这是 Python 属性魔法的核心。@property、类方法、静态方法,底层全都是描述符。
一个实现了 __get__, __set__, 或 __delete__ 方法的类,就是一个描述符。
class Integer: """数据描述符:强制属性必须是整数""" def __init__(self, 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, int): raise ValueError(f"{self.name} must be an integer") instance.__dict__[self.name] = value
class Point: x = Integer("x") # 描述符实例作为类属性 y = Integer("y")
def __init__(self, x, y): self.x = x # 触发 Integer.__set__ self.y = y
p = Point(1, 2)# p.x = "hello" # ValueError: x must be an integer3. 继承的奥秘:多重继承、Mixin 与 MRO
3.1 多重继承与菱形问题
Python 支持多重继承。当一个类继承多个父类时,如果父类中有同名方法,Python 如何决定调用哪一个?
Python 2.3 之后引入了 C3 线性化算法 来计算 MRO (Method Resolution Order)。
class A: def say(self): print("A")
class B(A): def say(self): print("B")
class C(A): def say(self): print("C")
class D(B, C): pass
d = D()d.say() # 输出 Bprint(D.mro())# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]原则:
- 子类优先于父类。
- 多个父类按照从左到右的顺序检查。
- 如果出现菱形继承(如上图,B和C都继承A),确保公共基类(A)最后被检查(但在
object之前)。
3.2 Mixin 模式
Mixin(混入)是一种设计模式,利用多重继承给类添加单一功能的“插件”,而不需要建立严格的父子关系。
class JsonSerializableMixin: def to_json(self): import json return json.dumps(self.__dict__)
class User(JsonSerializableMixin): def __init__(self, name): self.name = name
u = User("Alice")print(u.to_json()) # {"name": "Alice"}4. 接口与约束:ABC 与 Protocol
Python 是动态语言,通常不强制类型。但为了大型项目的健壮性,我们需要接口约束。
4.1 抽象基类 (Abstract Base Classes)
使用 abc 模块定义抽象基类,强制子类实现特定方法。
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self): pass
class Circle(Shape): def __init__(self, r): self.r = r
def area(self): return 3.14 * self.r ** 2
# s = Shape() # TypeError: Can't instantiate abstract classc = Circle(5) # OK4.2 Protocol (鸭子类型的静态检查)
Python 3.8 引入了 typing.Protocol。它不需要继承,只要类实现了协议规定的方法,类型检查器(如 MyPy)就认为它符合要求。
from typing import Protocol
class Flyer(Protocol): def fly(self) -> None: ...
class Bird: def fly(self): print("Bird flying")
class Plane: def fly(self): print("Plane flying")
def lift_off(obj: Flyer): obj.fly()
# Bird 和 Plane 不需要显式继承 Flyerlift_off(Bird())lift_off(Plane())5. 元编程 (Metaprogramming)
元编程是“编写写代码的代码”。在 Python 中,类也是对象,元类 (Metaclass) 就是用来创建类的类。
默认情况下,type 是所有类的元类。
class 关键字背后的逻辑:
# class MyClass: pass# 等价于:MyClass = type('MyClass', (), {})自定义元类
自定义元类通常继承自 type,并重写 __new__ 或 __init__。可以在类创建时修改类的定义(自动添加方法、验证属性等)。
class AutoDebugMeta(type): """自动给类中的所有方法添加打印调试信息的元类""" def __new__(mcs, name, bases, attrs): new_attrs = {} for key, value in attrs.items(): if callable(value) and not key.startswith("__"): # 包装函数 def wrapper(*args, **kwargs): print(f"Calling {key}...") return value(*args, **kwargs) new_attrs[key] = wrapper else: new_attrs[key] = value
return super().__new__(mcs, name, bases, new_attrs)
class MyService(metaclass=AutoDebugMeta): def process(self): print("Processing...")
s = MyService()s.process()# 输出:# Calling process...# Processing...6. 魔术方法大全
除了常见的 __init__, __str__,Python 提供了极其丰富的魔术方法。
属性访问控制
__getattr__(self, name): 访问不存在的属性时调用(兜底)。__getattribute__(self, name): 访问任何属性时都会调用(拦截所有访问,慎用,易递归)。__setattr__(self, name, value): 设置属性时调用。
容器模拟
__len__(self)__getitem__(self, key)__setitem__(self, key, value)__delitem__(self, key)__iter__(self)__contains__(self, item):in操作符。
上下文管理
__enter__,__exit__:with语句支持。
调用
__call__: 让实例像函数一样被调用instance()。
7. 内存管理与垃圾回收
Python 使用引用计数 (Reference Counting) 为主,标记-清除 (Mark and Sweep) 和 分代回收 (Generational Collection) 为辅的垃圾回收机制。
7.1 __del__ 析构方法
当对象的引用计数降为 0 时,__del__ 会被调用。
警告:尽量不要依赖 __del__ 来进行资源释放(如关闭文件),因为在循环引用等复杂情况下,它可能不会被立即调用,甚至不会被调用。应使用上下文管理器 (with)。
7.2 弱引用 (Weak Reference)
weakref 模块允许创建不增加引用计数的引用。常用于缓存实现,避免对象无法被回收。
import weakref
class Data: def __del__(self): print("Data died")
d = Data()r = weakref.ref(d) # 创建弱引用
print(r()) # 获取对象: <__main__.Data object ...>del d # 删除唯一强引用,对象立即被回收,输出 "Data died"print(r()) # None结语
Python 的面向对象远比表面看起来深奥。从简单的 class 定义,到背后的元类机制、描述符协议以及 C3 算法,Python 提供了一套逻辑自洽且极具扩展性的对象模型。
掌握这些细节,不仅能让你写出更高效、更健壮的代码,更能让你在阅读 Django, SQLAlchemy 等顶级框架源码时游刃有余。
发现错误或想要改进这篇文章?
在 GitHub 上编辑此页