如何做类似 Django 模型的元类技巧
How to do Django-models-like metaclass trick
我正在为我的研究项目使 Django 像 ORM 一样,因为我们不允许使用现有的 ORM(如果你想使用一个,你必须自己编写代码)并且只是为了教育自己,我认为像 Django 中的同类 ORM 会很好。
在 ORM 中,我不想使用与在 Django 中实现的样式相同的样式定义模型。即
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Django 使用 metaclasses,在我的项目中我也在使用,但我对 metaclasses 构造 class 的事实有疑问es 不是实例,因此所有属性都是 class 属性并在所有实例之间共享。
这是我尝试过的通用示例,但由于我之前所说的,它不起作用:
def getmethod(attrname):
def _getmethod(self):
return getattr(self, "__"+attrname).get()
return _getmethod
def setmethod(attrname):
def _setmethod(self, value):
return getattr(self, "__"+attrname).set(value)
return _setmethod
class Metaclass(type):
def __new__(cls, name, based, attrs):
ndict = {}
for attr in attrs:
if isinstance(attrs[attr], Field):
ndict['__'+attr] = attrs[attr]
ndict[attr] = property(getmethod(attr), setmethod(attr))
return super(Metaclass, cls).__new__(cls, name, based, ndict)
class Field:
def __init__(self):
self.value = 0;
def set(self, value):
self.value = value
def get(self):
return self.value
class Mainclass:
__metaclass__ = Metaclass
class Childclass(Mainclass):
attr1 = Field()
attr2 = Field()
a = Childclass()
print "a, should be 0:", a.attr1
a.attr1 = "test"
print "a, should be test:", a.attr1
b = Childclass()
print "b, should be 0:", b.attr1
我试图从 Djangos 源代码中查找,但它太复杂了,我无法理解,而且 "magic" 似乎隐藏在某处。
问题很简单,Django 如何在非常简单的示例中做到这一点?
答案真的很简单,一旦你检查了正确的代码。 Django 使用的 metaclass 将所有字段添加到 <model>._meta.fields
(好吧,有点),但是字段属性从实际的 class 中移除。唯一的例外是 RelatedField
subclass,在这种情况下会添加一个对象描述符(类似于 属性 - 事实上,属性 是一个对象描述符,仅使用本机实现)。
然后,在模型的__init__
方法中,代码遍历所有字段,并在*args
或**kwargs
中设置提供的值,或者设置默认值在实例上。
在您的示例中,这意味着 class Person
永远不会具有名为 first_name
和 last_name
的属性,但是这两个字段都存储在 Person._meta.fields
.但是,Person
的实例将始终具有名为 first_name
和 last_name
的属性,即使它们未作为参数提供。
我正在为我的研究项目使 Django 像 ORM 一样,因为我们不允许使用现有的 ORM(如果你想使用一个,你必须自己编写代码)并且只是为了教育自己,我认为像 Django 中的同类 ORM 会很好。
在 ORM 中,我不想使用与在 Django 中实现的样式相同的样式定义模型。即
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Django 使用 metaclasses,在我的项目中我也在使用,但我对 metaclasses 构造 class 的事实有疑问es 不是实例,因此所有属性都是 class 属性并在所有实例之间共享。
这是我尝试过的通用示例,但由于我之前所说的,它不起作用:
def getmethod(attrname):
def _getmethod(self):
return getattr(self, "__"+attrname).get()
return _getmethod
def setmethod(attrname):
def _setmethod(self, value):
return getattr(self, "__"+attrname).set(value)
return _setmethod
class Metaclass(type):
def __new__(cls, name, based, attrs):
ndict = {}
for attr in attrs:
if isinstance(attrs[attr], Field):
ndict['__'+attr] = attrs[attr]
ndict[attr] = property(getmethod(attr), setmethod(attr))
return super(Metaclass, cls).__new__(cls, name, based, ndict)
class Field:
def __init__(self):
self.value = 0;
def set(self, value):
self.value = value
def get(self):
return self.value
class Mainclass:
__metaclass__ = Metaclass
class Childclass(Mainclass):
attr1 = Field()
attr2 = Field()
a = Childclass()
print "a, should be 0:", a.attr1
a.attr1 = "test"
print "a, should be test:", a.attr1
b = Childclass()
print "b, should be 0:", b.attr1
我试图从 Djangos 源代码中查找,但它太复杂了,我无法理解,而且 "magic" 似乎隐藏在某处。
问题很简单,Django 如何在非常简单的示例中做到这一点?
答案真的很简单,一旦你检查了正确的代码。 Django 使用的 metaclass 将所有字段添加到 <model>._meta.fields
(好吧,有点),但是字段属性从实际的 class 中移除。唯一的例外是 RelatedField
subclass,在这种情况下会添加一个对象描述符(类似于 属性 - 事实上,属性 是一个对象描述符,仅使用本机实现)。
然后,在模型的__init__
方法中,代码遍历所有字段,并在*args
或**kwargs
中设置提供的值,或者设置默认值在实例上。
在您的示例中,这意味着 class Person
永远不会具有名为 first_name
和 last_name
的属性,但是这两个字段都存储在 Person._meta.fields
.但是,Person
的实例将始终具有名为 first_name
和 last_name
的属性,即使它们未作为参数提供。