Django过滤器与单个对象的获取?
-
06-07-2019 - |
题
我和一些同事正在讨论这个问题。当你期望只有一个对象时,是否有一种首选的方法来检索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。
根据我自己的经验,我可以告诉你,有时候你肯定在数据库中不可能有多个匹配的对象,但是会有两个...(当然除了通过它的主要对象获取对象键)。