跳过正文
Python 类
  1. Blog/

Python 类

·7099 字·15 分钟
目录
Python - 这篇文章属于一个选集。
§ 8: 本文

在 Python 的里,一切皆为对象。整数、字符串、列表,甚至函数,都是类的实例。类是创建对象的蓝图,它定义了一组属性(Attributes)和方法(Methods),封装了数据和操作数据的行为。

类与实例剖析
#

类的定义与实例化
#

使用 class 关键字定义一个类。类名通常采用驼峰命名法 (CamelCase)

class Dog:
    # 这是一个类属性,它被所有 Dog 类的实例共享
    species = "Canis familiaris"

    # __init__ 是一个特殊的初始化方法(构造器)
    # 当一个实例被创建时,__init__ 会被自动调用
    def __init__(self, name, age):
        # self 代表实例对象本身,必须作为方法的第一个参数
        # 下面是实例属性,它们属于每个独立的实例
        self.name = name
        self.age = age

    # 这是一个实例方法
    def bark(self):
        return f"{self.name} says Woof!"

实例化是根据蓝图创建具体对象的过程。

d1 = Dog("Buddy", 5)
print(f"{d1.name} is {d1.age} years old.")  # -> Buddy is 5 years old.
print(d1.bark())  # -> Buddy says Woof!

self 的本质
#

self 并非关键字,而是一个约定俗成的名称,指向实例对象本身。当你调用 d1.bark() 时,Python 解释器在内部会自动将其转换为 Dog.bark(d1)self 使得方法能够访问和操作实例自身的属性和方法。

类属性与实例属性
#

  • 类属性 (Class Attribute): 在 class 代码块内、所有方法之外定义。被该类的所有实例共享。通常用于定义该类所有对象共有的、不变的特性。
  • 实例属性 (Instance Attribute): 通常在 __init__ 方法中通过 self.attribute = value 定义。每个实例独有一份,用于描述每个对象特有的状态。
d2 = Dog("Lucy", 3)
print(d1.species) # -> Canis familiaris
print(d2.species) # -> Canis familiaris

# 修改类属性会影响所有实例
Dog.species = "A friendly dog"
print(d1.species)  # -> A friendly dog

类方法:超越实例的范畴
#

并非所有与类相关的功能都必须绑定到具体的实例上。

实例方法、类方法与静态方法
#

类型装饰器第一个参数用途
实例方法(无)self (实例)操作或访问实例的状态(实例属性)。这是最常见的方法类型。
类方法@classmethodcls (类)操作或访问类的状态(类属性)。最常用于工厂方法,即创建类的实例的替代构造器。
静态方法@staticmethod(无特定参数)作为一个与类相关的工具函数,它不依赖于类或实例的状态。逻辑上属于这个类,但功能上是独立的。

实践范例:工厂模式
#

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def from_string(cls, date_string):
        """工厂方法:通过 'YYYY-MM-DD' 格式的字符串创建 Date 实例。"""
        year, month, day = map(int, date_string.split('-'))
        return cls(year, month, day) # cls 就是 Date 类本身

    @staticmethod
    def is_valid_date(date_string):
        """静态方法:一个独立的工具函数,用于检查日期字符串的格式。"""
        try:
            year, month, day = map(int, date_string.split('-'))
            return True
        except (ValueError, TypeError):
            return False

# 使用
d1 = Date(2025, 11, 4)
d2 = Date.from_string("2025-12-25") # 使用类方法作为替代构造器,代码更具语义化

print(Date.is_valid_date("2025-02-30")) # True
print(d2.year) # -> 2025

继承:类的层次结构
#

继承允许一个类(子类)获取另一个类(父类)的属性和方法,是实现代码重用的核心机制。

基本继承与 super()
#

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

    def speak(self):
        raise NotImplementedError("Subclass must implement this abstract method")

class Bulldog(Dog): # Bulldog 继承自 Dog
    def __init__(self, name, age, wrinkle_level):
        # super() 返回一个代理对象,让你能调用父类的方法。
        # 这是实现方法扩展的标准做法,能正确处理复杂的多重继承。
        super().__init__(name, age)
        self.wrinkle_level = wrinkle_level

    # 覆盖 (Override) 父类的 bark 方法
    def bark(self):
        return f"{self.name} says Grrr!"

