实现特殊的元类。继承类中的非化字段

Posted

技术标签:

【中文标题】实现特殊的元类。继承类中的非化字段【英文标题】:Implement special metaclass. Nonify field in inherited class 【发布时间】:2017-10-27 22:18:36 【问题描述】:

我有一个任务:

实现元类“ModelCreator”,允许声明类 以下形式的字段:

class Student(object):  
    __metaclass__ = ModelCreator  
    name = StringField()

StringField 的位置 - 一些表明该字段是 文本域。所以必须有一个类,它的构造函数接收 命名参数“name”并将其存储在相应的属性中(使用 类型检查和强制转换)

所以你可以输入如下内容:

s = Student(name = 'abc')  
print s.name 

该类应允许继承并应验证此类 方式,您不能将数字写入文本字段。

这是我的实现,但继承类存在问题,它的“名称”字段不是空的(正如我所期望的那样)它从以前的类中接收名称值。

class StringField(object):
    def __init__(self, my_type, default=None):
        self.type = my_type
        self.name = None
        if default:
            self.default = default
        else:
            self.default = my_type()

    def __set__(self, instance, value):
        if isinstance(value, self.type):
            setattr(instance, self.name, value)
        else:
            raise TypeError("value must be type of ".format(self.type))

    def __get__(self, instance, owner):
        return getattr(instance, self.name, self.default)

    def __del__(self):
        raise AttributeError("you can`t remove this attribute ".format(self.name))


class ModelCreator(type):
    def __new__(mcs, name, bases, diction):
        socket = []
        for key, value in diction.iteritems():
            if isinstance(value, StringField):
                value.name = "_".format(key)
                socket.append(value.name)

        def new(cls, *args, **kwargs):
            for names in kwargs:
                if '_'.format(names) in diction['__slots__']:
                    if isinstance(kwargs[names], diction[names].type):
                        diction[names].default = kwargs[names]
                    else:
                        raise TypeError('attr has other type')
            return type(name, bases, diction)

        diction["__slots__"] = socket
        diction["__new__"] = new
        return super(ModelCreator, mcs).__new__(mcs, name, bases, diction)


class Student(object):
    __metaclass__ = ModelCreator
    name = StringField(str)


class School(Student):
    second_name = StringField(str)


def main():
       st = Student(name = "Hello")
       print st.name
       st.name = "Vlad"
       sc = School(second_name = "World")
       print sc.second_name, sc.name


if __name__ == '__main__':
    main()

此代码打印

你好 世界你好

但它应该(按任务)打印

你好 世界无

问题是:

为什么 type(st) 返回“ type 'type' ”? (我认为它是实例而不是类) 为什么 type(sc) 会返回“class 'ma​​in.ModelCreator'”?

如何在“Student”类中对“name”字段的值进行非统一化,所以它只会保存在“st”中(因为它现在以某种方式包含在“sc”中)?

【问题讨论】:

【参考方案1】:

这段代码有点复杂——但它所做的并不比你告诉它做的要少。

除了所需的描述符(即:包含__get____set__ 方法的类)和通常的元类机制之外,它的作用是将__new__ 方法插入到在多个方面出错的类中。

首先,分配给__new__ 类的new 方法通过使用对type. 的硬编码调用来结束其执行 - 这是最错误的事情 - 因为类型返回一个新类 - 而不是一个实例。插入的new 方法结束时的调用应该是object.__new__ - 或者更好的是,使用一种机制,在其__mro__ 的下一个类中调用__new__(但这不会是微不足道的- 因为您必须在您插入的 new 方法周围的元类 __new__ 代码中找到它。

无论如何 - 如果您希望使用此元类的类本身成为“类工厂”,那么在此处调用 type 才有意义 - 这不仅会返回带有声明字段的全新类,还会返回带有已发送-in 默认值。调用类型是您看到type(st) 返回type 的原因——这是您的第一个问题。

然后,它仍然是错误的:new 类方法,在每个实例化时调用,将默认属性设置为描述符(即“字段”) - 该默认值将应用于相同的所有其他实例化class - 或其他继承自它的类。您应该在调用 StringField 类时设置默认值(如果有),并在将成为类上的 __new__ 的方法上设置实例的值。

如果你首先调用超类__new__ 来获取一个实际的实例,然后循环传入的关键字参数,并使用setattr 作为设置属性的机制,就可以做到这一点。使用 setattr 将确保正确调用 StringField __set__ 方法。

因此,这段代码中有很多奇怪的地方,但尝试通过将元类 __new__ 重写为或多或少来修复它:

 def __new__(mcs, name, bases, diction):
    socket = set()
    # mechanism to inherit classes that make use of sockets:
    for base in bases:
        if hasattr(base, '__slots__'):
            socket.update(base.__slots__)
    for key, value in diction.iteritems():
        if isinstance(value, StringField):
            value.name = "_".format(key)
            socket.add(value.name)


    def __new__(cls, *args, **kwargs):
        # A working __new__ mechanism that respects inheritance.
        for supercls in cls.__mro__[1:]:
            if  '__new__' in supercls.__dict__:
                # don't pass args and kwargs up.
                # unless really there is distinct mechanism
                # to use args and kwargs than the StringField
                # class.
                # otherwise this would break any `__init__`
                # method you put in your classes.
                instance = supercls.__new__(cls)
                break  # the last class in __mro__ is object which always has '__new__'

        for names in kwargs:
            if '_'.format(names) in cls.__slots__:
                # no need to typecheck here. StringField's __set__ does that
                setattr(instance, kwargs[names])
        return instance

    diction["__slots__"] = list(socket)
    diction["__new__"] = __new__
    return super(ModelCreator, mcs).__new__(mcs, name, bases, diction)

也就是说,此时(2017 年)您不应该真正浪费时间研究 Python 2.7 中的这些高级机制——Python 2 的最后一个版本是在 2010 年,它将在 2020 年停止维护——这些机制已经改进并且在 3.x 系列中变得更好。在 Python 3.6 中,借助 __set_name__ 描述符功能和新的 __init_subclass__ 机制,您甚至不需要使用自定义元类来获得此处的预期结果。

【讨论】:

以上是关于实现特殊的元类。继承类中的非化字段的主要内容,如果未能解决你的问题,请参考以下文章

子类 Django ModelBase(Django 模型的元类)

抽象类 和 接口类

Python 元类中的继承如何工作?

在 python3.x 中显式继承“类型”以实现元类

用于预定义类创建的元类与继承

元类中 __new__ 的行为(也在继承的上下文中)