Django过滤器与获取单个对象?


147

我正在与一些同事就此进行辩论。当您只期望一个对象时,是否有一种在Django中检索对象的首选方法?

两种明显的方法是:

try:
    obj = MyModel.objects.get(id=1)
except MyModel.DoesNotExist:
    # We have no object! Do something...
    pass

和:

objs = MyModel.objects.filter(id=1)

if len(objs) == 1:
    obj = objs[0]
else:
    # We have no object! Do something...
    pass

第一种方法在行为上似乎更正确,但是在控制流中使用异常,这可能会带来一些开销。第二个是回旋处,但永远不会引发例外。

有什么想法是可取的?哪个更有效?

Answers:


177

get()专门为这种情况提供的。用它。

选项2几乎完全是该get()方法在Django中的实际实现方式,因此应该没有“性能”差异(而且您在考虑这一事实表明您在违反编程的基本规则之一,即试图在甚至没有编写和描述代码之前就对代码进行优化-直到有了代码并可以运行它,您才知道它的性能,而在此之前尝试进行优化是一条痛苦的路。


一切正确,但也许应该添加更多信息来回答?1. Python鼓励使用try / except(请参阅EAFP),这QS.get()就是很好的原因 。2.细节很重要:“只期待一个”总是意味着0-1个对象,还是可能有2个以上的对象,这种情况也应该处理(在这种情况下,这len(objs)是一个糟糕的主意)?3.在没有基准的情况下,不要假设任何开销(我认为在这种情况下try/except,只要至少有一半的电话返回某些内容,它将更快)
强加于

>即在尝试编写代码之前就优化代码。这是一个有趣的说法。我一直认为我应该在实现某些东西之前先考虑最可选的方式。错了吗 您能详细说明这一点吗?有一些资源可以对此进行详细说明吗?
沙玛·夏尔(Parth Sharma)

我很惊讶没有人提到first()。其他建议似乎表明这是针对这种情况的要求。stackoverflow.com/questions/5123839/…–
NeilG

29

您可以安装一个名为django-annoying的模块,然后执行以下操作:

from annoying.functions import get_object_or_None

obj = get_object_or_None(MyModel, id=1)

if not obj:
    #omg the object was not found do some error stuff

1
为什么使用这种方法会很烦人?对我来说看起来不错!
托马斯

17

1是正确的。在Python中,异常与返回的开销相等。对于简化的证明,你可以看看这个

2这就是Django在后端所做的事情。如果未找到任何项目或找到多个对象,则get调用filter并引发异常。


1
那测试是相当不公平的。引发异常的大部分开销是堆栈跟踪的处理。该测试的堆栈长度为1,比您通常在应用程序中发现的堆栈长度低得多。
罗布·杨

@罗伯·杨:你是什么意思?在典型的“请求宽恕而不是允许”方案中,您在哪里看到堆栈跟踪处理?处理时间取决于异常运行的距离,而不是异常发生的深度(当我们不使用Java编写并调用e.printStackTrace()时)。而且最常见的情况是(例如在字典查找中)-异常会抛出到try
Tomasz Gandor

12

我参加聚会有点晚了,但是在Django 1.6中,有一个关于查询集的first()方法。

https://docs.djangoproject.com/zh-CN/dev/ref/models/querysets/#django.db.models.query.QuerySet.first


返回与查询集匹配的第一个对象,如果没有匹配的对象,则返回None。如果QuerySet未定义排序,则主键自动对queryset进行排序。

例:

p = Article.objects.order_by('title', 'pub_date').first()
Note that first() is a convenience method, the following code sample is equivalent to the above example:

try:
    p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
    p = None

它不能保证查询中只有一个对象
py_dude

8

我对Django的经验一无所知,但是选项1清楚地告诉系统您要的是1个对象,而第二个选项则没有。这意味着选项1可以更轻松地利用缓存或数据库索引,尤其是在不能保证您要筛选的属性唯一的情况下。

同样(再次推测),第二个选项可能必须创建某种类型的结果集合或迭代器对象,因为filter()调用通常可以返回许多行。您可以使用get()绕过它。

最后,第一个选项既较短,又省略了额外的临时变量-只有很小的差别,但有一点帮助。


没有使用Django的经验,但仍然会发现。无论语言或框架如何,默认情况下明确,简洁和安全都是好原则。
nevelis

8

为什么所有这些工作?用1个内置快捷方式替换4行。(这会自己尝试/除外。)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

1
当这是所需的行为时,这很好,但是有时您可能想要创建丢失的对象,或者拉取是可选信息。
SingleNegationElimination

2
那是Model.objects.get_or_create()为了什么
游艇司机

7

有关异常的更多信息。如果不提高它们,它们几乎不会花费任何成本。因此,如果您知道可能会有结果,请使用异常,因为使用条件表达式无论如何都将付出每次检查的费用。另一方面,它们在被提高时的花费比条件表达式要高一些,因此,如果您期望在某个频率上没有结果(例如,如果有内存,则有30%的时间),那么条件检查就会变成事实便宜一点。

但这是Django的ORM,可能是数据库的往返访问,甚至是缓存的结果,可能会主导性能特征,因此,在这种情况下,最好使用可读性,因为您希望得到一个结果,请使用use get()


4

我已经解决了这个问题,发现选项2执行了两个SQL查询,对于这样一个简单的任务来说,这是多余的。看到我的注释:

objs = MyModel.objects.filter(id=1) # This does not execute any SQL
if len(objs) == 1: # This executes SELECT COUNT(*) FROM XXX WHERE filter
    obj = objs[0]  # This executes SELECT x, y, z, .. FROM XXX WHERE filter
else: 
    # we have no object!  do something
    pass

执行单个查询的等效版本为:

items = [item for item in MyModel.objects.filter(id=1)] # executes SELECT x, y, z FROM XXX WHERE filter
count = len(items) # Does not execute any query, items is a standard list.
if count == 0:
   return None
return items[0]

通过切换到这种方法,我可以大幅减少我的应用程序执行的查询数量。


1

有趣的问题,但对我来说,选项2可能过早优化。我不确定哪个性能更好,但是选项#1的外观和感觉对我来说更加Python化。


1

我建议使用其他设计。

如果要对可能的结果执行功能,则可以从QuerySet派生,如下所示: http //djangosnippets.org/snippets/734/

结果非常棒,例如,您可以:

MyModel.objects.filter(id=1).yourFunction()

在此,过滤器返回一个空的查询集或具有单个项目的查询集。您的自定义查询集函数也是可链接和可重用的。如果要对所有条目执行此操作:MyModel.objects.all().yourFunction()

它们也是在管理界面中用作操作的理想选择:

def yourAction(self, request, queryset):
    queryset.yourFunction()

0

选项1更优雅,但请务必使用try..except。

根据我自己的经验,我可以告诉您,有时您确定数据库中不可能有多个匹配的对象,但是会有两个...(当然,通过主键获取对象除外)。


0

抱歉,在这个问题上又增加了一个问题,但是我使用的是django分页器,并且在我的数据管理应用程序中,允许用户选择要查询的内容。有时,这是文档的ID,但在其他情况下,这是返回多个对象(即Queryset)的常规查询。

如果用户查询ID,则可以运行:

Record.objects.get(pk=id)

这将在Django的分页器中引发错误,因为它是记录而不是记录的查询集。

我需要运行:

Record.objects.filter(pk=id)

它返回其中包含一项的Queryset。然后分页器工作正常。


要使用分页器-或期望使用QuerySet的任何功能-您的查询必须返回QuerySet。正如您已经意识到的那样,不要在使用.filter()和.get()之间切换,而是坚持使用.filter()并提供“ pk = id”过滤器。这就是该用例的模式。
Cornel Masson

0

。得到()

返回与给定查找参数匹配的对象,该参数应采用“字段查找”中所述的格式。

如果找到多个对象,则get()会引发MultipleObjectsReturned。MultipleObjectsReturned异常是模型类的属性。

如果找不到给定参数的对象,则get()会引发DidNotExist异常。此异常也是模型类的属性。

。过滤()

返回一个新的QuerySet,其中包含与给定查找参数匹配的对象。

注意

如果要获取单个唯一对象,请使用get();如果要获取与查找参数匹配的所有对象,请使用filter()。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.