多重继承与方法解析顺序 (MRO)
#

Python 支持一个类从多个父类继承。当不同父类有同名方法时,Python 使用 C3 线性化算法来确定一个明确的方法解析顺序 (MRO)

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

# 可以通过 __mro__ 属性或 mro() 方法查看
print(D.__mro__)
# -> (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

MRO 保证了属性查找顺序的确定性和一致性,即使在复杂的菱形继承结构中也是如此。

说明:C3 线性化算法

C3 线性化算法(C3 Linearization Algorithm)是 Python(从 2.3 版本开始)用来确定多重继承中方法解析顺序(Method Resolution Order, MRO)的算法。它的核心任务是,当一个类继承自多个父类时,创建一个明确、一致且无歧义的方法和属性查找顺序列表。

1. 为什么需要一个复杂的算法?—— “菱形问题”

如果没有一个好的算法,多重继承会很快变得混乱。最经典的问题就是 “菱形问题” (The Diamond Problem)

graph TD
    A[class A] --> B[class B]
    A --> C[class C]
    B --> D[class D]
    C --> D[class D]
class A:
    def who_am_i(self):
        print("I am an A")

class B(A):
    def who_am_i(self):
        print("I am a B")

class C(A):
    def who_am_i(self):
        print("I am a C")

class D(B, C):
    pass

d = D()
d.who_am_i() # ???

当调用 d.who_am_i() 时,Python 应该调用哪个版本的方法?B 的还是 C 的?如果 BC 都没有这个方法,应该只调用一次 A 的方法,还是两次?

一个糟糕的 MRO 算法可能会导致:

  • 歧义:不知道该调用哪个方法。
  • 冗余调用:多次调用同一个基类的方法。
  • 不一致性:查找顺序违背了程序员的直觉。

C3 算法就是为了优雅地解决这些问题而生的。

2. C3 算法的三个指导原则

C3 算法之所以优秀,是因为它始终遵循三个关键原则:

  1. 子类优先于父类 (Children First):在 MRO 列表中,子类永远出现在其父类之前。
  2. 尊重父类顺序 (Parent Order Matters):在定义类时,父类的顺序 (class D(B, C):) 会被保留。在 MRO 列表中,B 会出现在 C 之前。
  3. 单调性 (Monotonicity):一个类的 MRO 是其所有父类 MRO 的延伸和扩展,而不会颠覆父类自身的继承顺序。这保证了继承链的一致性和可预测性。

3. C3 算法的工作原理

C3 算法的核心思想是合并(merge)父类的 MRO 列表。其基本公式可以表示为:

MRO(C) = [C] + merge(MRO(Parent1), MRO(Parent2), ..., [Parent1, Parent2, ...])

这里的 merge 操作是关键,它的规则如下:

  1. 从第一个父类的 MRO 列表的头部(第一个元素)开始。
  2. 检查这个头部元素是否出现在任何其他父类 MRO 列表的尾部(除头部外的所有元素)中。
  3. 如果“否”(它是一个“好头”),则将其从所有列表中取出,并添加到最终的 MRO 列表中。然后回到步骤 1。
  4. 如果“是”(它是一个“坏头”,意味着有其他类需要优先于它被解析),则跳过这个列表,去检查下一个父类 MRO 列表的头部。
  5. 重复以上过程,直到所有列表为空。如果无法找到一个“好头”但列表仍不为空,则意味着继承层次结构存在冲突,Python 会在定义类时就抛出 TypeError

4. 实例演练:破解菱形问题

