【问题标题】:Django get a random objectDjango 得到一个随机对象
【发布时间】:2014-05-14 01:06:38
【问题描述】:

我正在尝试从模型 A 中获取随机对象

目前,这段代码运行良好:

random_idx = random.randint(0, A.objects.count() - 1)
random_object = A.objects.all()[random_idx]

但我觉得这段代码更好:

random_object = A.objects.order_by('?')[0]

哪个是最好的?使用第一个代码删除对象可能出现问题?因为,例如,我可以有 10 个对象,但编号为 10 的对象不再存在?我是否误解了 A.objects.all()[random_idx] 中的某些内容?

【问题讨论】:

  • 为什么要进行 2 个查询(一个用于计数,一个用于实际选择)而不是 1 个?
  • 我认为第二个可能更好,但第一个不受您描述的问题的影响,因为它正在索引您已经绑定的列表,而不是通过数据库 ID 选择。另外,为什么不random.choice(A.objects.all())
  • @Two-BitAlchemist blergh,这是最糟糕的:从数据库中获取所有行以便只返回一个。
  • @DanielRoseman 它的可读性也很强,如果在其他地方使用A.objects.all(),则按顺序排列(与解决方案 2 不同),并简明扼要地说明了另一个潜在的用例。我没有看到任何关于性能的问题,只是什么会起作用,对于少数对象,可读性更重要。

标签: python django object random


【解决方案1】:

刚刚在看这个。行:

random_object = A.objects.order_by('?')[0]

据报道已经关闭了许多服务器。

不幸的是,Erwans 代码在访问非顺序 ID 时导致错误。

还有另一种简单的方法:

import random

items = list(Product.objects.all())

# change 3 to how many random items you want
random_items = random.sample(items, 3)
# if you want only a single random item
random_item = random.choice(items)

这样做的好处是它可以无错误地处理非顺序 ID。

【讨论】:

  • 查看random模块的文档,random.sample(items, 1)[0]可以通过使用random.choice(items)来避免。见random.choice
  • 如果要从random.choice(items)获取对象,请使用items = list(Product.objects.all())
【解决方案2】:

第二段代码是正确的,但速度可能会慢一些,因为在 SQL 中会生成一个 ORDER BY RANDOM() 子句,该子句会打乱整个结果集,然后在此基础上采用 LIMIT

第一段代码仍然需要评估整个结果集。例如,如果您的 random_idx 接近最后一个可能的索引怎么办?

更好的方法是从您的数据库中选择一个随机 ID,然后选择它(这是一个主键查找,因此速度很快)。我们不能假设我们在1MAX(id) 之间的每个id 都可用,以防您删除了某些内容。所以以下是一个效果很好的近似值:

import random

# grab the max id in the database
max_id = A.objects.order_by('-id')[0].id

# grab a random possible id. we don't know if this id does exist in the database, though
random_id = random.randint(1, max_id + 1)

# return an object with that id, or the first object with an id greater than that one
# this is a fast lookup, because your primary key probably has a RANGE index.
random_object = A.objects.filter(id__gte=random_id)[0]

【讨论】:

  • 第一个代码不会评估整个列表。 Django 查询集中的切片被翻译成 SQL 中的 LIMIT/OFFSET 调用。
  • 我的意思是:SQL 中的 LIMIT/OFFSET 非常慢,因为它几乎要评估整个列表。
  • 您应该将get 替换为filter。现在您收到以下错误:TypeError: 'A' object does not support indexing
  • 我会将所有“id”替换为“pk”。欲了解更多信息,请查看stackoverflow.com/questions/2165865/django-queries-id-vs-pk
  • 如果 PK 中有太多间隙,例如在不断重新导入的表中,这将不起作用。
【解决方案3】:

在以上所有方面都有所改进:

from random import choice

pks = A.objects.values_list('pk', flat=True)
random_pk = choice(pks)
random_obj = A.objects.get(pk=random_pk)

【讨论】:

    【解决方案4】:

    如何计算最大主键并获得随机pk?

    Django ORM Cookbook》一书比较了以下函数的执行时间,以从给定模型中获取随机对象。

    from django.db.models import Max
    from myapp.models import Category
    
    def get_random():
        return Category.objects.order_by("?").first()
    
    def get_random3():
        max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
        while True:
            pk = random.randint(1, max_id)
            category = Category.objects.filter(pk=pk).first()
            if category:
                return category
    

    对一百万个数据库条目进行了测试:

    In [14]: timeit.timeit(get_random3, number=100)
    Out[14]: 0.20055226399563253
    
    In [15]: timeit.timeit(get_random, number=100)
    Out[15]: 56.92513192095794
    

    source

    看到这些结果后,我开始使用以下 sn-p:

    from django.db.models import Max
    import random
    
    def get_random_obj_from_queryset(queryset):
        max_pk = queryset.aggregate(max_pk=Max("pk"))['max_pk']
        while True:
            obj = queryset.filter(pk=random.randint(1, max_pk)).first()
            if obj:
                return obj
    

    到目前为止,只要有一个 id,它就可以完成这项工作。 请注意,如果您将模型 id 替换为 uuid 或其他内容,get_random3 (get_random_obj_from_queryset) 函数将不起作用。此外,如果删除了太多实例,while 循环会减慢进程。

    【讨论】:

      【解决方案5】:

      另一种方式:

      pks = A.objects.values_list('pk', flat=True)
      random_idx = randint(0, len(pks)-1)
      random_obj = A.objects.get(pk=pks[random_idx])
      

      即使 pk 中存在较大间隙也可以工作,例如,如果您想在随机选择剩余对象之一之前过滤查询集。

      编辑:固定调用 randint(感谢@Quique)。停止 arg 包含在内。

      https://docs.python.org/3/library/random.html#random.randint

      【讨论】:

      • 应该是:random_idx = randint(0, len(pks) - 1)
      • 其实应该是random_pk = choice(pks)
      【解决方案6】:

      我正在与 Django 2.1.7、PostgreSQL 10 分享我的最新测试结果。

      students = Student.objects.all()
      for i in range(500):
          student = random.choice(students)
          print(student)
      
      # 0.021996498107910156 seconds
      
      for i in range(500):
          student = Student.objects.order_by('?')[0]
          print(student)
      
      # 0.41299867630004883 seconds
      

      使用 random.choice() 进行随机获取似乎快了大约 2 倍。

      【讨论】:

        【解决方案7】:

        您可以使用“随机”模块中的“选择”

        from .models import MyModel
        from random import choice    
        
        MyRandomChoice = choice(MyModel.objects.all())
        

        【讨论】:

        • 虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!
        猜你喜欢
        • 2013-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-28
        • 2015-11-10
        • 2021-10-23
        相关资源
        最近更新 更多