gpt4 book ai didi

python - 为什么/如何迭代列表并每次调用 'pass' 修复此功能?

转载 作者:太空宇宙 更新时间:2023-11-03 15:21:01 25 4
gpt4 key购买 nike

我写了下面的函数:

def auto_update_ratings(amounts, assessment_entries_qs, lowest_rating=-1):
start = 0
rating = lowest_rating
ids = assessment_entries_qs.values_list('id', flat=True)

for i in ids: # I have absolutely no idea why this seems to be required:
pass # without this loop, the last AssessmentEntries fail to update
# in the following for loop.

for amount in amounts:
end_mark = start + amount
entries = ids[start:end_mark]
a = assessment_entries_qs.filter(id__in=entries).update(rating=rating)
start = end_mark
rating += 1

它做了它应该做的事情(即更新 assessment_entries_qs 中的相关条目数,每个评分(从 lowest_rating 开始)如 amounts 中指定)。这是一个简单的例子:

>>> assessment_entries = AssessmentEntry.objects.all()
>>> print [ae.rating for ae in assessment_entries]
[None, None, None, None, None, None, None, None, None, None]
>>>
>>> auto_update_ratings((2,4,3,1), assessment_entries, 1)
>>> print [ae.rating for ae in assessment_entries]
[1, 1, 2, 2, 2, 2, 3, 3, 3, 4]

但是,如果我在遍历 amounts 之前不遍历 ids,该函数只会更新查询集的一个子集:使用我当前的测试数据(大约 250 AssessmentEntries 在查询集中),它总是导致恰好 84 个 AssessmentEntries 没有被更新。

有趣的是,始终是第二个 for 循环的最后一次迭代不会导致任何更新(尽管该迭代中的其余代码确实正确执行),以及前一次迭代的一部分。查询集在传递给此函数之前是 ordered_by('?') 的,如果我简单地添加前面的“空”for 循环,就可以达到预期的结果,所以它似乎不是我的数据的问题)。

一些更多的细节,以防它们被证明是相关的:

  • AssessmentEntry.rating 是一个标准的IntegerField(null=True,blank=True)
  • 我使用这个函数纯粹是为了测试目的,所以我只是从 iPython 中执行它。
  • 测试数据库是SQLite。

问题:有人可以解释为什么我似乎需要遍历 ids,尽管实际上没有以任何方式接触数据,以及为什么不这样做的功能仍然(某种程度上)正确执行,但总是无法更新查询集中的最后几项,尽管显然仍在迭代它们?

最佳答案

QuerySet 和 QuerySet 切片是惰性求值的。迭代 ids 执行查询并使 ids 表现得像一个静态列表而不是 QuerySet。因此,当您遍历 ids 时,它会导致 entries 稍后成为一组固定值;但是如果您遍历 ids,那么 entries 只是一个子查询,其中添加了一个 LIMIT 子句代表你做的切片。

下面是详细情况:

def auto_update_ratings(amounts, assessment_entries_qs, lowest_rating=-1):
# assessment_entries_qs is an unevaluated QuerySet
# from your calling code, it would probably generate a query like this:
# SELECT * FROM assessments ORDER BY RANDOM()
start = 0
rating = lowest_rating
ids = assessment_entries_qs.values_list('id', flat=True)
# ids is a ValueQuerySet that adds "SELECT id"
# to the query that assessment_entries_qs would generate.
# So ids is now something like:
# SELECT id FROM assessments ORDER BY RANDOM()

# we omit the loop

for amount in amounts:
end_mark = start + amount
entries = ids[start:end_mark]
# entries is now another QuerySet with a LIMIT clause added:
# SELECT id FROM assessments ORDER BY RANDOM() LIMIT start,(start+end_mark)
# When filter() gets a QuerySet, it adds a subquery
a = assessment_entries_qs.filter(id__in=entries).update(rating=rating)
# FINALLY, we now actually EXECUTE a query which is something like this:
# UPDATE assessments SET rating=? WHERE id IN
# (SELECT id FROM assessments ORDER BY RANDOM() LIMIT start,(start+end_mark))
start = end_mark
rating += 1

由于 entries 中的子查询每次插入 时都会执行,而且它的顺序是随机的,所以你做的切片是没有意义的!此函数没有确定性行为。

但是,当您迭代 ids 时,您实际上执行的是查询,因此您的切片再次具有确定性行为,并且代码会按照您的预期进行。

让我们看看当您改用循环时会发生什么:

ids = assessment_entries_qs.values_list('id', flat=True)

# Iterating ids causes the query to actually be executed
# This query was sent to the DB:
# SELECT id FROM assessments ORDER BY RANDOM()
for id in ids:
pass

# ids has now been "realized" and contains the *results* of the query
# e.g., [5,1,2,3,4]
# Iterating again (or slicing) will now return values rather than modify the query

for amount in amounts:
end_mark = start + amount
entries = ids[start:end_mark]
# because ids was executed, entries contains definite values
# When filter() gets actual values, it adds a simple condition
a = assessment_entries_qs.filter(id__in=entries).update(rating=rating)
# The query executed is something like this:
# UPDATE assessments SET rating=? WHERE id IN (5,1)
# "(5,1)" will change on each iteration, but it will always be a set of
# scalar values rather than a subquery.
start = end_mark
rating += 1

如果您需要立即评估 QuerySet 以获取其所有值,而不是执行无所事事的迭代,只需将其转换为列表即可:

    ids = list(assessment_entries_qs.values_list('id', flat=True))

Django 文档也详细介绍了 when exactly a QuerySet is evaluated .

关于python - 为什么/如何迭代列表并每次调用 'pass' 修复此功能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14916403/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com