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

时间:2017-05-26 23:34:28

标签: python python-2.7 inheritance field metaclass

我有一个任务:

  

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

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

其中StringField - 表示此字段的某个对象   文本域。所以必须有一个类,其构造函数接收   命名参数" name"并将其存储在相应的属性中(使用   类型检查和演员)

     

所以你可以输入这样的东西:

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

该类应该允许继承,并且应该验证类型   方式,你不能写一个数字到文本字段。

这是我的实现,但是继承类的问题是"" 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()

此代码打印

  

您好
  世界你好

但它应该(通过任务)打印

  

您好
  世界无

问题是:

  

为什么类型(st)返回"键入' type' &#34 ;? (我认为这不是一个班级)   为什么类型(sc)返回" class' main .ModelCreator' "

     

如何确定" name"的价值。 "学生"中的字段class,所以它只会保存在" st" (因为它现在以某种方式包含在" sc")?

1 个答案:

答案 0 :(得分:2)

这段代码有点令人费解 - 但它没有比你告诉它做的更好。

它做了什么,除了需要的描述符(即:包含__get____set__方法的类)和通常的元类机制之外,还将__new__方法插入到类中在几个方面是错误的。

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

无论如何 - 如果你想要使用这个元类的类本身就是“类工厂”,那么只调用new是有意义的 - 这不仅会返回声明字段的全新类,而且还会返回发送的默认值。调用类型是您看到type返回type(st)的原因 - 这是您的第一个问题。

然后,它仍然是错误的:在每个瞬间调用的type类方法将默认属性设置为descritor(即“field”) - 并且该默认值将应用于每个其他实例化同一个类 - 或从其继承的其他类。您应该在调用new类时设置默认值(如果有),并在将成为类StringField的方法上设置实例的值。

如果您首先调用超类__new__来获取实际实例,然后循环传入的关键字参数,并使用__new__作为设置属性的机制,则可以执行此操作。使用setattr将确保正确调用StringField setattr方法。

所以,这段代码中有许多奇怪的东西,但尝试修复它的方法是重新编写你的元类__set__或多或少:

__new__

那就是说,你现在不应该浪费你的时间来研究Python 2.7中的这些高级机制(2017年) - Python 2最后一个版本是在2010年,它将在2020年停止维护 - 这些机制已经改进并且在3.x系列中得到了更好的表现。在Python 3.6中,使用 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) 描述符功能和新的__set_name__机制,您甚至不需要使用自定义元类来获得此处的预期结果。