跳转至

07 - 元类与描述符

学习时间: 90 分钟 重要性: ⭐⭐⭐ 深入理解 Python 对象模型


🎯 学习目标

  • 理解 Python 的对象模型和类的创建过程
  • 掌握元类的基本概念和使用方法
  • 理解描述符协议及其应用场景
  • 知道何时(以及何时不要)使用这些高级特性

🧠 元类( Metaclass )

什么是元类

在 Python 中,一切皆对象——包括类本身。类是由元类创建的对象。

Python
# Python 3.10+ 语法

# 类的类型是什么?
class MyClass:
    pass

print(type(MyClass))  # <class 'type'>
print(type(int))      # <class 'type'>
print(type(str))      # <class 'type'>

# type 是所有类的默认元类
# type 本身也是一个类,它的类型是它自己
print(type(type))     # <class 'type'>

类创建的本质

Python
# 这两种定义方式是等价的:

# 方式一:class 语句
class Dog:
    species = "Canis familiaris"

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

    def bark(self) -> str:
        return f"{self.name} says woof!"

# 方式二:直接调用 type()
Dog = type(
    "Dog",                          # 类名
    (),                             # 基类元组
    {                               # 类属性字典
        "species": "Canis familiaris",
        "__init__": lambda self, name: setattr(self, "name", name),
        "bark": lambda self: f"{self.name} says woof!",
    }
)

定义自定义元类

Python
# 自定义元类继承自 type
class MyMeta(type):
    def __new__(mcs, name: str, bases: tuple, namespace: dict):
        """
        创建类对象

        参数:
            mcs: 元类本身
            name: 新类的名称
            bases: 基类元组
            namespace: 类的命名空间字典(包含属性和方法)
        """
        print(f"Creating class: {name}")
        print(f"Bases: {bases}")
        print(f"Namespace keys: {list(namespace.keys())}")

        # 调用父类 (type) 的 __new__ 创建类对象
        cls = super().__new__(mcs, name, bases, namespace)
        return cls

    def __init__(cls, name: str, bases: tuple, namespace: dict):
        """初始化类对象(在 __new__ 之后调用)"""
        print(f"Initializing class: {name}")
        super().__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        """当类被调用时(即创建实例时)"""
        print(f"Creating instance of {cls.__name__}")
        instance = super().__call__(*args, **kwargs)
        return instance


# 使用自定义元类
class MyClass(metaclass=MyMeta):
    class_attr = "hello"

    def __init__(self, value: int):
        self.value = value

# 输出:
# Creating class: MyClass
# Bases: ()
# Namespace keys: ['__module__', '__qualname__', 'class_attr', '__init__']
# Initializing class: MyClass

obj = MyClass(42)
# 输出:
# Creating instance of MyClass

实用元类示例:单例模式

Python
class SingletonMeta(type):
    """单例元类:确保类只有一个实例"""

    _instances: dict[type, object] = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]


class Database(metaclass=SingletonMeta):
    def __init__(self, connection_string: str):
        self.connection_string = connection_string
        print(f"Connecting to {connection_string}")


# 测试单例
db1 = Database("postgresql://localhost/mydb")  # 打印连接信息
db2 = Database("postgresql://localhost/other")  # 不打印,返回已有实例

print(db1 is db2)  # True
print(db1.connection_string)  # postgresql://localhost/mydb

实用元类示例:自动注册

Python
class PluginRegistry(type):
    """插件注册元类:自动收集所有插件类"""

    plugins: dict[str, type] = {}

    def __new__(mcs, name: str, bases: tuple, namespace: dict):
        cls = super().__new__(mcs, name, bases, namespace)

        # 不注册基类
        if bases:  # 只有子类才注册
            mcs.plugins[name] = cls
            print(f"Registered plugin: {name}")

        return cls

    @classmethod
    def get_plugin(mcs, name: str) -> type | None:
        return mcs.plugins.get(name)


class BasePlugin(metaclass=PluginRegistry):
    """插件基类"""
    def execute(self):
        raise NotImplementedError


class JsonPlugin(BasePlugin):
    def execute(self):
        return "Processing JSON"


class XmlPlugin(BasePlugin):
    def execute(self):
        return "Processing XML"


# 输出:
# Registered plugin: JsonPlugin
# Registered plugin: XmlPlugin

# 使用插件
print(PluginRegistry.plugins)
# {'JsonPlugin': <class 'JsonPlugin'>, 'XmlPlugin': <class 'XmlPlugin'>}

