]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-130285: Fix handling of zero or empty counts in random.sample() (gh-130291)
authorRaymond Hettinger <rhettinger@users.noreply.github.com>
Fri, 21 Feb 2025 17:33:10 +0000 (11:33 -0600)
committerGitHub <noreply@github.com>
Fri, 21 Feb 2025 17:33:10 +0000 (11:33 -0600)
Lib/random.py
Lib/test/test_random.py
Misc/NEWS.d/next/Library/2025-02-21-10-32-05.gh-issue-130285.C0fkh7.rst [new file with mode: 0644]

index 8b9a270c429e4acbac39a00f2b328cf9902c5679..1abcae77c8be577cf699aee20e62efb49bc8394f 100644 (file)
@@ -421,11 +421,11 @@ class Random(_random.Random):
             cum_counts = list(_accumulate(counts))
             if len(cum_counts) != n:
                 raise ValueError('The number of counts does not match the population')
-            total = cum_counts.pop()
+            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 greater than zero')
+            if total < 0:
+                raise ValueError('Counts must be non-negative')
             selections = self.sample(range(total), k=k)
             bisect = _bisect
             return [population[bisect(cum_counts, s)] for s in selections]
index 51f9193b269eee88ce3d6fafed2bdb2b9ddb4020..96f6cc86219a5daff778c8156873b31443ad9a7c 100644 (file)
@@ -225,8 +225,6 @@ class TestBasicOps:
             sample(['red', 'green', 'blue'], counts=10, k=10)               # counts not iterable
         with self.assertRaises(ValueError):
             sample(['red', 'green', 'blue'], counts=[-3, -7, -8], k=2)      # counts are negative
-        with self.assertRaises(ValueError):
-            sample(['red', 'green', 'blue'], counts=[0, 0, 0], k=2)         # counts are zero
         with self.assertRaises(ValueError):
             sample(['red', 'green'], counts=[10, 10], k=21)                 # population too small
         with self.assertRaises(ValueError):
@@ -234,6 +232,20 @@ class TestBasicOps:
         with self.assertRaises(ValueError):
             sample(['red', 'green', 'blue'], counts=[1, 2, 3, 4], k=2)      # too many counts
 
+        # Cases with zero counts match equivalents without counts (see gh-130285)
+        self.assertEqual(
+            sample('abc', k=0, counts=[0, 0, 0]),
+            sample([], k=0),
+        )
+        self.assertEqual(
+            sample([], 0, counts=[]),
+            sample([], 0),
+        )
+        with self.assertRaises(ValueError):
+            sample([], 1, counts=[])
+        with self.assertRaises(ValueError):
+            sample('x', 1, counts=[0])
+
     def test_choices(self):
         choices = self.gen.choices
         data = ['red', 'green', 'blue', 'yellow']
diff --git a/Misc/NEWS.d/next/Library/2025-02-21-10-32-05.gh-issue-130285.C0fkh7.rst b/Misc/NEWS.d/next/Library/2025-02-21-10-32-05.gh-issue-130285.C0fkh7.rst
new file mode 100644 (file)
index 0000000..7e0a4d2
--- /dev/null
@@ -0,0 +1,4 @@
+Fix corner case for :func:`random.sample` allowing the *counts* parameter to
+specify an empty population. So now, ``sample([], 0, counts=[])`` and
+``sample('abc', k=0, counts=[0, 0, 0])`` both give the same result as
+``sample([], 0)``.