db.Property机制解析
2010 11 1 01:32 PM 3005次查看
分类:Google App Engine 标签:Google App Engine, Python
首先,所有的属性都是db.Property或其子类的实例,而这个类定义了__get__和__set__方法,这也就意味着Property实际上是个data descriptor。
所以考虑这样一个Model:
class A(db.Model):
name = db.StringProperty(required=True)
a = A(name='123')
当构造a的时候,实际上并不是把a.__dict__['name']设为'123',而是调用了db.StringProperty.__set__(self, a, 'name')。而__set__干了这件事:
def __set__(self, model_instance, value):
value = self.validate(value)
setattr(model_instance, self._attr_name(), value)
很显然,它做了个检查后,把属性名加了个'_',然后设置到model_instance上了。于是检查一下a._name和a.__dict__['_name'],会发现值为'123'。因此这也说明,不要随便在model_instance里写'_'开头的属性,要注意别与Property名相冲突。顺便看看validate做了什么:
def validate(self, value):
if self.empty(value):
if self.required:
raise BadValueError('Property %s is required' % self.name)
else:
if self.choices:
match = False
for choice in self.choices:
if choice == value:
match = True
if not match:
raise BadValueError('Property %s is %r; must be one of %r' %
(self.name, value, self.choices))
if self.validator is not None:
self.validator(value)
return value
由于我这个例子很简单,只设置了required=True,因此当self.empty(value)为真时,就会抛出BadValueError('Property %s is required' % self.name)。同时也能注意到,它的返回值才是真正的value,因此把str转成unicode或db.Text就能在这里自动完成了。
而当访问a.name时,获取的也不是a.__dict__['name'],而是调用db.StringProperty.__get__(self, a, A)。
不用说也知道,它只是返回a._name而已:
def __get__(self, model_instance, model_class):
if model_instance is None:
return self
try:
return getattr(model_instance, self._attr_name())
except AttributeError:
return None
至此,Property可以检查属性值的秘密就揭开了,可Google为什么要用这么复杂的descriptor来实现这个机制呢?熟悉Java或C#的,一定知道getter和setter这2个玩意,为了在设置值时做些检查,不得不实现这2个接口。Python当然也有相应的property函数可以实现这个目的,但是这样一来,我们的模型定义就会变成:
class A(object):
def _get_name(self):
return self._name
def _set_name(self, name):
self._name = name
name = property(_get_name, _set_name)
我这里还没做属性检查什么的,但是代码量就已经远超过Google的实现了。因此,说白了Google只是为了开发者的方便,而让实现变得复杂的。同时这也获得了一个好处:可以重用__get__和__set__的代码,不需要每定义一个属性,就去写一个validator。当然,它的秘密还不止这一点。看db.Model的定义,你会发现这个函数:
@classmethod
def properties(cls):
return dict(cls._properties)
可是我们在定义A类的时候,明明没有去手动维护A._properties,为什么也能获取到它的属性呢?其实在db.Model定义的第一行就揭示了这个秘密:
class Model(object):
__metaclass__ = PropertiedClass
因此在A定义完后,会自动引入它的metaclass,而这个PropertiedClass主要是做了这样一个操作:_initialize_properties(cls, name, bases, dct)。至于_initialize_properties的代码,由于太长,我就不贴出来, 主要就是维护_properties和_unindexed_properties。
Google自然也不会是为了好玩而这样引入一个metaclass,不然_initialize_properties的代码不可能这么冗长。仔细想想就会发现,继承一个Model的时候,如果子类定义了和父类同名的属性,就会将其覆盖了。而如果2者的定义不同,初始化父类时可能就报错了。因此_initialize_properties不得不检查这种情况。
此外,前面的__set__和__get__都去取了self._attr_name(),而它又会去取self.name,可是我们在初始化Property时并没有显式地对其赋值,那么self.name是怎么来的呢?
其实_initialize_properties还对所有属性调用了attr.__property_config__(model_class, attr_name),这样就把self.name设定上去了。
于是,Property机制就解析到这里了,有兴趣的还可以自行研究一下ReferenceProperty、_ReverseReferenceProperty、ComputedProperty和Expando。其实本文主要就是展示一下descriptor和metaclass的用法,Python提供了那么多好玩的东西,不会用就太可惜了。
0条评论 你不来一发么↓