Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-41773: random.choices doesn't support non-finite weights #22441

Merged
merged 1 commit into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Doc/library/random.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ Functions for sequences

The *weights* or *cum_weights* can use any numeric type that interoperates
with the :class:`float` values returned by :func:`random` (that includes
integers, floats, and fractions but excludes decimals). Behavior is
undefined if any weight is negative. A :exc:`ValueError` is raised if all
integers, floats, and fractions but excludes decimals). Weights are assumed
to be non-negative and finite. A :exc:`ValueError` is raised if all
weights are zero.

For a given seed, the :func:`choices` function with equal weighting
Expand Down
4 changes: 3 additions & 1 deletion Lib/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from warnings import warn as _warn
from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
from math import sqrt as _sqrt, acos as _acos, cos as _cos, sin as _sin
from math import tau as TWOPI, floor as _floor
from math import tau as TWOPI, floor as _floor, isfinite as _isfinite
from os import urandom as _urandom
from _collections_abc import Set as _Set, Sequence as _Sequence
from itertools import accumulate as _accumulate, repeat as _repeat
Expand Down Expand Up @@ -492,6 +492,8 @@ def choices(self, population, weights=None, *, cum_weights=None, k=1):
total = cum_weights[-1] + 0.0 # convert to float
if total <= 0.0:
raise ValueError('Total of weights must be greater than zero')
if not _isfinite(total):
raise ValueError('Total of weights must be finite')
bisect = _bisect
hi = n - 1
return [population[bisect(cum_weights, random() * total, 0, hi)]
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,22 @@ def test_choices_with_all_zero_weights(self):
with self.assertRaises(ValueError):
self.gen.choices('AB', [0.0, 0.0])

def test_choices_negative_total(self):
with self.assertRaises(ValueError):
self.gen.choices('ABC', [3, -5, 1])

def test_choices_infinite_total(self):
with self.assertRaises(ValueError):
self.gen.choices('A', [float('inf')])
with self.assertRaises(ValueError):
self.gen.choices('AB', [0.0, float('inf')])
with self.assertRaises(ValueError):
self.gen.choices('AB', [-float('inf'), 123])
with self.assertRaises(ValueError):
self.gen.choices('AB', [0.0, float('nan')])
with self.assertRaises(ValueError):
self.gen.choices('AB', [float('-inf'), float('inf')])

def test_gauss(self):
# Ensure that the seed() method initializes all the hidden state. In
# particular, through 2.2.1 it failed to reset a piece of state used
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Note in documentation that :func:`random.choices` doesn't support non-finite
weights, raise :exc:`ValueError` when given non-finite weights.