Skip to content

random.sample raises "IndexError: pop from empty list" when both "population" and "counts" are empty #130285

Description

@Dominik1123

Bug report

Bug description:

I just encountered the situation where I used random.sample but both the population and counts arguments were empty (my algorithm had nothing left to choose from). So, basically this situation:

>>> random.sample([], 1, counts=[])
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    random.sample([], 1, counts=[])
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/path/to/lib/python3.14/random.py", line 424, in sample
    total = cum_counts.pop()
IndexError: pop from empty list

Instead of the IndexError, I expected a ValueError, similar to the following situations:

>>> random.sample([], 1)
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    random.sample([], 1)
    ~~~~~~~~~~~~~^^^^^^^
  File "/path/to/lib/python3.14/random.py", line 434, in sample
    raise ValueError("Sample larger than population or is negative")
ValueError: Sample larger than population or is negative
>>> 
>>> random.sample([1], 2, counts=[1])
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    random.sample([1], 2, counts=[1])
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/path/to/lib/python3.14/random.py", line 429, in sample
    selections = self.sample(range(total), k=k)
  File "/path/to/lib/python3.14/random.py", line 434, in sample
    raise ValueError("Sample larger than population or is negative")
ValueError: Sample larger than population or is negative

The docs mention that

If the sample size is larger than the population size, a ValueError is raised.

In addition, I would expect the following to work:

>>> random.sample([], 0, counts=[])
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    random.sample([], 0, counts=[])
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/path/to/lib/python3.14/random.py", line 424, in sample
    total = cum_counts.pop()
IndexError: pop from empty list

similar to how it works when counts is not specified:

>>> random.sample([], 0)
[]

Not sure though what CPython's backwards-compatibility policy has to say here, since changing the exception type – or, in the second case, removing the exception altogether – might actually break someone's code...


Tested with:

Python 3.14.0a5 (main, Feb 12 2025, 14:51:40) [Clang 19.1.6 ] on linux
cpython-3.14.0a5-linux-x86_64-gnu

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Linked PRs

Activity

  1. self-assigned this
    on Feb 18, 2025
  2. rhettinger commented on Feb 18, 2025

    @rhettinger
    Contributor

    Neither of these cases was tested or intended behavior, so it would be reasonable to fix them both. I'll work on a PR soonish. Thanks for the report.

    Because of the possibility of breaking code, I'm -0 on backporting the edit.

  3. Dominik1123 commented on Feb 18, 2025

    @Dominik1123
    ContributorAuthor

    Somewhat related is the situation when k=0 and all counts are zero:

    >>> random.sample('abc', k=0, counts=[0,0,0])
    Traceback (most recent call last):
      File "<python-input-1>", line 1, in <module>
        random.sample('abc', k=0, counts=[0,0,0])
        ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/path/to/lib/python3.14/random.py", line 428, in sample
        raise ValueError('Total of counts must be greater than zero')
    ValueError: Total of counts must be greater than zero

    According to the docs:

    Repeated elements can be specified one at a time or with the optional keyword-only counts parameter. For example, sample(['red', 'blue'], counts=[4, 2], k=5) is equivalent to sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5).

    So, extrapolating this to zero counts, this would be random.sample('abc', k=0, counts=[0,0,0]) is equivalent to random.sample([], k=0) which, however, returns [].

  4. StanFromIreland commented on Feb 18, 2025

    @StanFromIreland
    Member

    I propose we do

    cum_counts = list(_accumulate(counts))

    if not cum_counts:
        if k == 0:
            return []
        else:
            raise ValueError("Sample larger than population or is negative")
    

    if len(cum_counts) != n:

    And add tests, what do you think @rhettinger ? I see you are planning to do this

  5. Dominik1123 commented on Feb 18, 2025

    @Dominik1123
    ContributorAuthor

    What about (random.py#L424)

    total = cum_counts.pop() if cum_counts else 0
    if not isinstance(total, int):
        raise TypeError('Counts must be integers')
    if total < 0:
        raise ValueError('Total of counts must be non-negative')

    That would also address the random.sample('abc', k=0, counts=[0,0,0]) == random.sample([], k=0) case.

    If total == 0 and k > 0 this also wouldn't change the type of exception as it would become ValueError("Sample larger than population or is negative") instead.

  6. added a commit that references this issue on Feb 19, 2025
  7. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Feb 21, 2025
  8. added a commit that references this issue on Feb 21, 2025
  9. added 2 commits that reference this issue on Feb 21, 2025
  10. added 2 commits that reference this issue on Feb 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions