0%

详解python django面向关系数据库的ORM对象映射系统(1)

django是一套开发成本低、迭代周期快的python web框架,而如mysql等关系数据库则是网站的必备组件,django通过设计一套python对象与数据库表的映射系统ORM,使得开发者不用写一行SQL语句就能实现极其复杂的关系数据库操作,特别是关联多张表的SQL操作。这让开发者的精力可以放在业务的迭代实现中,忽略SQL细节,同时提供了还不错的SQL语句性能。本文主要分析该ORM系统的实现原理及其设计思路,顺带描述python元类这个“黑魔法”。接下来,我们首先描述django model的一般用法,再说明ORM系统的结构,以及为何如此设计。

关系数据库相对于hbase等面向海量数据的列式存储数据库而言,大多为行式存储数据库。所以这里我们主要关注表、行,django的ORM系统中,允许让应用开发者定义一个继承django.db.models.Model(事实上是django.db.models.base.Model)的类对应着表,而该类的实例对应着行的方式操作关系数据库。其中,类中的静态成员对应着列名称,而实例中的同名成员则对应着一行数据中的列。例如: class Article(models.Model): title = models.CharField(verbose_name=’标题’, max_length=255) content = HTMLField(verbose_name=’内容’) 这里的Article代表着表,Article.title是列名。若有实例article = Article(),此时article.title则表示一行中的title列的数据。所以,类和实例都会有同名的静态与对象成员title哦。

ORM框架为每个表对应的类都生成了objects对象(如果你没有显式指定表的Manager的话),而这个objects对象拥有操作表的所有方法,诸如批量查询filter、单次查询get、更新update等。所以当我们执行SQL操作时,比如查询整表,可以如下: articles = Article.objects.all() 当我们查询时,大多会查询到多行数据,比如上面的all方法返回的是整张表的全部行。所以我们需要一个容器,保存着SQL操作返回的全部Article实例,它就是models.QuerySet。QuerySet是一个很强大的类,它与objects对象共用操作表的方法,这些方法支持非常复杂的参数,不只有==、>、<、in、like等操作,还支持含有外键等方式的多表关联查询,这些下一篇文章再细说。

为了方便快速开发复杂的SQL操作,QuerySet的SQL操作方法返回的还是QuerySet对象,这样就可以嵌套叠加着、由多个QuerySet方法组合完成一个SQL操作。例如: Article.objects.filter(title=’xxx’).filter(type=1).distinct() 同时,QuerySet对象还具有“懒执行”的效果,只要没有真的使用查询出的行中数据时,查询就不会被django执行。这意味着我们尽可以写下大量的QuerySet方法,其返回的对象可以被多个条件分支反复使用。关于这一部分的实现也将在下一篇说明。

本文主要讲述ORM的总体框架,以下开始说明其实现方法。

当我们想通过类、对象这套OO系统映射关系数据库时,用类映射表、类成员映射列、实例映射行、实例成员映射行中的列,这是很自然的做法。作为中间件的实现者,最自然的基于OO的想法是实现一个强大的Model基类,其含有操作表的所有方法,由应用开发者继承基类后,自己定义列以及行中的列变量。然而这却是行不通的,因为:

  1. 空表没有一行数据,此时Model类没有实例,但却要有表结构,所以用户不能自己定义self下的行中的列成员;
  2. Model类实例只表示一行,而“一行”是没有办法包含所有SQL操作的;
  3. Model类只能表示“表”这个结构,同样没有办法包含所有SQL操作;
  4. 只有“多行”这个概念可以适配表中的任意数据,也就是QuerySet容器,所以,由Model基类提供所有表操作方法是行不通的。

因此,由QuerySet实现几乎所有SQL操作方法是可行的,且由于QuerySet对象表示的若干行数据,SQL方法就可以被用户轻易的理解为操作这些行数据,也容易实现,而Django也确实是这么干的。那么,当未执行过查询时,QuerySet对象还不存在,这些表方法如何提供给用户呢?通常,我们可以在Model基类中提供一个方法或者成员,返回一个包含QuerySet中方法的对象(QuerySet表示若干行,所以此时不能直接返回QuerySet),而django选择提供一个成员叫objects,它是models.Manage类的实例,而这个Manager类虽然其定义中没有SQL操作方法,但被Django框架悄悄的通过“元类”的方式,将QuerySet中的所有方法都注入到Manager类中了。以上所述的内容如下图所示:

如果查看django源代码会发现上图中的红色类BaseManagerFromQuerySet并不存在,它是由type元类生成的,也就是由它将QuerySet类里的方法注入到Manager类中的,从而让objects对象拥有了操作表的方法。这套系统依赖于python元类才能实现,那么,什么是元类呢?