让我们用 C3 算法手动计算上面菱形问题中 D 的 MRO。

  • 已知 MROs: (为简化,我们假设它们都继承自 object)
    • MRO(object) = [object]
    • MRO(A) = [A, object]
    • MRO(B) = [B, A, object]
    • MRO(C) = [C, A, object]
  • 计算 MRO(D):
    • MRO(D) = [D] + merge(MRO(B), MRO(C), [B, C])
    • 代入已知 MROs: merge([B, A, object], [C, A, object], [B, C])
  • 开始合并:
    1. 检查 B (第一个列表的头): B 没有出现在 [C, A, object] 的尾部,也没有出现在 [B, C] 的尾部。它是个好头
      • 取出 B
      • 当前 MRO: [D, B]
      • 待合并列表: [A, object], [C, A, object], [C]
    2. 检查 A (第一个列表的头): A 出现在了 [C, A, object] 的尾部。它是个坏头。跳过。
    3. 检查 C (第二个列表的头): C 没有出现在 [A, object] 的尾部。它是个好头
      • 取出 C
      • 当前 MRO: [D, B, C]
      • 待合并列表: [A, object], [A, object]
    4. 检查 A (第一个列表的头): A 没有出现在 [A, object] 的尾部。它是个好头
      • 取出 A
      • 当前 MRO: [D, B, C, A]
      • 待合并列表: [object], [object]
    5. 检查 object (第一个列表的头): object 是个好头
      • 取出 object
      • 当前 MRO: [D, B, C, A, object]
      • 待合并列表: [], [] (全部为空)
  • 最终结果:MRO(D) = [D, B, C, A, object]

现在,让我们用 Python 验证一下:

>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

结果完全一致!这意味着当调用 d.who_am_i() 时,Python 会:

  1. D 中查找。
  2. B 中查找(找到并执行 B 的版本)。
  3. 如果 B 中没有,则在 C 中查找。
  4. 如果 C 中也没有,则在 A 中查找。

5. C3 的健壮性:检测不一致的继承

C3 算法还能在类定义时就阻止不合逻辑的继承。

class X: pass
class Y: pass
class A(X, Y): pass
class B(Y, X): pass

class C(A, B): pass
# TypeError: Cannot create a consistent method resolution
# order (MRO) for bases Y, X

这里,A 的 MRO 要求 XY 之前,而 B 的 MRO 要求 YX 之前。C3 算法在合并时会发现无法同时满足这两个相互矛盾的条件,因此直接拒绝创建类 C,从而在源头上避免了混乱。

总结

C3 线性化算法是 Python 多重继承系统的智能核心。它不仅仅是一个技术细节,更是保证了 Python 的 OOP 模型既强大灵活,又保持了确定性、可预测性和安全性的关键所在。它优雅地解决了困扰许多其他语言的菱形继承问题,让开发者可以更有信心地使用多重继承。

对象模型控制与自动化
#

@property:受控属性伪装
#

@property 装饰器可以将一个方法转换为一个“只读属性”,并能为其添加验证逻辑的“设置器”和“删除器”,从而在不破坏简洁访问方式的前提下实现封装。

class Circle:
    def __init__(self, radius):
        self._radius = radius # 使用 _ 前缀约定为内部属性

    @property
    def radius(self):
        """Getter: 返回半径。"""
        return self._radius

    @radius.setter
    def radius(self, value):
        """Setter: 在设置值之前进行验证。"""
        if value <= 0:
            raise ValueError("Radius must be positive")
        self._radius = value

    @property
    def area(self):
        """一个通过计算得出的只读属性。"""
        return 3.14159 * (self._radius ** 2)

c = Circle(10)
print(c.radius)  # 像访问属性一样调用 getter
print(c.area)    # 访问计算属性
c.radius = 12    # 调用 setter,自动进行验证

数据类 (dataclasses)
#

对于主要用于存储数据的类,dataclasses 模块可以自动生成 __init__, __repr__, __eq__ 等大量模板代码,从而让开发者“告别”模板代码 (Python 3.7+)。

from dataclasses import dataclass

@dataclass(frozen=True) # frozen=True 使实例创建后不可变
class InventoryItem:
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

item1 = InventoryItem("Pen", 1.5, 100)
item2 = InventoryItem("Pen", 1.5, 100)
print(item1) # 自动生成了友好的 __repr__
print(item1 == item2) # True, 自动生成了 __eq__

dataclasses 极大提高了开发效率,是现代 Python 中构建数据模型类的首选方式。

性能与内存优化:__slots__
#

默认情况下,Python 实例使用一个字典 (__dict__) 来存储属性,这很灵活但内存开销大。当需要大规模创建实例时,__slots__ 可以显著优化性能和内存。

__slots__ 通过声明一组固定的属性,让 Python 使用更紧凑的内部结构替代 __dict__

class Point:
    __slots__ = ('x', 'y') # 声明实例只有 x 和 y 两个属性
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
# p.z = 3 # -> AttributeError,因为实例不能再动态添加新属性

