在Python元类中使用类添加动态属性

时间:2016-11-07 12:12:50

标签: python django-rest-framework mongoengine 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来说不​​正确。所以我使用元类动态地添加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

如何将PlanSerializer类设置为元类__new__方法中的属性?

1 个答案:

答案 0 :(得分:0)

您遇到的问题是当您尝试使用该行时 subplans = PlanSerializer(many=True, required=False)当您尝试使用元类时,当您的PlanSerializerclass本身尚未定义时,行class_obj = globals()[name]。 (在How is super() in Python 3 implemented?)检查我的答案

在元类中执行此操作的正确方法是首先调用超类的新类 - 它返回实际的类对象,然后调用该对象 - 其中包含:

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

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

但是,您根本不需要元类 - 您可以简单地将subplans属性设置为描述符 - 并且懒惰地检索属性。

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

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

我之所以说可能是因为如果Mongo在初始化类本身时需要设置属性,那么它将无法工作 - 如果是这种情况,你可以尝试使用描述符对象。描述符只是实现__get__方法的对象,如下所示。这通常使用@property装饰器完成,但这对于类级别属性不起作用,这是本案例所需要的。

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类的调用的使用被延迟到实际使用时,而不是解析类主体的时间,它应该有效。