类是用于生成对象的,大部分编程语言都需要提前把类定义好才能编写基于“类”生成对象的代码。然而,python是个例外:一切皆对象,包括类也是对象,那么生成“类”这个对象的“类”称呼什么呢?元类!python允许开发者使用元类在运行时更改生成“类”的方式。

就像object是所有类的基类,而type是所有元类的基类。任何类都是由type生成的,哪怕我们显式定义的类也会由type默认的生成。所以,我们自然也可以由type隐式得生成类,type生成类的方式如下:

1
cls = type(name, base, attrs) 

name也就是类名,base是基类,而attrs就是属性,所有的成员和方法都在其中。type返回的则是类。而上图中的BaseManagerFromQuerySet类就是这么生成的,如下所示:

1
2
3
4
5
6
def from\_queryset(cls, queryset\_class, class\_name=None): 
if class\_name is None:
class\_name = '%sFrom%s' % (cls.\_\_name\_\_, queryset\_class.\_\_name\_\_)
class\_dict = { '\_queryset\_class': queryset\_class, }
class\_dict.update(cls.\_get\_queryset\_methods(queryset\_class))
return type(class\_name, (cls,), class\_dict)

所以,这里通常queryset_class就是QuerySet类,而cls就是BaseManager类。BaseManager的_get_queryset_methods方法负责把QuerySet中的方法注入到class_dict属性中,进而让BaseManagerFromQuerySet类具备了SQL操作方法。而Manager类就是继承上面构造出的类,如下所示:

1
class Manager(BaseManager.from\_queryset(QuerySet)): pass 

python中的类生成对象时,都是先由__new__方法生成对象,再通过__init__方法初始化对象。由于python并不需要用户管理内存,所以我们定义类时往往只重载__init__方法。元类生成类时也一样,只不过类不需要__init__方法初始化,所以我们通常定义元类时需要重载__new__方法。

1
2
3
class ModelBase(type): """ Metaclass for all models. """ 
def \_\_new\_\_(cls, name, bases, attrs):
super\_new = super(ModelBase, cls).\_\_new\_\_ new\_attrs = {...} new\_class = super\_new(cls, name, bases, new\_attrs)   ...   manager = Manager() manager.auto\_created = True cls.add\_to\_class('objects', manager)   return new\_class

上面的ModelBase方法就是生成所有Model类的方法。同时,objects也是在生成类的时候就自动插入的。这里要插一句:python使用meta元类的规则是首先在当前类中查找是否使用元类,如果没有,再依次去父类中查看是否使用元类,若查找到显式指定的元类,则直接使用该元类创建类,若未找到,则使用默认的type元类生成类。所以,虽然用户描述表的Model类并没有使用元类,但仍然隐式得通过基类django.db.models.base.Model类使用了上面的ModelBase元类。而Model使用元类的方法也不太一样:

1
class Model(six.with\_metaclass(ModelBase)): 

而通常我们可能是这么使用的:

1
class Model(object, metaclass=ModelBase): 

这是因为,six这个库又基于上面的查找元类也会从父类找一遍规则套了一层:

1
2
3
def with\_metaclass(meta, \*bases): class metaclass(meta): 
def \_\_new\_\_(cls, name, this\_bases, d): return meta(name, bases, d)
return type.\_\_new\_\_(metaclass, 'temporary\_class', (), {})

Model是继承自动生成的父类temporary_class,而temporary_class,则使用了元类ModelBase生成。而上文的add_to_class实际调用了setattr方法,它可以向一个python object添加一个属性或者方法,如下 :

1
def setattr(p\_object, name, value) 

这里name就是目标属性的变量名,value是其值。实际上,类成员中代表的是列,而代表行的Model实例是在Model父类的__init__方法中设置的,如下:

1
fields\_iter = iter(opts.fields) for val, field in zip(args, fields\_iter): if val is \_DEFERRED: continue setattr(self, field.attname, val) kwargs.pop(field.name, None)   

还需要注意的是objects其实是由ManagerDescripter作为descripter包装了Manager对象,如下所示:

1
2
3
4
5
6
7
8
9
class ManagerDescriptor(object): 
def \_\_init\_\_(self, manager):
self.manager = manager  
def \_\_get\_\_(self, instance, cls=None):
if instance is not None:
raise AttributeError("Manager isn't accessible via %s instances" % cls.\_\_name\_\_)  
if cls.\_meta.abstract:
raise AttributeError("Manager isn't available; %s is abstract" % ( cls.\_meta.object\_name, ))  
return cls.\_meta.managers\_map\[self.manager.name\]

因为__get__方法的instance其实是调用objects的对象,如果通过类调用,例如Article.objects时,则instance参数为None。所以,这个descripter就是起到只允许非abstract类调用的目的。 以上就是ORM整体架构,下一篇我们再详述QuerySet是如何支持复杂查询的。