若在您数据的子集上需要进行很多操作同时还希望减少代码量及需要生成的查询的复杂性时,可以考虑使用这个基于类的方法来过滤查询。
为模型使用类和静态方法
为管理器所能添加的方法的类型是没有限制的。如前面所示,方法可以返回 QuerySet,也可以返回相关模型类的实例(比如 self.model)。
在有些情况下,您可能希望执行一些与模型相关的操作,但又不能返回实例或 QuerySets。Django 文档指出所有非模型类实例上的方法都应在管理器中,但还有一个可能性就是使用 Python 类和静态方法。
如下所示是一个实用方法的简单示例,这个方法与 Format 类有关,与具体某个实例无关。
# Return the canonical name for a format extension based on some # common values that might be seen "in the wild" def check_extension(extension): if extension == 'text' or extension == 'txt' or extension == '.csv': return 'text' if extension.lower() == 'epub' or extension == 'zip': return 'epub' if 'htm' in extension: return 'html' raise Exception('Did not get known extension') |
上述代码并不接受或返回 Format 类的实例,所以把它作为实例方法并不恰当。也可以把它添加给 FormatManager,但是由于它根本不能访问数据库,所以把它放在那里也不太合适。
一个解决办法就是把它添加给 Format 类并用 @staticmethod 修饰符把它声明为一个静态方法,如下所示。
清单 10. 作为模型类上的静态方法添加一个实用函数
class Format(models.Model): type = models.CharField(choices=( ('Text file', 'text'), ('ePub ebook', 'epub'), ('HTML file', 'html')), max_length=10) @staticmethod def check_extension(extension): if extension == 'text' or extension == 'txt' or extension == '.csv': return 'text' if extension.lower() == 'epub' or extension == 'zip': return 'epub' if 'htm' in extension: return 'html' raise Exception('Did not get known extension') def __unicode__(self): return self.type |
这个方法可被称为 Format.check_extension(extension),它既不需要 Format 实例,也不需要创建一个管理器。
Python 还提供了 @classmethod 修饰符,它能基于类生成方法,并且第一个参数就是类本身。如果想要在不实例化的情况下执行类对象本身上的某种自查(introspection),这一点会很有用。
Django V1.1 中的聚集查询
在 2009 年 4 月发布的 V1.1 中,Django 的 ORM 包括了很多功能强大的查询方法,这些方法所提供的功能以前只有通过原始的 SQL 才可用。对于对 SQL 心存戒心的 Python 开发人员 — 以及任何希望他/她的 Django 应用程序能跨多个数据库引擎可用的人而言,这的确是个福音。
在当今根据需求而不断调整而成的应用程序中,通常不仅需要能依常规的字段,如字母顺序或创建日期,来对项目进行排序,还需要按其他某种动态数据对项目进行排序。例如,在示例应用程序中,您可能需要按受欢迎程度对文档进行排序,也就是基于每个文档的注释的数量进行排列。在 Django V1.1 发布之前,往往需要编写一些定制 SQL 代码,才能实现这个功能,结果,所创建的存储过程不可移植,或 — 最糟的 — 编写的面向对象的查询十分低效。另一种方法就是定义一个 dummy 数据库字段,其中包含用来计数的理想值(例如,注释行的数量)并通过覆盖文档的 save() 方法手动更新它。
Django 聚合排除了所有上述需求。现在仅用一个 QuerySet 方法(annotate())就可以实现对文档按注释的数量进行排序。清单 11 提供了一个示例。
清单 11. 使用聚合对结果按注释的数量进行排序
from django.db.models import Count # Create some sample Documents unpopular = Document.objects.create(name='Unpopular document', format=format_html) popular = Document.objects.create(name='Popular document', format=format_html) # Assign more comments to "popular" than to "unpopular" for i in range(0,10): Comment.objects.create(document=popular) for i in range(0,5): Comment.objects.create(document=unpopular) # If we return results in the order they were created (id order, by default), we get # the "unpopular" document first. In [1]: Document.objects.all() Out[1]: [<Document: Unpopular document>, <Document: Popular document>] # If we instead annotate the result set with the total number of # comments on each Document and then order by that computed value, we # get the "popular" document first. In [2]: Document.objects.annotate(Count('comments')).order_by('-comments__count') Out[2]: [<Document: Popular document>, <Document: Unpopular document>] |
annotate() QuerySet 方法自身并不执行任何聚合。相反,它可以指示 Django 将所传递的表达式的值指定给结果集中的一个伪列。默认情况下,这个列的名称将是所提供的字段名(这里就是 Comment.document.related_name() 的值)。上述代码调用了 django.db.models.Count,这只是聚合库中诸多可用简单数学函数中的一个。(要获得完整清单,参见 参考资料。)
Document.objects.annotate(Count('comments')) 的结果是一个 QuerySet,并向其添加了一个新属性 — comments__count —。如果想覆盖那个默认名称,可以将新的名称做为一个关键字参数传递。
Document.objects.annotate(popularity=Count('comments')) |
现在,这个中间 QuerySet 包含了与每个文件相关联的所有注释的计数值,我们就可以按这个字段进行排序了。由于我们希望把拥有最多注释的文档显示排在第一个,所以我们采用了降序,比如 .order_by('-comments__count')。
使用聚合不仅减少编写代码的量,而且还可以确保这些操作能被快速地完成,因为它们是依靠数据库引擎来完成这些数学计算的。比起先通过 ORM 抽取所有相关数据,然后再手动地对结果集进行计算,使用聚合的处理过程显然更高效。
Django V1.1 中的其他聚合类型
新的聚合库不仅可以返回更复杂的结果集,还可以返回直接从数据库抽取出的非 QuerySet 结果。例如,要获得数据库中所有文档的注释数量的平均值,可以使用下面的代码:
In [1]: from django.db.models import Avg In [2]: Document.objects.aggregate(Avg('comments')) Out[2]: {'comments__avg': 8.0} |
可以将聚合应用到过滤过的或未经过滤的查询,由 annotate 生成的列也可以像普通字段那样被过滤。还可以将聚合方法跨连接应用。例如,可以基于注释的级别(比如在一个 Slashdot 风格的站点中)聚合文档。要获得更多关于聚合的信息,请参见 参考资料。
结束语
对于对象关系型映射器的一种指责是它们抽象掉了太多数据库引擎,以至于用它们编写可伸缩的高效应用程序不太可能。对于某些类型的应用程序 — 拥有数百万的访问量和高度关联的模型的应用程序,这个结论常常是正确的。
绝大多数应用程序从未有过这么大的访问量,也达不到那么复杂的水平。然而,ORM 是被设计用来快速启动项目并帮助开发人员在对 SQL 没有深入了解的情况下就可以开发基于数据库的项目。虽然您的 Web 站点越来越大、越来越受欢迎,如我们在本文第一部分所描述的那样,您仍然需要对其性能进行审核。最终,您可能需要用原始 SQL 或存储过程来替代基于 ORM 的代码。
所幸,像 Django ORM 这样的简单易用的 ORM 的功能在不断发展。Django V1.1 聚合库是一个很大的进步,在提供熟悉的面向对象语法的同时,还提供了高效的查询生成。要想获得更多的灵活性,Python 开发人员还应该关注一下 SQLAlchemy,特别是对于那些不依赖于 Django 的 Python Web 应用程序。(责任编辑:A6)