Skip to content

bpo-42222: Modernize integer test/conversion in randrange() #23064

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

Merged
merged 9 commits into from
Dec 28, 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
9 changes: 9 additions & 0 deletions Doc/library/random.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,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
Original file line number Diff line number Diff line change
@@ -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.)