在Python中强制具有多重继承的类具有特定的元类

2024-10-01 22:38:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个类(a 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总是选择最派生元类的行为吗?你知道吗


Tags: docsschema情况pass手动子类classmarshmallow
1条回答
网友
1楼 · 发布于 2024-10-01 22:38:29

第一件事:我应该警告你,你可能有一个“xy”的问题: 如果一个类被设计为与元类中的机制一起工作,如果这些机制在创建时没有运行,那么这个类很可能无法工作。你知道吗

第二件事:语言不会“独自”做到这一点。它不会,因为它会因为上面的原因破坏东西。所以,有一些方法是可以采取的,都是侵入性的,希望你知道你在做什么。如果您的OptionaLinkSchema依赖于ModelSchemaMeta上的任何特性,它将简单地中断。你知道吗

我可以想出三种方法来实现“剥离继承的元类”的等价性

第一种方法

你没有对你的问题说什么,但是我在Marshmallow的源代码树中没有看到ModelSchemaMeta,它是你创建的元类吗?如果是这样的话,因为Marshmallow使用了嵌套的Meta类的“djangish”习惯用法来声明类本身的内容,所以可以使用这个Meta命名空间中的属性来跳过自己的元类。你知道吗

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

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)):
    ...

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

第三种方法

另一种更简单的方法是手动创建派生的元类,只对所需元类的调用进行硬编码,跳过超类。 这可能会产生足够“清醒”的代码,以便在生产中实际可用。你知道吗

因此,您不需要“设置任意元类”,而是创建一个有效的元类来执行您想要的操作。你知道吗

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代码的控制——正如我之前所说,这比第二种方法更符合语言的“预期规则”。你知道吗

相关问题 更多 >

    热门问题