我和一些同事正在讨论这个问题。当你期望只有一个对象时,是否有一种首选的方法来检索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

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

有关哪一项更可取的任何想法?哪个更有效率?

有帮助吗?

解决方案

get()是专门针对这种情况提供的。使用它。

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

其他提示

您可以安装名为 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是正确的。在Python中,异常具有与返回相同的开销。有关简化证明,您可以查看

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

我参加派对有点晚了,但是对于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

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

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

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

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

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

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

但这是Django的ORM,并且可能是数据库的往返,甚至是缓存的结果,可能会主导性能特征,因此在这种情况下,由于您只需要一个结果,因此支持可读性,使用<代码>得到()

我已经解决了这个问题并发现选项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]

通过切换到这种方法,我能够大大减少应用程序执行的查询次数。

有趣的问题,但对于我来说,选项#2过早优化。我不确定哪个性能更高,但选项#1肯定看起来和我感觉更加pythonic。

我建议采用不同的设计。

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

结果非常棒,例如:

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

此处,filter返回空查询集或带有单个项的查询集。您的自定义查询集功能也是可链接和可重用的。如果你想为你的所有条目执行它: MyModel.objects.all()。yourFunction()

它们也非常适合用作管理界面中的操作:

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top