【问题标题】:Force a class with multiple inheritance to have a specific metaclass in Python强制具有多重继承的类在 Python 中具有特定的元类
【发布时间】:2020-02-17 06:25:26
【问题描述】:

我有一个类(Marshmallow schema),它是两个父母的子类:

class OptionalLinkSchema(JsonApiSchema, ModelSchema): pass
  • JsonApiSchema 具有元类 SchemaMeta
  • ModelSchema 具有元类 ModelSchemaMeta(SchemaMeta)(它是 ModelSchema 的子类)。

现在,我想要我的班级使用 ModelSchemaMeta 元类,我只想要更简单的 SchemaMeta。但是,根据Python docs,“选择了最衍生的元类”,这意味着无论我做什么,都会选择ModelSchemaMeta作为元类。

即使我尝试手动选择元类,也会发生同样的事情:

class OptionalLinkSchema(JsonApiSchema, ModelSchema, metaclass=SchemaMeta): pass

print(type(OptionalLinkSchema))
<class 'marshmallow_sqlalchemy.schema.ModelSchemaMeta'>

有没有办法覆盖 Python 总是选择最派生的元类的行为?

【问题讨论】:

    标签: python python-3.x metaclass marshmallow


    【解决方案1】:

    首先要做的事情:我应该警告你,你可能有一个“xy”问题: 如果一个类被设计为使用其元类中的机制进行计数,如果在创建它时没有运行这些机制,那么该类很可能无法工作。

    第二件事:语言不会“靠自己”做到这一点。它不会因为上述原因而破坏事物。因此,可以采取一些方法,这些方法都是侵入性的,并且期望您知道自己在做什么。如果您的OptionaLinkSchema 依赖于ModelSchemaMeta 上的任何功能,它就会中断。

    我可以想到 3 种方法来实现“剥离继承的元类”的等价物

    第一种方法

    您没有就您的问题发表任何意见,但我在 Marshmallow 的源代码树中没有看到 ModelSchemaMeta - 它是您创建的元类吗?如果是这样,由于 Marshmallow 使用嵌套 Meta 类的“djangoish”习语来声明有关类本身的内容,因此您可以在此 Meta 命名空间中使用属性来跳过您自己的元类。

    如果是这样,请将这样的代码添加到您不需要的元类的 __new__ 方法中(我从 Marshmallow 的源代码本身中选择“元”解析代码):

    class ModelSchemaMeta(SchemaMeta):
    
        def __new__(mcs, name, bases, attrs):
            meta = attrs.get("Meta")
            if getattr(meta, "skip_model_schema_meta", False):
                # force ShemaMeta metaclass
                cls = super().__new__(SchemaMeta, name, bases, attrs)
                # manually call `__init__` because Python does not do that
                # if the value returned from `__new__` is not an instance
                # of `mcs`
                cls.__init__(name, bases, attrs)
                return cls
            # Your original ModelSchemaMeta source code goes here
    
    
    
    class OptionalLinkSchema(...):
        ...
    
        class Meta:
            skip_model_schema_meta = True
            ...
    

    第二种方法

    执行此操作的一种更具侵入性的方式,但适用于任何类继承树的方法是编写代码,该代码将采用具有不需要的元类的超类并创建它的克隆。但是,由于涉及到 type 以外的自定义元类,在这种情况下,这种工作的可能性很小 - 因为“克隆”过程不会为“祖父母”元类提供它想要转换为的预期原始字段最终类中的属性。此外,Marsmallow 使用类注册表 - 创建这样的中间克隆也会在其中注册克隆。

    简而言之:这种方法不适用于您的情况,但我在这里描述它,因为它可能对其他遇到此问题的人有用:

    def strip_meta(cls,):
        "builds a clone of cls, stripping the most derived metaclass it has.
    
        There are no warranties the resulting cls will work at all - specially
        if one or more of the metaclasses that are being kept make transformations
        on the attributes as they are declared in the class body.
        "
        new_meta = type(cls).__mro__[1:]
        return (new_meta(cls.__name__, cls.__mro__, dict(cls.__dict__)))
    
    
    
    class OptionalLinkSchema(FirstClass, strip_meta(SecondClass)):
        ...
    
    

    (如果 SecondClass 本身是从使用不需要的元类的其他人派生的,则任何人都试图使用此方法,则必须修改策略以递归地剥离超类的元类)

    第三种方法

    另一种更简单的方法是手动创建派生元类,然后硬编码对所需元类的调用,跳过超类。 这个东西可能会产生足够“清醒”的代码,可以在生产中实际使用。

    因此,您不要“设置任意元类” - 相反,您创建一个有效的元类来执行您想要的操作。

    class NewMeta(ModelSchemaMeta, SchemaMeta):
        """Order of inheritance matters: we ant to skip methods on the first 
        baseclass, by hardwiring calls to SchemaMeta. If we do it the other way around,
        `super` calls on SchemaMeta will go to ModelSchemaMeta).
    
        Also, if ModeSchemaMeta inherits from SchemaMeta, you can inherit from it alone
    
        """
    
        def __new__(*args, **kw):
            return SchemaMeta.__new__(*args, **kw)
    
    
        def __init__(*args, **kw):
            return SchemaMeta.__init__(*args, **kw)
    
        # repeat for other methos you want to use from SchemaMeta
        # also, undesired attributes can be set to "None":
    
        undesired_method_in_model_schema_meta = None
    
    

    这与第一种方法的区别在于ModelSchemaMeta 将是元类的继承树,而最终的元类将是这个新的派生元类。但这并不取决于您对 ModelSchemaMeta 代码的控制权——而且,正如我之前所说,这比第二种方法更“符合语言的预期规则”。

    【讨论】:

    • 谢谢!第三个选项看起来很有希望,我会试一试
    • 澄清一下,ModelSchemaModelSchemaMeta 定义在 marshmallow-sqlalchemy 库中,JsonApiSchema 定义在 marshmallow-jsonapi 中,所以我实际上没有任何控制权这些类中的任何一个。
    • 我不知道在你的项目中你用 Marshmallow 编写代码有多远 - 但我最近发现的一个库是 pydantic - pydantic-docs.helpmanual.io - 我发现它非常方便,因为它利用了键入注释以创建架构。
    猜你喜欢
    • 2014-11-15
    • 2020-11-01
    • 2019-12-12
    • 2019-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    相关资源
    最近更新 更多