Django获取随机对象
我正在尝试从模型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 个回答
还有另一种方法:
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
怎么样来计算最大的主键并获取随机的主键呢?
这本书《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循环会让处理速度变慢。
第二段代码是正确的,但可能会比较慢,因为在SQL中,它会生成一个 ORDER BY RANDOM()
的命令,这个命令会把所有结果打乱顺序,然后再根据这个顺序取出一定数量的结果。
第一段代码虽然也要处理所有结果,但假设你的随机索引接近最后一个可能的索引,那就会有问题。
更好的方法是从数据库中随机选择一个ID,这样查找速度会很快,因为这是通过主键来查找的。我们不能假设在 1
到 MAX(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]
在以上内容的基础上进行改进:
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对象,然后随机选择一个主键,最后只加载我们选择的那个对象。
我刚刚在看这个。那行代码:
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而不会出错。