属性存储__dict__:灵活、“昂贵”
#

工作原理

当你定义一个普通 Python 类(没有 __slots__)时,每个实例都会在内部维护一个名为 __dict__ 的字典。这个字典负责存储该实例的所有属性(包括方法的引用,尽管方法通常是类属性)。

class MyClass:
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.c = 0 # 运行时动态添加的属性

obj = MyClass(1, 2)

# obj.__dict__ 内部存储了这些信息:
# {'a': 1, 'b': 2, 'c': 0}

为什么“内存开销大”?

  • 字典本身的开销:Python 的字典是一种非常高效的数据结构,但它为了实现 O(1) 的平均查找时间,需要额外的空间来存储哈希表、键、值,以及处理哈希冲突等。即使实例只有一个属性,__dict__ 本身就有一个固定的最小开销。
  • 每个实例一个字典:这意味着如果你创建了 10,000 个 MyClass 的实例,你就创建了 10,000 个 __dict__ 对象。即使每个实例只存储几个简单的属性,这些字典加起来的内存占用也会相当可观。
  • 动态灵活性: __dict__ 允许你在运行时随意添加、修改、删除属性(如 obj.d = 4)。这种灵活性是以内存为代价的。

为什么“性能”有影响?

  • 查找的间接性:访问 obj.a 时,Python 实际上是先查找 obj 对象,找到它的 __dict__,然后在 __dict__ 中查找键 'a'。这个额外的查找步骤(虽然平均是 O(1))仍然比直接访问一个预先知道位置的内存空间要慢。
  • 字典操作的开销:添加、删除属性时,字典需要进行哈希计算、可能扩容等操作,这些都会消耗 CPU 时间。

在一个非常大的仓库(__dict__)里找东西。即使知道大概的位置,还是需要在这个巨大的仓库里搜索。如果有很多这样的仓库(每个实例一个),管理和搜索它们会变得很慢。


__slots__方案
#

工作原理

当定义 __slots__ 时,实际上是在告诉 Python:“这个类的实例只会拥有这些指定的属性,并且不需要为它们准备一个 __dict__。”

class Point:
    __slots__ = ('x', 'y') # 声明实例只有 x 和 y 两个属性

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

p = Point(1, 2)

"""
>>> p.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute '__dict__'. Did you mean: '__dir__'?
"""

在这种情况下,Point 的实例 p 不会拥有 __dict__。取而代之的是,Python 会为 xy 在实例内部预留固定的内存空间(通常是以 C 语言结构体的方式实现),并提供直接访问这些内存位置的方法。

如何“优化性能和内存”?

  • 内存优化
    • __dict__ 开销:最直接的节省就是省去了每个实例持有一个字典的开销。
    • 固定内存占用:每个实例的内存占用就等于其声明的属性所占用的空间,加上少量对象管理的开销。这比一个动态字典的总开销要小得多,尤其是在属性数量不多但实例数量庞大的情况下。
  • 性能优化
    • 直接访问:访问 p.x 时,Python 可以直接根据预定义的结构(相当于 C 语言中的结构体成员访问)定位并读取内存,速度更快。
    • 避免动态操作开销:因为属性是固定的,Python 不需要执行字典的哈希、查找、扩容等动态操作,从而提高了属性访问和修改的速度。

使用 __slots__ 就像是为实例设计了一个固定大小的、带有明确标签的工具箱。每个工具(属性)都有其固定位置。查找和取用工具时,直接知道在哪里找,非常快。而且,这个工具箱比前面那个大仓库(__dict__)小很多,也更容易携带。


实际节省效果
#

通过一个简单的实验来量化内存节省:

# %%
# 导入所需库
import timeit
import gc
import psutil
import os
import sys

# --- 定义测试用的类 ---

class PointWithDict:
    """使用默认 __dict__ 的类"""
    def __init__(self, x, y):
        self.x = x
        self.y = y

class PointWithSlots:
    """使用 __slots__ 优化内存的类"""
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y

# --- 测试函数定义 ---