plugin_cls = PluginRegistry.get_plugin("JsonPlugin")
if plugin_cls:
    plugin = plugin_cls()
    print(plugin.execute())  # Processing JSON

⚠️ 何时使用元类

适合使用元类的场景: - 框架开发(如 Django ORM 、 SQLAlchemy ) - 插件系统和自动注册 - 强制 API 规范 - 单例等设计模式

不建议使用元类的场景: - 简单的类定制(使用类装饰器更简单) - 可以用 __init_subclass__ 解决的问题( Python 3.6+)

Python
# Python 3.6+ 的 __init_subclass__ 是更简单的替代方案

class BasePlugin:
    plugins: dict[str, type] = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        BasePlugin.plugins[cls.__name__] = cls
        print(f"Registered: {cls.__name__}")


class JsonPlugin(BasePlugin):
    pass  # 自动注册

class XmlPlugin(BasePlugin):
    pass  # 自动注册

print(BasePlugin.plugins)
# {'JsonPlugin': <class 'JsonPlugin'>, 'XmlPlugin': <class 'XmlPlugin'>}

🔧 描述符( Descriptor )

什么是描述符

描述符是实现了描述符协议的对象,用于自定义属性的访问、设置和删除行为。

描述符协议: - __get__(self, obj, objtype=None) - 获取属性时调用 - __set__(self, obj, value) - 设置属性时调用 - __delete__(self, obj) - 删除属性时调用 - __set_name__(self, owner, name) - 描述符被赋值给类属性时调用( Python 3.6+)

Python
class Descriptor:
    """描述符协议示例"""

    def __set_name__(self, owner: type, name: str):
        """当描述符被赋值给类属性时调用"""
        print(f"Descriptor assigned to {owner.__name__}.{name}")
        self.name = name
        self.private_name = f"_{name}"

    def __get__(self, obj, objtype=None):
        """获取属性值"""
        if obj is None:
            # 通过类访问(如 MyClass.attr)
            return self
        return getattr(obj, self.private_name, None)

    def __set__(self, obj, value):
        """设置属性值"""
        print(f"Setting {self.name} = {value}")
        setattr(obj, self.private_name, value)

    def __delete__(self, obj):
        """删除属性"""
        print(f"Deleting {self.name}")
        delattr(obj, self.private_name)


class MyClass:
    attr = Descriptor()  # 触发 __set_name__

    def __init__(self, value: int):
        self.attr = value  # 触发 __set__


# 输出:
# Descriptor assigned to MyClass.attr

obj = MyClass(42)
# 输出:
# Setting attr = 42

print(obj.attr)  # 触发 __get__,输出: 42

实用描述符示例:类型验证

Python
class Typed:
    """类型验证描述符"""

    def __init__(self, expected_type: type):
        self.expected_type = expected_type

    def __set_name__(self, owner: type, name: str):
        self.name = name
        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 isinstance(value, self.expected_type):
            raise TypeError(
                f"{self.name} must be {self.expected_type.__name__}, "
                f"got {type(value).__name__}"
            )
        setattr(obj, self.private_name, value)


class Person:
    name = Typed(str)
    age = Typed(int)

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


# 正常使用
person = Person("Alice", 25)
print(person.name, person.age)  # Alice 25

# 类型错误
try:
    person.age = "not an int"
except TypeError as e:
    print(e)  # age must be int, got str

实用描述符示例:范围验证

Python
class Range:
    """范围验证描述符"""

    def __init__(self, min_value: float | None = None, max_value: float | None = None):
        self.min_value = min_value
        self.max_value = max_value

    def __set_name__(self, owner: type, name: str):
        self.name = name
        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 self.min_value is not None and value < self.min_value:
            raise ValueError(f"{self.name} must be >= {self.min_value}")
        if self.max_value is not None and value > self.max_value:
            raise ValueError(f"{self.name} must be <= {self.max_value}")
        setattr(obj, self.private_name, value)


class Product:
    price = Range(min_value=0)
    quantity = Range(min_value=0, max_value=10000)
    discount = Range(min_value=0, max_value=1)

    def __init__(self, price: float, quantity: int, discount: float = 0):
        self.price = price
        self.quantity = quantity
        self.discount = discount


# 正常使用
product = Product(99.99, 100, 0.1)

# 验证错误
try:
    product.price = -10
except ValueError as e:
    print(e)  # price must be >= 0

try:
    product.discount = 1.5
except ValueError as e:
    print(e)  # discount must be <= 1

组合描述符:构建验证系统

Python
from abc import ABC, abstractmethod


