Fixed #32518 -- Doc'd that QuerySet.contains() should not be overused.

Thanks Tim McCurrach for the idea.
This commit is contained in:
John Hollingsworth 2022-01-26 10:50:46 -07:00 committed by Mariusz Felisiak
parent e3f34b1f09
commit d70b4bea18
1 changed files with 30 additions and 22 deletions

View File

@ -261,44 +261,52 @@ But:
.. _overuse_of_count_and_exists: .. _overuse_of_count_and_exists:
Don't overuse ``count()`` and ``exists()`` Don't overuse ``contains()``, ``count()``, and ``exists()``
------------------------------------------ -----------------------------------------------------------
If you are going to need other data from the QuerySet, evaluate it immediately. If you are going to need other data from the QuerySet, evaluate it immediately.
For example, assuming an Email model that has a ``subject`` attribute and a For example, assuming a ``Group`` model that has a many-to-many relation to
many-to-many relation to User, the following code is optimal:: ``User``, the following code is optimal::
if display_emails: members = group.members.all()
emails = user.emails.all()
if emails: if display_group_members:
print('You have', len(emails), 'emails:') if members:
for email in emails: if current_user in members:
print(email.subject) print("You and", len(members) - 1, "other users are members of this group.")
else:
print("There are", len(members), "members in this group.")
for member in members:
print(member.username)
else: else:
print('You do not have any emails.') print("There are no members in this group.")
It is optimal because: It is optimal because:
#. Since QuerySets are lazy, this does no database queries if #. Since QuerySets are lazy, this does no database queries if
``display_emails`` is ``False``. ``display_group_members`` is ``False``.
#. Storing ``user.emails.all()`` in the ``emails`` variable allows its result #. Storing ``group.members.all()`` in the ``members`` variable allows its
cache to be re-used. result cache to be re-used.
#. The line ``if emails`` causes ``QuerySet.__bool__()`` to be called, which #. The line ``if members:`` causes ``QuerySet.__bool__()`` to be called, which
causes the ``user.emails.all()`` query to be run on the database. If there causes the ``group.members.all()`` query to be run on the database. If there
aren't any results, it will return ``False``, otherwise ``True``. aren't any results, it will return ``False``, otherwise ``True``.
#. The use of ``len(emails)`` calls ``QuerySet.__len__()``, reusing the result #. The line ``if current_user in members:`` checks if the user is in the result
cache. cache, so no additional database queries are issued.
#. The ``for`` loop iterates over the already filled cache. #. The use of ``len(members)`` calls ``QuerySet.__len__()``, reusing the result
cache, so again, no database queries are issued.
#. The ``for member`` loop iterates over the result cache.
In total, this code does either one or zero database queries. The only In total, this code does either one or zero database queries. The only
deliberate optimization performed is using the ``emails`` variable. Using deliberate optimization performed is using the ``members`` variable. Using
``QuerySet.exists()`` for the ``if`` or ``QuerySet.count()`` for the count ``QuerySet.exists()`` for the ``if``, ``QuerySet.contains()`` for the ``in``,
would each cause additional queries. or ``QuerySet.count()`` for the count would each cause additional queries.
Use ``QuerySet.update()`` and ``delete()`` Use ``QuerySet.update()`` and ``delete()``
------------------------------------------ ------------------------------------------