在 Python metaclass 中添加带有 class 的动态 属性

Add dynamic property with class in Python metaclass

我将 mongoengine 与 django rest 框架一起使用。我的模特:

import mongoengine as mongo
class Plan(mongo.Document):
    slug = mongo.StringField(max_length=255, primary_key=True)
    subplans = mongo.ListField(mongo.EmbeddedDocumentField('self'))

我需要看起来像这样的序列化程序:

class PlanSerializer(serializers.DocumentSerializer):
    subplans = PlanSerializer(many=True, required=False)

    class Meta:
        model = Plan

但是 Python 不正确。所以我使用 metaclass 动态添加 subplans 字段:

class AddSubplanAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        # this code is incorrect because PlanSerializer not in globals
        class_obj = globals()[name]
        dct['subplans'] = class_obj(many=True, required=False)
        return super(AddSubplanAttrMetaclass, cls).__new__(cls, name, bases, dct)

class PlanSerializer(serializers.DocumentSerializer, metaclass=AddSubplanAttrMetaclass):

    class Meta:
        model = Plan

如何在 metaclass 的 __new__ 方法中将 PlanSerializer class 设置为 属性?

你遇到的问题是,当你尝试使用这条线 subplans = PlanSerializer(many=True, required=False) 并且在尝试使用 metaclass 时,行 class_obj = globals()[name] 当您的 PlanSerializerclass 本身尚未定义时。 (在 How is super() in Python 3 implemented? 查看我的回答)

在 metaclass 中做到这一点的正确方法是首先调用超级class 的新对象 - returns 你就是实际的 class 对象,然后调用该对象 - 沿:

class AddSubplanAttrMetaclass(type):
    def __new__(metacls, name, bases, dct):
        # this code is incorrect because PlanSerializer not in globals
        class_obj = super(AddSubplanAttrMetaclass, cls).__new__(metacls, name, bases, dct)
        class_obj.subplans = class_obj(many=True, required=False)
        return class_obj

但这都不是必需的,并且可能仍然存在问题 - 因为当您仍在 metaclass 的 __new__ 中时,并非所有 class 初始化都已完成(甚至 __init__) 方法。例如,如果 PlanSerializer__init__ 方法本身将使用 super,该调用将失败 - super 只能在 class 之后使用完全初始化。

但是,您根本不需要元class - 您可能只需将 subplans 属性设置为描述符 - 然后懒惰地检索属性。

class PlanSerializer(serializers.DocumentSerializer):
    class Meta:
        model = Plan

PlanSerializer.subplans = PlanSerializer(many=True, required=False)

我说可能是因为如果 Mongo 需要在初始化 class 本身时设置属性,这将不起作用 - 如果是这种情况,您可以尝试求助于描述符对象.描述符只是一个实现 __get__ 方法的对象,如下所示。这通常是使用 @property 装饰器完成的,但这不适用于 class 级别属性,而您在这种情况下需要它。

class PlanSerializer(serializers.DocumentSerializer):
    class Subplans(object):
        serializer = None
        def __get__(self,  instance, owner):
            if not self.serializer:
                self.serializer = PlanSerializer(many=True, required=False)   
            return  self.serializer
    subplans = Subplans()

    class Meta:
        model = Plan

这样,对Subplans class 的调用延迟到实际使用时,而不是解析class 主体时,它应该可以。