我有以下python代码:
models.py
class Person(models.Model):
first_name = models.CharField(max_length=32, null=True, blank=True)
last_name = models.CharField(max_length=64, null=True, blank=True)
order = models.PositiveSmallIntegerField(null=True, blank=True)
我添加了两个人Persons
,名为'人1'和#2;人物2'分别。它们的顺序都是1。
views.py
def get_people():
people = Person.objects.order_by('order')
print(people)
for p in people:
print(p)
if p == people.last():
print ('Last Person')
结果如下:
>>> get_people()
[<Person: Person 1>, <Person: Person 2>]
<Person 1>
u'Last Person'
<Person 2>
进行了一些挖掘,但我发现了这个结果和根本原因。
>>> people = Person.objects.order_by('order')
>>> print(people)
[<Person: Person 1>, <Person: Person 2>]
>>> print(people.first())
<Person 1>
>>> print(people.last())
<Person 1>
>>> people.first() == people.last()
True
>>> people[0]
<Person 1>
>>> people[1]
<Person 2>
我查看了源代码,看来last()方法正在运行reverse()
,并且选择了相同的顺序。由于这两个元素具有相同的订单号1,因此反向方法返回与原始列表完全相同的列表,假设因为当排序反向时,相同的规则适用于平局,具有最低记录ID的元素是第一个,而不是真正扭转已经检索的列表。我不明白为什么他们不只是获取已经检索过的元素列表并从索引中获取最后一个元素。我尝试使用[-1]否定索引来获取它,但是没有实现并引发异常。
那么有人可以解释为什么这样编码吗?如果您的几个元素对所订购的属性共享相同的值,则可能会出现问题。特别是如果在后续调用last()时多次访问查询集。它是出于性能还是其他一些我没有看到的问题?我没有在这个用例中使用last()方法,而只是进行这种比较,而不是:
if p == people[len(people) - 1]:
这很有效。在这种情况下,我知道人们不是空的,所以我们不会得到一个IndexError - 如果它是空的,代码永远不会在循环中执行。一般情况可能是:
l = len(people)
return None if l == 0 else return people[l -1]
或者:
try:
l = len(people)
return people[l - 1]
except IndexError:
return None
请问您对此行为有何见解? Django文档中唯一指出last()方法就像first()但返回查询集的最后一个元素。在这种情况下,它没有按照描述起作用。这种行为使我感到困惑。我认为它只需要从当前列表中获取最后一个元素,而不是创建一个新的反转列表并获得它的第一个元素。
提前致谢...
答案 0 :(得分:2)
如果有人对这种边缘情况有任何想法,原因很可能是一致性和性能的结合。
首先,您通常无法评估整个查询集只是为了获得最后一个元素而不会造成巨大的性能损失。 Person.objects.order_by('order').last()
应该得到一行,而不是整个表 - 它可能包含数百万行。因此,在未评估的查询集的情况下,您需要在SQL中反转顺序并获取顶部元素。这总是会遇到你描述的问题。
只有在评估查询集时才可以获取缓存中的最后一个元素,但这意味着您会得到不一致的结果。请使用以下代码:
people = Person.objects.order_by('order')
p1 = people.last()
bool(people)
p2 = people.last()
在您的示例中,p1
将为<Person 1>
。但是,如果在评估查询集时采用缓存的最后一个元素,p2
将突然变为<Person 2>
,因为缓存已填满。这种自我不一致使得开发人员的工作非常困难。
虽然这可能不是非常直观,但它是在实际数据库查询中转换.last()
方法并获得可接受的性能和自洽结果的最佳方法。无序或部分有序的结果集具有未定义的顺序(甚至可能在查询之间任意更改)这一事实是SQL的一个易于理解的方面,因此总体而言这是least astonishment的路径。
答案 1 :(得分:1)
我相信代码的问题在于你按升序排序,让Django知道如何处理两者之间的平局。 SQL中与您在get_people()
方法中编写的内容相同,如下所示:
SELECT * FROM Person ORDER BY order ASC
因此,如果有两个人都具有相同的“订单”值,您的结果将永远无法正确返回。相反,您需要一个看起来更类似于此的查询:
SELECT * FROM Person ORDER BY order, last_name, first_name
(假设您希望在订购后首先按姓氏排序。
我使用我设计过的应用程序遇到了这样的问题,解决方案非常简单。尝试使用Django API找出潜在的“问题”(尽管实际上它只是像表格的设计一样聪明),而不是打败你的头脑,你可以使用这样的东西:
views.py
def get_people():
people = Person.objects.order_by('order', 'last_name', 'first_name')
print(people)
for p in people:
print(p)
if p == people.last():
print ('Last Person')
请注意,在我们通过Django“创建查询”的行中,我包含了多个列。这将解决你的领带问题,所以如果两个人有相同的订单,它将按姓氏排序。