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='内容')
ORM框架为每个表对应的类都生成了objects对象(如果你没有显式指定表的Manager的话),而这个objects对象拥有操作表的所有方法,诸如批量查询filter、单次查询get、更新update等。所以当我们执行SQL操作时,比如查询整表,可以如下:
articles = Article.objects.all()
为了方便快速开发复杂的SQL操作,QuerySet的SQL操作方法返回的还是QuerySet对象,这样就可以嵌套叠加着、由多个QuerySet方法组合完成一个SQL操作。例如:
Article.objects.filter(title='xxx').filter(type=1).distinct()
本文主要讲述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生成类的方式如下:
cls = type(name, base, attrs)
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)
class Manager(BaseManager.from_queryset(QuerySet)):
pass
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
class Model(six.with_metaclass(ModelBase)):
class Model(object, metaclass=ModelBase):
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', (), {})
def setattr(p_object, name, value)
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对象,如下所示:
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]
以上就是ORM整体架构,下一篇我们再详述QuerySet是如何支持复杂查询的。