Skip to main content
 首页 » 编程设计

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

2025年05月04日13dflying

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

请您参考如下方法:

首先要注意的是:我应该警告你,你可能有一个“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 
        ... 

第二种方法

这样做的一种更具侵入性的方法,但它适用于任何类继承树,它的代码将采用带有不需要的元类的父类(super class)并创建它的克隆。但是,由于涉及 type 以外的自定义元类,因此在这种情况下,这种工作的可能性很小 - 因为“克隆”过程不会为“祖 parent ”元类提供预期的原始字段想要转换为最终类中的属性。此外,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 本身是从使用不需要的元类的其他人派生的,则必须修改策略以递归地剥离父类(super class)的元类)

第三种方法

另一种更简单的方法是手动创建派生元类,然后将调用硬编码到所需的元类,跳过父类(super class)。 这件事可能会产生足够“清醒”的代码以实际用于生产。

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

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