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)
randrange(start, stop[, step])
Return a randomly selected element from ``range(start, stop, step)``. This is
equivalent to ``choice(range(start, stop, step))``, but doesn't actually build a
range object.
Return a randomly selected element from ``range(start, stop, step)``.
The positional argument pattern matches that of :func:`range`. Keyword arguments
should not be used because the function may use them in unexpected ways.
This is roughly equivalent to ``choice(range(start, stop, step))`` but
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
:meth:`randrange` is more sophisticated about producing equally distributed
values. Formerly it used a style like ``int(random()*n)`` which could produce
slightly uneven distributions.
.. deprecated:: 3.10
The automatic conversion of non-integer types to equivalent integers is
deprecated. Currently ``randrange(10.0)`` is losslessly converted to
``randrange(10)``. In the future, this will 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`.
.. versionchanged:: 3.12
Automatic conversion of non-integer types is no longer supported.
Calls such as ``randrange(10.0)`` and ``randrange(Fraction(10, 1))``
now raise a :exc:`TypeError`.
.. function:: randint(a, b)

View File

@ -102,8 +102,6 @@ Deprecated
Removed
=======
Porting to Python 3.12
======================
@ -120,6 +118,14 @@ Changes in the Python API
contain ASCII letters and digits and underscore.
(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
=============

View File

@ -282,67 +282,34 @@ class Random(_random.Random):
## -------------------- integer methods -------------------
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
endpoint; in Python this is usually not what you want.
Roughly equivalent to ``choice(range(start, stop, step))`` but
supports arbitrarily large ranges and is optimized for common cases.
"""
# This code is a bit messy to make it fast for the
# common case while still doing adequate error checking.
try:
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)
istart = _index(start)
if stop is None:
# We don't check for "step != 1" because it hasn't been
# type checked and converted to an integer yet.
if step is not _ONE:
raise TypeError('Missing a non-None stop argument')
raise TypeError("Missing a non-None stop argument")
if istart > 0:
return self._randbelow(istart)
raise ValueError("empty range for randrange()")
# stop argument supplied.
try:
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)
# Stop argument supplied.
istop = _index(stop)
width = istop - istart
try:
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)
istep = _index(step)
# Fast path.
if istep == 1:
if width > 0:
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.
if istep > 0:
@ -352,7 +319,7 @@ class Random(_random.Random):
else:
raise ValueError("zero step for randrange()")
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)
def randint(self, a, b):

View File

@ -485,50 +485,44 @@ class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertEqual(rint, 0)
def test_randrange_errors(self):
raises = partial(self.assertRaises, ValueError, self.gen.randrange)
# Empty range
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)
raises_value_error = partial(self.assertRaises, ValueError, self.gen.randrange)
raises_type_error = partial(self.assertRaises, TypeError, self.gen.randrange)
def test_randrange_argument_handling(self):
randrange = self.gen.randrange
with self.assertWarns(DeprecationWarning):
randrange(10.0, 20, 2)
with self.assertWarns(DeprecationWarning):
randrange(10, 20.0, 2)
with self.assertWarns(DeprecationWarning):
randrange(10, 20, 1.0)
with self.assertWarns(DeprecationWarning):
randrange(10, 20, 2.0)
with self.assertWarns(DeprecationWarning):
with self.assertRaises(ValueError):
randrange(10.5)
with self.assertWarns(DeprecationWarning):
with self.assertRaises(ValueError):
randrange(10, 20.5)
with self.assertWarns(DeprecationWarning):
with self.assertRaises(ValueError):
randrange(10, 20, 1.5)
# Empty range
raises_value_error(3, 3)
raises_value_error(-721)
raises_value_error(0, 100, -12)
# Zero step
raises_value_error(0, 42, 0)
raises_type_error(0, 42, 0.0)
raises_type_error(0, 0, 0.0)
# Non-integer stop
raises_type_error(3.14159)
raises_type_error(3.0)
raises_type_error(Fraction(3, 1))
raises_type_error('3')
raises_type_error(0, 2.71827)
raises_type_error(0, 2.0)
raises_type_error(0, Fraction(2, 1))
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):
# 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.