【问题标题】:.filter() vs .get() for single object? (Django)Django过滤器与获取单个对象?
【发布时间】:2010-11-04 08:52:06
【问题描述】:

我正在与一些同事就此进行辩论。当您只需要一个对象时,是否有一种首选方法可以在 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

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

有什么想法更可取吗?哪个更有效率?

【问题讨论】:

    标签: django django-models django-queryset


    【解决方案1】:

    get() 提供specifically for this case。使用它。

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

    【讨论】:

    • 一切都是正确的,但也许应该添加更多信息来回答? 1. Python 鼓励 try/except(参见EAFP),这就是为什么QS.get() 很好。 2. 细节很重要:“只期望一个”是否意味着总是 0-1 个对象,或者可能有 2 个以上的对象并且这种情况也应该处理(在这种情况下len(objs) 是一个糟糕的主意)? 3. 不要假设没有基准的开销(我认为在这种情况下try/except 会更快,只要至少有一半的调用返回一些东西)
    • > 即在编写和分析代码之前尝试优化代码 这是一个有趣的评论。我一直认为我应该在实现之前考虑最可选的方式来实现它。那是错的吗?你能详细说明这一点吗?是否有一些资源可以详细解释这一点?
    • 我很惊讶没有人提到 first()。其他建议似乎表明这是针对这种情况的要求。 stackoverflow.com/questions/5123839/…
    • @ParthSharma 我听说过的高效开发公理是“工作、漂亮、快速”。这并不排除在实施之前花点时间进行计划,而是将重点放在使某些东西达到可用状态,然后再花时间进行可能对最终用户或目的无关紧要的优化。该原则至少可以追溯到 1983 年的“计算机系统设计提示”一书中(以许多名称)。 wiki.c2.com/?MakeItWorkMakeItRightMakeItFast
    • 使用get 的唯一问题是它不支持某些查询条件(即检查-平等),在这种情况下filter 带有附加处理多个结果是唯一的选择。
    【解决方案2】:

    您可以安装一个名为 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
    

    【讨论】:

    • 为什么有这样的方法很烦人?我觉得很好!
    • @Thomas 在不知道模块的情况下,我猜它是为通常烦人的 django 东西提供功能。从他们的页面 (pypi.org/project/django-annoying): > 这是一个 django 应用程序,它试图消除 Django 框架中令人讨厌的东西。
    【解决方案3】:

    1 是正确的。在 Python 中,异常的开销与返回的开销相同。如需简化证明,您可以查看this

    2 这就是 Django 在后端所做的。 get 调用 filter 并在未找到项目或找到多个对象时引发异常。

    【讨论】:

    • 那个测试很不公平。抛出异常的很大一部分开销是堆栈跟踪的处理。该测试的堆栈长度为 1,这比您通常在应用程序中发现的要短得多。
    • @Rob Young:你是什么意思?在典型的“请求宽恕而不是许可”方案中,您在哪里看到堆栈跟踪处理?处理时间取决于异常传播的距离,而不是发生的深度(当我们不使用 java 编写并调用 e.printStackTrace() 时)。大多数情况下(如在字典查找中) - 异常在 try 下方抛出。
    【解决方案4】:

    我参加聚会有点晚了,但是在 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
    

    【讨论】:

    • 不保证查询中只有一个对象
    【解决方案5】:

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

    from django.shortcuts import get_object_or_404
    
    obj = get_object_or_404(MyModel, id=1)
    

    【讨论】:

    • 这是理想的行为,但有时,您可能想要创建缺少的对象,或者拉取是可选信息。
    • 这就是Model.objects.get_or_create() 的用途
    【解决方案6】:

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

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

    最后,第一个选项更短,并且省略了额外的临时变量——只有很小的区别,但每一点都有帮助。

    【讨论】:

    • 没有使用 Django 的经验,但仍然可以。无论语言或框架如何,默认情况下明确、简洁和安全都是很好的原则。
    【解决方案7】:

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

    但是这是 Django 的 ORM,并且可能是到数据库的往返,甚至是缓存的结果,很可能会支配性能特征,因此有利于可读性,在这种情况下,由于您期望只有一个结果,所以使用 @ 987654321@.

    【讨论】:

      【解决方案8】:

      我玩过这个问题,发现选项 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]
      

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

      【讨论】:

        【解决方案9】:

        .get()

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

        get() 引发 MultipleObjectsReturned 如果有多个对象 成立。 MultipleObjectsReturned 异常是 模型类。

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

        .filter()

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

        注意

        当你想得到一个唯一的对象时使用 get() 和 filter() 当您想要获取与您的查找参数匹配的所有对象时。

        【讨论】:

          【解决方案10】:

          有趣的问题,但对我来说,选项 #2 带有过早优化的味道。我不确定哪个性能更高,但选项 #1 对我来说确实看起来和感觉更 Pythonic。

          【讨论】:

            【解决方案11】:

            我建议不同的设计。

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

            结果非常棒,例如:

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

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

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

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

            【讨论】:

              【解决方案12】:

              选项 1 更优雅,但一定要使用 try..except。

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

              【讨论】:

                【解决方案13】:

                很抱歉在这个问题上再添加一个,但我使用的是 django 分页器,并且在我的数据管理应用程序中,允许用户选择要查询的内容。有时这是文档的 id,但除此之外,它是返回多个对象的一般查询,即 Queryset。

                如果用户查询id,我可以运行:

                Record.objects.get(pk=id)
                

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

                我需要跑步:

                Record.objects.filter(pk=id)
                

                它返回一个包含一个项目的查询集。然后分页器就可以正常工作了。

                【讨论】:

                • 要使用分页器 - 或任何需要 QuerySet 的功能 - 您的查询必须返回一个 QuerySet。不要在使用 .filter() 和 .get() 之间切换,坚持使用 .filter() 并提供“pk=id”过滤器,正如您已经意识到的那样。这就是这个用例的模式。
                猜你喜欢
                • 2014-05-31
                • 1970-01-01
                • 2014-02-10
                • 2019-12-27
                • 2018-08-05
                • 2021-07-31
                • 2015-03-24
                • 1970-01-01
                相关资源
                最近更新 更多