gh-86388 Remove deprecated behaviors in randrange() (#92677)

This commit is contained in:
Raymond Hettinger 2022-05-11 23:54:51 -05:00 committed by GitHub
parent f67d71b431
commit 68fec31364
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 102 deletions

View file

@ -123,27 +123,26 @@ Functions for integers
.. function:: randrange(stop) .. function:: randrange(stop)
randrange(start, stop[, step]) randrange(start, stop[, step])
Return a randomly selected element from ``range(start, stop, step)``. This is Return a randomly selected element from ``range(start, stop, step)``.
equivalent to ``choice(range(start, stop, step))``, but doesn't actually build a
range object.
The positional argument pattern matches that of :func:`range`. Keyword arguments This is roughly equivalent to ``choice(range(start, stop, step))`` but
should not be used because the function may use them in unexpected ways. supports arbitrarily large ranges and is optimized for common cases.
The positional argument pattern matches the :func:`range` function.
Keyword arguments should not be used because they can interpreted
in unexpected ways. For example ``range(start=100)`` is interpreted
as ``range(0, 100, 1)``.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
:meth:`randrange` is more sophisticated about producing equally distributed :meth:`randrange` is more sophisticated about producing equally distributed
values. Formerly it used a style like ``int(random()*n)`` which could produce values. Formerly it used a style like ``int(random()*n)`` which could produce
slightly uneven distributions. slightly uneven distributions.
.. deprecated:: 3.10 .. versionchanged:: 3.12
The automatic conversion of non-integer types to equivalent integers is Automatic conversion of non-integer types is no longer supported.
deprecated. Currently ``randrange(10.0)`` is losslessly converted to Calls such as ``randrange(10.0)`` and ``randrange(Fraction(10, 1))``
``randrange(10)``. In the future, this will raise a :exc:`TypeError`. now raise a :exc:`TypeError`.
.. deprecated:: 3.10
The exception raised for non-integral values such as ``randrange(10.5)``
or ``randrange('10')`` will be changed from :exc:`ValueError` to
:exc:`TypeError`.
.. function:: randint(a, b) .. function:: randint(a, b)

View file

@ -102,8 +102,6 @@ Deprecated
Removed Removed
======= =======
Porting to Python 3.12 Porting to Python 3.12
====================== ======================
@ -120,6 +118,14 @@ Changes in the Python API
contain ASCII letters and digits and underscore. contain ASCII letters and digits and underscore.
(Contributed by Serhiy Storchaka in :gh:`91760`.) (Contributed by Serhiy Storchaka in :gh:`91760`.)
* Removed randrange() functionality deprecated since Python 3.10. Formerly,
randrange(10.0) losslessly converted to randrange(10). Now, it raises a
TypeError. Also, the exception raised for non-integral values such as
randrange(10.5) or randrange('10') has been changed from ValueError to
TypeError. This also prevents bugs where ``randrange(1e25)`` would silently
select from a larger range than ``randrange(10**25)``.
(Originally suggested by Serhiy Storchaka gh-86388.)
Build Changes Build Changes
============= =============

View file

@ -282,67 +282,34 @@ class Random(_random.Random):
## -------------------- integer methods ------------------- ## -------------------- integer methods -------------------
def randrange(self, start, stop=None, step=_ONE): def randrange(self, start, stop=None, step=_ONE):
"""Choose a random item from range(start, stop[, step]). """Choose a random item from range(stop) or range(start, stop[, step]).
This fixes the problem with randint() which includes the Roughly equivalent to ``choice(range(start, stop, step))`` but
endpoint; in Python this is usually not what you want. supports arbitrarily large ranges and is optimized for common cases.
""" """
# This code is a bit messy to make it fast for the # This code is a bit messy to make it fast for the
# common case while still doing adequate error checking. # common case while still doing adequate error checking.
try: istart = _index(start)
istart = _index(start)
except TypeError:
istart = int(start)
if istart != start:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer arg 1 for randrange()")
_warn('non-integer arguments to randrange() have been deprecated '
'since Python 3.10 and will be removed in a subsequent '
'version',
DeprecationWarning, 2)
if stop is None: if stop is None:
# We don't check for "step != 1" because it hasn't been # We don't check for "step != 1" because it hasn't been
# type checked and converted to an integer yet. # type checked and converted to an integer yet.
if step is not _ONE: if step is not _ONE:
raise TypeError('Missing a non-None stop argument') raise TypeError("Missing a non-None stop argument")
if istart > 0: if istart > 0:
return self._randbelow(istart) return self._randbelow(istart)
raise ValueError("empty range for randrange()") raise ValueError("empty range for randrange()")
# stop argument supplied. # Stop argument supplied.
try: istop = _index(stop)
istop = _index(stop)
except TypeError:
istop = int(stop)
if istop != stop:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer stop for randrange()")
_warn('non-integer arguments to randrange() have been deprecated '
'since Python 3.10 and will be removed in a subsequent '
'version',
DeprecationWarning, 2)
width = istop - istart width = istop - istart
try: istep = _index(step)
istep = _index(step)
except TypeError:
istep = int(step)
if istep != step:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer step for randrange()")
_warn('non-integer arguments to randrange() have been deprecated '
'since Python 3.10 and will be removed in a subsequent '
'version',
DeprecationWarning, 2)
# Fast path. # Fast path.
if istep == 1: if istep == 1:
if width > 0: if width > 0:
return istart + self._randbelow(width) return istart + self._randbelow(width)
raise ValueError("empty range for randrange() (%d, %d, %d)" % (istart, istop, width)) raise ValueError(f"empty range in randrange({start}, {stop})")
# Non-unit step argument supplied. # Non-unit step argument supplied.
if istep > 0: if istep > 0:
@ -352,7 +319,7 @@ class Random(_random.Random):
else: else:
raise ValueError("zero step for randrange()") raise ValueError("zero step for randrange()")
if n <= 0: if n <= 0:
raise ValueError("empty range for randrange()") raise ValueError(f"empty range in randrange({start}, {stop}, {step})")
return istart + istep * self._randbelow(n) return istart + istep * self._randbelow(n)
def randint(self, a, b): def randint(self, a, b):

View file

@ -485,50 +485,44 @@ class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(rint, 0) self.assertEqual(rint, 0)
def test_randrange_errors(self): def test_randrange_errors(self):
raises = partial(self.assertRaises, ValueError, self.gen.randrange) raises_value_error = partial(self.assertRaises, ValueError, self.gen.randrange)
# Empty range raises_type_error = partial(self.assertRaises, TypeError, self.gen.randrange)
raises(3, 3)
raises(-721)
raises(0, 100, -12)
# Non-integer start/stop
self.assertWarns(DeprecationWarning, raises, 3.14159)
self.assertWarns(DeprecationWarning, self.gen.randrange, 3.0)
self.assertWarns(DeprecationWarning, self.gen.randrange, Fraction(3, 1))
self.assertWarns(DeprecationWarning, raises, '3')
self.assertWarns(DeprecationWarning, raises, 0, 2.71828)
self.assertWarns(DeprecationWarning, self.gen.randrange, 0, 2.0)
self.assertWarns(DeprecationWarning, self.gen.randrange, 0, Fraction(2, 1))
self.assertWarns(DeprecationWarning, raises, 0, '2')
# Zero and non-integer step
raises(0, 42, 0)
self.assertWarns(DeprecationWarning, raises, 0, 42, 0.0)
self.assertWarns(DeprecationWarning, raises, 0, 0, 0.0)
self.assertWarns(DeprecationWarning, raises, 0, 42, 3.14159)
self.assertWarns(DeprecationWarning, self.gen.randrange, 0, 42, 3.0)
self.assertWarns(DeprecationWarning, self.gen.randrange, 0, 42, Fraction(3, 1))
self.assertWarns(DeprecationWarning, raises, 0, 42, '3')
self.assertWarns(DeprecationWarning, self.gen.randrange, 0, 42, 1.0)
self.assertWarns(DeprecationWarning, raises, 0, 0, 1.0)
def test_randrange_argument_handling(self): # Empty range
randrange = self.gen.randrange raises_value_error(3, 3)
with self.assertWarns(DeprecationWarning): raises_value_error(-721)
randrange(10.0, 20, 2) raises_value_error(0, 100, -12)
with self.assertWarns(DeprecationWarning):
randrange(10, 20.0, 2) # Zero step
with self.assertWarns(DeprecationWarning): raises_value_error(0, 42, 0)
randrange(10, 20, 1.0) raises_type_error(0, 42, 0.0)
with self.assertWarns(DeprecationWarning): raises_type_error(0, 0, 0.0)
randrange(10, 20, 2.0)
with self.assertWarns(DeprecationWarning): # Non-integer stop
with self.assertRaises(ValueError): raises_type_error(3.14159)
randrange(10.5) raises_type_error(3.0)
with self.assertWarns(DeprecationWarning): raises_type_error(Fraction(3, 1))
with self.assertRaises(ValueError): raises_type_error('3')
randrange(10, 20.5) raises_type_error(0, 2.71827)
with self.assertWarns(DeprecationWarning): raises_type_error(0, 2.0)
with self.assertRaises(ValueError): raises_type_error(0, Fraction(2, 1))
randrange(10, 20, 1.5) raises_type_error(0, '2')
raises_type_error(0, 2.71827, 2)
# Non-integer start
raises_type_error(2.71827, 5)
raises_type_error(2.0, 5)
raises_type_error(Fraction(2, 1), 5)
raises_type_error('2', 5)
raises_type_error(2.71827, 5, 2)
# Non-integer step
raises_type_error(0, 42, 3.14159)
raises_type_error(0, 42, 3.0)
raises_type_error(0, 42, Fraction(3, 1))
raises_type_error(0, 42, '3')
raises_type_error(0, 42, 1.0)
raises_type_error(0, 0, 1.0)
def test_randrange_step(self): def test_randrange_step(self):
# bpo-42772: When stop is None, the step argument was being ignored. # bpo-42772: When stop is None, the step argument was being ignored.

View file

@ -0,0 +1,5 @@
Removed randrange() functionality deprecated since Python 3.10. Formerly,
randrange(10.0) losslessly converted to randrange(10). Now, it raises a
TypeError. Also, the exception raised for non-integral values such as
randrange(10.5) or randrange('10') has been changed from ValueError to
TypeError.