Skip to content

Commit

Permalink
bpo-42222: Modernize integer test/conversion in randrange() (#23064)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhettinger committed Dec 28, 2020
1 parent 1031f23 commit a9621bb
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 11 deletions.
9 changes: 9 additions & 0 deletions Doc/library/random.rst
Expand Up @@ -135,6 +135,15 @@ Functions for integers
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 ``range(10.5)``
will be changed from :exc:`ValueError` to :exc:`TypeError`.

.. function:: randint(a, b)

Return a random integer *N* such that ``a <= N <= b``. Alias for
Expand Down
54 changes: 43 additions & 11 deletions Lib/random.py
Expand Up @@ -51,6 +51,7 @@
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 operator import index as _index
from itertools import accumulate as _accumulate, repeat as _repeat
from bisect import bisect as _bisect
import os as _os
Expand Down Expand Up @@ -297,28 +298,59 @@ def randrange(self, start, stop=None, step=1):

# This code is a bit messy to make it fast for the
# common case while still doing adequate error checking.
istart = int(start)
if istart != start:
raise ValueError("non-integer arg 1 for randrange()")
try:
istart = _index(start)
except TypeError:
if int(start) == start:
istart = int(start)
_warn('Float arguments to randrange() have been deprecated\n'
'since Python 3.10 and will be removed in a subsequent '
'version.',
DeprecationWarning, 2)
else:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer arg 1 for randrange()")
if stop is None:
if istart > 0:
return self._randbelow(istart)
raise ValueError("empty range for randrange()")

# stop argument supplied.
istop = int(stop)
if istop != stop:
raise ValueError("non-integer stop for randrange()")
try:
istop = _index(stop)
except TypeError:
if int(stop) == stop:
istop = int(stop)
_warn('Float arguments to randrange() have been deprecated\n'
'since Python 3.10 and will be removed in a subsequent '
'version.',
DeprecationWarning, 2)
else:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer stop for randrange()")

try:
istep = _index(step)
except TypeError:
if int(step) == step:
istep = int(step)
_warn('Float arguments to randrange() have been deprecated\n'
'since Python 3.10 and will be removed in a subsequent '
'version.',
DeprecationWarning, 2)
else:
_warn('randrange() will raise TypeError in the future',
DeprecationWarning, 2)
raise ValueError("non-integer step for randrange()")
width = istop - istart
if step == 1 and width > 0:
if istep == 1 and width > 0:
return istart + self._randbelow(width)
if step == 1:
if istep == 1:
raise ValueError("empty range for randrange() (%d, %d, %d)" % (istart, istop, width))

# Non-unit step argument supplied.
istep = int(step)
if istep != step:
raise ValueError("non-integer step for randrange()")
if istep > 0:
n = (width + istep - 1) // istep
elif istep < 0:
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_random.py
Expand Up @@ -542,6 +542,26 @@ def test_randrange_errors(self):
raises(0, 42, 0)
raises(0, 42, 3.14159)

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)

def test_randbelow_logic(self, _log=log, int=int):
# check bitcount transition points: 2**i and 2**(i+1)-1
# show that: k = int(1.001 + _log(n, 2))
Expand Down
@@ -0,0 +1,9 @@
Harmonized random.randrange() argument handling to match range().

* The integer test and conversion in randrange() now uses
operator.index().
* Non-integer arguments to randrange() are deprecated.
* The *ValueError* is deprecated in favor of a *TypeError*.
* It now runs a little faster than before.

(Contributed by Raymond Hettinger and Serhiy Storchaka.)

0 comments on commit a9621bb

Please sign in to comment.