ZetCode

Python __mro_entries__ 方法

最后修改于 2025 年 4 月 8 日

这篇全面的指南探讨了 Python 的 __mro_entries__ 方法,这是一个特殊的允许自定义方法解析顺序 (MRO) 计算的方法。 我们将介绍它的目的、协议类、多重继承模式和实际示例。

基本定义

__mro_entries__ 方法允许对象在类继承中使用时指定它们的基类。 它在类创建期间被调用,以在 MRO 计算之前修改基类列表。

主要特征:它必须接受原始的 bases 元组,返回替换基类的元组,并启用协议风格的编程。 它在 Python 3.7 中作为 PEP 560 的一部分被引入。

基本的 __mro_entries__ 实现

这是一个简单的实现,展示了 __mro_entries__ 如何在继承期间替换基类。 这演示了它的基本行为。

basic_mro_entries.py
class ProtocolBase:
    def __mro_entries__(self, bases):
        print(f"Replacing {self} in bases {bases}")
        return (object,)

class MyClass(ProtocolBase()):  # Note the instantiation
    pass

print(MyClass.__mro__)

这个例子展示了 __mro_entries__ 如何替换一个基类。 ProtocolBase 实例在实际继承中被 object 替换。 输出显示修改后的 MRO。

该方法接收原始的 bases 元组并返回新的基类。 这发生在 Python 计算类的 Method Resolution Order 之前。

实现协议类

__mro_entries__ 通常用于实现不出现在最终继承层次结构中但强制执行接口的协议类。

protocol_class.py
class Serializable:
    def __mro_entries__(self, bases):
        return ()  # Remove from final bases
    
    def __init_subclass__(cls):
        # Check required methods exist
        if not hasattr(cls, 'serialize'):
            raise TypeError("Missing serialize method")
        if not hasattr(cls, 'deserialize'):
            raise TypeError("Missing deserialize method")

class Data(Serializable()):  # Note parentheses
    def serialize(self):
        return "data"
    
    def deserialize(self, data):
        self.value = data

# This would raise TypeError:
# class BadData(Serializable()): pass

这个 Serializable 协议在子类化期间检查所需的方法,但不出现在最终的类层次结构中。 空元组返回将其从 bases 中移除。

__init_subclass__ 钩子执行接口验证。 这种模式用于 Python 的 typing.Protocol 实现。

简化多重继承

__mro_entries__ 可以通过用实际基类替换代理对象来简化复杂的多重继承场景。

multiple_inheritance.py
class JSONMixin:
    def __mro_entries__(self, bases):
        from json import JSONEncoder
        return (JSONEncoder,)

class XMLMixin:
    def __mro_entries__(self, bases):
        from xml.etree.ElementTree import Element
        return (Element,)

class DataProcessor(JSONMixin(), XMLMixin()):
    pass

print(DataProcessor.__mro__)

这个例子展示了 mixin 类如何动态插入实际基类。 DataProcessor 最终直接继承自 JSONEncoder 和 Element。

这种模式有助于避免复杂的导入时依赖关系,同时在最终类中保持清晰的继承关系。

动态基类选择

__mro_entries__ 可以根据运行时条件或配置动态选择基类,从而实现灵活的类创建。

dynamic_bases.py
class BackendSelector:
    def __init__(self, backend_type):
        self.backend_type = backend_type
    
    def __mro_entries__(self, bases):
        if self.backend_type == 'sql':
            from .sql_backend import SQLBackend
            return (SQLBackend,)
        else:
            from .nosql_backend import NoSQLBackend
            return (NoSQLBackend,)

class Database(BackendSelector('sql')()):
    pass

print(Database.__mro__)

这个例子演示了基于配置的运行时基类选择。 BackendSelector 在类创建期间在 SQL 和 NoSQL 后端之间进行选择。

BackendSelector 之后的括号创建一个参与类定义的实例。 这种模式对于插件系统非常有用。

兼容性垫片

__mro_entries__ 可以提供兼容性垫片,在不更改现有代码的情况下使旧类层次结构适应新的类层次结构。

compatibility_shim.py
class LegacyAdapter:
    def __mro_entries__(self, bases):
        # Replace with modern equivalent
        from modern import NewBase
        return (NewBase,)
    
    # Provide legacy interface
    def old_method(self):
        instance = self.__class__()
        return instance.new_method()

class MyClass(LegacyAdapter()):
    def new_method(self):
        return "Modern implementation"

obj = MyClass()
print(obj.old_method())  # Works via adapter

这个兼容性垫片用其现代等效项替换了旧的基类,同时保持了旧的接口。 适配器转换方法调用。

__mro_entries__ 方法在类创建期间透明地启用此转换,从而帮助进行库迁移。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程