Skip to content

Fix the heapq nlargest/nsmallest equivalence claim for negative n - #157681

Open
Prafyl wants to merge 1 commit into
python:mainfrom
Prafyl:docs/heapq-negative-n
Open

Prafyl wants to merge 1 commit into
python:mainfrom
Prafyl:docs/heapq-negative-n

Conversation

@Prafyl

@Prafyl Prafyl commented Sep 17, 2026

Copy link
Copy Markdown

The docs and the docstrings for nlargest and nsmallest both say:

Equivalent to:  sorted(iterable, key=key)[:n]
Equivalent to:  sorted(iterable, key=key, reverse=True)[:n]

That is not true for a negative n:

>>> heapq.nsmallest(-1, [5, 3, 8, 1, 9, 2])
[]
>>> sorted([5, 3, 8, 1, 9, 2])[:-1]
[1, 2, 3, 5, 8]

So nlargest and nsmallest are not equivalent to that slice when n is a negative number.

nsmallest builds its first chunk with [(elem, i) for i, elem in zip(range(n), it)]. range(-1) is empty, and thats why result ends up empty and the function returns [] right there at the if not result check. nlargest ends up the same way, it just uses zip(range(0, -n, -1), it) instead, which is also empty when n is negative.

n == 0 is fine, both give [], so its only the negative n that differs.

Rather than adding a caveat i changed the slice to [:max(n, 0)], which keeps it an expression you can paste into a REPL. I checked that against both functions over every list up to length 5 with lots of duplicates, and over random inputs with a key= and with generators, and it matches everywhere.

test_nsmallest and test_nlargest in Lib/test/test_heapq.py already check this equivalence, but the n values they loop over are (0, 1, 2, 10, 100, 400, 999, 1000, 1100), so a negative n never gets tried. i added some negative values there and corrected the expected expression to match.

The same wording is in Doc/library/heapq.rst and in the two docstrings in Lib/heapq.py, so i changed all four. but there is no behaviour change.

The docs and both docstrings say nlargest and nsmallest are equivalent
to sorted(iterable, key=key)[:n] and sorted(iterable, key=key,
reverse=True)[:n], but that is wrong when n is negative.
nsmallest(-1, [5, 3, 8, 1, 9, 2]) returns [] while sorted(...)[:-1]
returns [1, 2, 3, 5, 8].

nsmallest builds its first chunk with zip(range(n), it) and nlargest
with zip(range(0, -n, -1), it). Both are empty when n is negative, so
both return [] at the `if not result` check. n == 0 agrees, so it is
only negative n that differs.

Changed the slice to [:max(n, 0)] rather than adding a caveat, so the
equivalence stays an expression you can actually run. Applies to
Doc/library/heapq.rst and to the two docstrings in Lib/heapq.py.

test_nsmallest and test_nlargest already check this equivalence, but
only with non-negative n, so negative values are added there and the
expected expression is corrected to match.
@Prafyl
Prafyl requested a review from rhettinger as a code owner September 17, 2026 13:15
@python-cla-bot

python-cla-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app

bedevere-app Bot commented Sep 17, 2026

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #34613960 | 📁 Comparing 4703cc6 against main (1e03154)

  🔍 Preview build  

1 file changed
± library/heapq.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant