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

Cor*_*ory 137 django django-models django-queryset

我和一些同事正在讨论这个问题.当你期望只有一个对象时,有没有一种首选的方法来检索Django中的对象?

两个明显的方法是:

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

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

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

第一种方法似乎在行为上更正确,但在控制流中使用异常可能会引入一些开销.第二个更环形,但不会引发异常.

关于哪一个更好的想法?哪个更有效率?

Jam*_*ett 167

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

选项2几乎就是如何get()在Django中实际实现该方法,所以不应该存在"性能"差异(而且你正在考虑它的事实表明你违反了编程的一个基本规则,即试图甚至在编写和分析之前优化代码 - 直到你拥有代码并运行它,你不知道它将如何执行,并且在此之前尝试优化是一条痛苦的道路.


pri*_*stc 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
Run Code Online (Sandbox Code Playgroud)

  • @Thomas我认为这个想法是不讨厌没有这样的方法...... (6认同)

小智 16

1是对的.在Python中,异常具有与返回相同的开销.为了简化证明你可以看看这个.

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

  • 这个测试相当不公平。抛出异常的很大一部分开销是堆栈跟踪的处理。该测试的堆栈长度为 1,比您通常在应用程序中找到的堆栈长度要短得多。 (2认同)

Bas*_*Ben 10

我参加派对有点晚了,但是对于Django 1.6,查询集上有first()方法.

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


返回查询集匹配的第一个对象,如果没有匹配的对象,则返回None.如果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
Run Code Online (Sandbox Code Playgroud)

  • 它不能保证查询中只有一个对象 (3认同)

Kyl*_*tan 8

我不能说任何Django的经验,但选项#1明确告诉系统你要求1个对象,而第二个选项没有.这意味着选项#1可以更轻松地利用缓存或数据库索引,尤其是在您要过滤的属性不能保证唯一的情况下.

另外(再次推测)第二个选项可能必须创建某种结果集合或迭代器对象,因为filter()调用通常可以返回许多行.你用get()绕过了这个.

最后,第一个选项更短,省略了额外的临时变量 - 只是一个微小的差异,但每一点都有帮助.


kru*_*ubo 8

为什么这一切都有效?用1个内置快捷方式替换4行.(这有自己的尝试/除外.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)
Run Code Online (Sandbox Code Playgroud)

  • 那就是`Model.objects.get_or_create()` (2认同)

Sin*_*ion 7

有关异常的更多信息.如果他们没有成长,他们几乎没有任何成本.因此,如果您知道可能会有结果,请使用异常,因为使用条件表达式无论如何都要支付每次检查的成本.另一方面,当它们被提升时,它们比条件表达式花费更多,所以如果你期望没有某个频率的结果(例如,30%的时间,如果内存服务),则条件检查结果要便宜一点.

但这是Django的ORM,可能是数据库的往返,甚至是缓存的结果,可能会主导性能特征,所以在这种情况下支持可读性,因为你期望只有一个结果,使用get().


Jan*_*bel 5

我稍微研究了一下这个问题,发现选项 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
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

通过改用这种方法,我能够大幅减少应用程序执行的查询数量。