class Validator(ABC):
    """验证器基类"""

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

    @abstractmethod
    def validate(self, value) -> None:
        """子类实现具体验证逻辑"""
        pass


class String(Validator):
    def __init__(self, min_length: int = 0, max_length: int | None = None):
        self.min_length = min_length
        self.max_length = max_length

    def validate(self, value) -> None:
        if not isinstance(value, str):
            raise TypeError(f"{self.name} must be a string")
        if len(value) < self.min_length:
            raise ValueError(f"{self.name} must be at least {self.min_length} characters")
        if self.max_length and len(value) > self.max_length:
            raise ValueError(f"{self.name} must be at most {self.max_length} characters")


class PositiveNumber(Validator):
    def validate(self, value) -> None:
        if not isinstance(value, (int, float)):
            raise TypeError(f"{self.name} must be a number")
        if value <= 0:
            raise ValueError(f"{self.name} must be positive")


class Email(Validator):
    def validate(self, value) -> None:
        if not isinstance(value, str):
            raise TypeError(f"{self.name} must be a string")
        if "@" not in value or "." not in value.split("@")[-1]:
            raise ValueError(f"{self.name} must be a valid email address")


class User:
    username = String(min_length=3, max_length=20)
    email = Email()
    age = PositiveNumber()

    def __init__(self, username: str, email: str, age: int):
        self.username = username
        self.email = email
        self.age = age


# 使用
user = User("alice", "alice@example.com", 25)

try:
    user.username = "ab"  # 太短
except ValueError as e:
    print(e)

try:
    user.email = "invalid-email"  # 无效邮箱
except ValueError as e:
    print(e)

数据描述符 vs 非数据描述符

Python
# 数据描述符:实现了 __get__ 和 __set__(或 __delete__)
# 非数据描述符:只实现了 __get__

class DataDescriptor:
    """数据描述符"""
    def __get__(self, obj, objtype=None):
        return "data descriptor get"

    def __set__(self, obj, value):
        pass  # 即使不做任何事,也算数据描述符


class NonDataDescriptor:
    """非数据描述符"""
    def __get__(self, obj, objtype=None):
        return "non-data descriptor get"


class MyClass:
    data = DataDescriptor()
    non_data = NonDataDescriptor()


obj = MyClass()

# 数据描述符优先级高于实例属性
obj.__dict__["data"] = "instance value"
print(obj.data)  # "data descriptor get" (描述符优先)

# 实例属性优先级高于非数据描述符
obj.__dict__["non_data"] = "instance value"
print(obj.non_data)  # "instance value" (实例属性优先)

属性查找优先级: 1. 数据描述符 2. 实例属性(obj.__dict__) 3. 非数据描述符 4. 类属性 5. __getattr__(如果定义了)


🎯 最佳实践

元类 vs 类装饰器 vs __init_subclass__

Python
# 根据需求选择合适的工具:

# 1. 简单的类修改 → 类装饰器
def add_repr(cls):
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@add_repr
class Point:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

# 2. 子类自动处理 → __init_subclass__
class BaseModel:
    _registry = {}

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        BaseModel._registry[cls.__name__] = cls

# 3. 深度定制类创建行为 → 元类
# 仅在需要在类创建过程中进行复杂操作时使用

描述符 vs property

Python
# property 适合单个类的简单属性逻辑
class Circle:
    def __init__(self, radius: float):
        self._radius = radius

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, value: float):
        if value < 0:
            raise ValueError("Radius must be non-negative")
        self._radius = value


# 描述符适合可复用的属性逻辑
class NonNegative:
    def __set_name__(self, owner, name):
        self.name = name
        self.private_name = f"_{name}"

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name, 0)

    def __set__(self, obj, value):
        if value < 0:
            raise ValueError(f"{self.name} must be non-negative")
        setattr(obj, self.private_name, value)


class Rectangle:
    width = NonNegative()
    height = NonNegative()

    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

✅ 本章小结

概念 用途 复杂度
元类 控制类的创建过程 ⭐⭐⭐⭐⭐
__init_subclass__ 简化的子类注册 ⭐⭐
类装饰器 类创建后的修改 ⭐⭐
描述符 自定义属性访问 ⭐⭐⭐
property 单属性的简单逻辑

记住: - 元类是强大但复杂的工具,优先考虑更简单的替代方案 - 描述符非常适合创建可复用的验证逻辑 - property 是最轻量的属性定制方式


📌 下一步:学习 06-并发与异步编程 掌握 Python 的并发模型。