Django获取随机对象

44 投票
9 回答
40764 浏览
提问于 2025-04-18 01:02

我正在尝试从模型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]的理解有误?

9 个回答

1

还有另一种方法:

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

即使在主键(pks)之间有较大的间隔,这种方法也能正常工作,比如说你想在随机选择剩下的对象之前,先对查询结果进行过滤。

编辑:修正了randint的调用(感谢@Quique)。停止参数是包含在内的。

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

7

怎么样来计算最大的主键并获取随机的主键呢?

这本书《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

详细信息请见 源链接

看到这些结果后,我开始使用以下代码片段:

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循环会让处理速度变慢。

12

第二段代码是正确的,但可能会比较慢,因为在SQL中,它会生成一个 ORDER BY RANDOM() 的命令,这个命令会把所有结果打乱顺序,然后再根据这个顺序取出一定数量的结果。

第一段代码虽然也要处理所有结果,但假设你的随机索引接近最后一个可能的索引,那就会有问题。

更好的方法是从数据库中随机选择一个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]
36

在以上内容的基础上进行改进:

from random import choice

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

我们首先获取一个可能的主键列表,而不加载任何Django对象,然后随机选择一个主键,最后只加载我们选择的那个对象。

83

我刚刚在看这个。那行代码:

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

据说导致了很多服务器崩溃。

不幸的是,Erwan的代码在访问不连续的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而不会出错。

撰写回答