def measure_memory(cls_to_test, num_instances):
    """
    测量创建指定数量的实例所导致的进程内存增量。
    返回值为字节数。
    """
    # 在测量前运行垃圾回收,确保一个干净的初始状态
    gc.collect()

    # 获取当前进程对象
    process = psutil.Process(os.getpid())
    
    # 记录创建对象前的内存占用
    mem_before = process.memory_info().rss

    # 创建对象列表
    objects = [cls_to_test(i, i * 2) for i in range(num_instances)]
    
    # 记录创建对象后的内存占用
    mem_after = process.memory_info().rss
    
    # 清理创建的对象,避免影响后续测试
    del objects
    gc.collect()
    
    # 返回内存增量
    return mem_after - mem_before

def measure_performance(cls_to_test, num_instances):
    """
    测量对象的创建、访问和修改性能。
    返回一个包含三个时间值的元组。
    """
    # 1. 测量创建时间
    # timeit 的 setup 参数用于准备环境,stmt 是要重复执行的语句
    creation_stmt = f"[{cls_to_test.__name__}(i, i*2) for i in range({num_instances})]"
    creation_setup = f"from __main__ import {cls_to_test.__name__}"
    # 我们只执行一次创建操作,因为创建大量对象本身就很耗时
    creation_time = timeit.timeit(stmt=creation_stmt, setup=creation_setup, number=1)

    # 准备用于访问和修改测试的对象列表
    objects = [cls_to_test(i, i * 2) for i in range(num_instances)]

    # 2. 测量访问时间
    def access_objects():
        total = 0
        for p in objects:
            total += p.x
            total += p.y
        return total
    
    # number 设置为 100,以获得更稳定的平均时间
    access_time = timeit.timeit(access_objects, number=100)

    # 3. 测量修改时间
    def modify_objects():
        for p in objects:
            p.x += 1
            p.y -= 1
            
    modify_time = timeit.timeit(modify_objects, number=100)
    
    # 清理对象
    del objects
    gc.collect()

    return creation_time, access_time, modify_time

def run_comparison(cls_to_test, num_instances):
    """
    运行并打印指定类的完整内存和性能测试结果。
    """
    print(f"--- 正在测试: {cls_to_test.__name__} ---")
    
    # 内存测试
    mem_used = measure_memory(cls_to_test, num_instances)
    print(f"内存占用增量: {mem_used / 1024**2:.2f} MB ({mem_used:,.0f} 字节)")

    # 性能测试
    creation, access, modification = measure_performance(cls_to_test, num_instances)
    print(f"  - 对象创建时间: {creation:.6f} 秒")
    print(f"  - 属性访问时间 (100次): {access:.6f} 秒")
    print(f"  - 属性修改时间 (100次): {modification:.6f} 秒")
    print("-" * 30 + "\n")

#%%
# 定义测试规模
NUM_INSTANCES = 500_000 # 使用下划线提高可读性

print(f"Python 版本: {sys.version.split()[0]}")
print(f"测试实例数量: {NUM_INSTANCES:,}")
print("=" * 40 + "\n")

# 对两个类分别进行测试
run_comparison(PointWithDict, NUM_INSTANCES)
run_comparison(PointWithSlots, NUM_INSTANCES)
# %%

实验结果:

  • PointWithSlots 实例的内存占用会比 PointWithDict 实例小很多。
  • 但是在实际测试中发现与官方文档中的描述存在差异——访问 PointWithSlots 实例属性的性能并不会明显优于 PointWithDict
# - Mac本地(3.11.8)
"""
Python 版本: 3.11.8
测试实例数量: 500,000
========================================

--- 正在测试: PointWithDict ---
内存占用增量: 80.04 MB (83,927,040 字节)
  - 对象创建时间: 0.188822 秒
  - 属性访问时间 (100次): 5.118392 秒
  - 属性修改时间 (100次): 7.855338 秒
------------------------------

--- 正在测试: PointWithSlots ---
内存占用增量: 48.02 MB (50,352,128 字节)
  - 对象创建时间: 0.203976 秒
  - 属性访问时间 (100次): 5.476134 秒
  - 属性修改时间 (100次): 6.850384 秒
------------------------------
"""

