实现特殊的元类。继承类中的非化字段
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 'main.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__
机制,您甚至不需要使用自定义元类来获得此处的预期结果。
【讨论】:
以上是关于实现特殊的元类。继承类中的非化字段的主要内容,如果未能解决你的问题,请参考以下文章