# - colab(3.11.13)
"""
Python 版本: 3.11.13
测试实例数量: 500,000
========================================

--- 正在测试: PointWithDict ---
内存占用增量: 82.04 MB (86,024,192 字节)
  - 对象创建时间: 0.293373 秒
  - 属性访问时间 (100次): 4.721711 秒
  - 属性修改时间 (100次): 4.083924 秒
------------------------------

--- 正在测试: PointWithSlots ---
内存占用增量: 48.43 MB (50,782,208 字节)
  - 对象创建时间: 0.162264 秒
  - 属性访问时间 (100次): 5.220282 秒
  - 属性修改时间 (100次): 4.003415 秒
------------------------------
"""

总结 __slots__ 的权衡
#

  • 优势:
    • 显著的内存节省:特别适合于需要创建数万甚至数百万个对象的场景。
    • 更快的属性访问和修改速度。与官方文档存在差异的主要原因可能在于 Python 3.6+ 版本对字典进行了重大优化,使得小字典的访问速度大幅提升 Stack Overflow
      • 紧凑字典(Compact Dict):Python 3.6+ 采用新的字典实现,内存布局更紧凑
      • 小字典优化:对于少量属性(如你的测试中只有 x, y 两个属性),字典查找已经非常快
      • 键共享字典:同一类的多个实例可以共享键,减少内存和提升速度
    • 阻止动态添加意外属性:有助于提高代码的健壮性,避免无意中引入新的状态。
  • 劣势:
    • 丧失灵活性:不能动态添加新属性。
    • 不支持 __dict__:你无法通过 instance.__dict__ 来查看或修改属性,也无法使用 vars(instance)
    • 继承时的注意事项:如果父类定义了 __slots__,子类也必须定义自己的 __slots__(或者在其 __slots__ 中包含父类的 slot 名称),否则子类将无法使用 __slots__,会重新获得 __dict__,导致一些潜在的混乱。
    • 对某些内省工具不友好:一些依赖 __dict__ 的第三方库或工具可能无法与 __slots__ 类完美工作。

何时使用 __slots__

当且仅当:

  1. 正在处理大量同类型的对象(数千到数百万)。
  2. 这些对象的属性集合是固定的,不会在运行时动态增减。
  3. 不关心实例的 __dict__,或者知道如何处理没有 __dict__ 的类。
  4. 内存占用属性访问速度是性能瓶颈,且 __slots__ 能提供可观的改善。

在其他情况下,__dict__ 的灵活性通常更受欢迎。

高级设计模式与底层机制
#

“私有”变量与名称改写
#

Python 没有真正的 private。但 __ (双下划线) 前缀会触发名称改写 (Name Mangling),解释器会将 __variable 改为 _ClassName__variable。这主要为了避免子类意外覆盖父类的内部同名属性,而不是为了实现数据隐藏。

class MyClass:
    def __init__(self):
        self.__super_private = "secret"

obj = MyClass()
# print(obj.__super_private) # -> AttributeError
print(obj._MyClass__super_private) # -> secret,仍可访问

抽象基类 (ABCs) - 定义接口规范
#

abc 模块允许你定义抽象基类,以强制其子类必须实现特定的方法。这在构建框架或定义插件接口时至关重要。

from abc import ABC, abstractmethod

class MediaLoader(ABC):
    @abstractmethod
    def load(self, source):
        """定义一个接口,子类必须实现它。"""
        pass

class ImageLoader(MediaLoader):
    def load(self, source): # 若不实现 load 方法,则在实例化时会报错
        print(f"Loading image from {source}")

# loader = MediaLoader() # -> TypeError: Can't instantiate abstract class...
img_loader = ImageLoader()

元类 (Metaclasses) - 创建类的类
#

官方文档:元类

这是 Python 对象模型最深邃的部分。元类是创建类的类。默认的元类是 type。当你写下 class MyClass: ... 时,Python 解释器在背后调用元类来创建 MyClass 这个类对象。

元类允许你在类被创建时自动地修改或增强类。这是 Django ORM、SQLAlchemy 等框架实现其“魔法”的核心技术,它们通过元类读取类定义,并自动生成与数据库交互所需的方法和属性。

极少开发场景需要编写元类,但理解其概念至关重要。它解释了 Python 框架中许多强大功能的来源,并展示了 Python 对象模型的极致灵活性——不仅能控制对象的行为,还能控制类本身的行为。

Python - 这篇文章属于一个选集。
§ 8: 本文