Skip to content

Commit 31c33c8

Browse files
authored
Merge pull request #4748 from jyj0816/random
Fix int.from_bytes and Update random.py and test/test_random.py from CPython v3.11.2
2 parents 8c5a279 + 8c9a33b commit 31c33c8

File tree

3 files changed

+71
-89
lines changed

3 files changed

+71
-89
lines changed

Lib/random.py

Lines changed: 28 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,11 @@ def seed(self, a=None, version=2):
167167
elif version == 2 and isinstance(a, (str, bytes, bytearray)):
168168
if isinstance(a, str):
169169
a = a.encode()
170-
a = int.from_bytes(a + _sha512(a).digest(), 'big')
170+
a = int.from_bytes(a + _sha512(a).digest())
171171

172172
elif not isinstance(a, (type(None), int, float, str, bytes, bytearray)):
173-
_warn('Seeding based on hashing is deprecated\n'
174-
'since Python 3.9 and will be removed in a subsequent '
175-
'version. The only \n'
176-
'supported seed types are: None, '
177-
'int, float, str, bytes, and bytearray.',
178-
DeprecationWarning, 2)
173+
raise TypeError('The only supported seed types are: None,\n'
174+
'int, float, str, bytes, and bytearray.')
179175

180176
super().seed(a)
181177
self.gauss_next = None
@@ -250,10 +246,8 @@ def __init_subclass__(cls, /, **kwargs):
250246
break
251247

252248
def _randbelow_with_getrandbits(self, n):
253-
"Return a random int in the range [0,n). Returns 0 if n==0."
249+
"Return a random int in the range [0,n). Defined for n > 0."
254250

255-
if not n:
256-
return 0
257251
getrandbits = self.getrandbits
258252
k = n.bit_length() # don't use (n-1) here because n can be 1
259253
r = getrandbits(k) # 0 <= r < 2**k
@@ -262,7 +256,7 @@ def _randbelow_with_getrandbits(self, n):
262256
return r
263257

264258
def _randbelow_without_getrandbits(self, n, maxsize=1<<BPF):
265-
"""Return a random int in the range [0,n). Returns 0 if n==0.
259+
"""Return a random int in the range [0,n). Defined for n > 0.
266260
267261
The implementation does not use getrandbits, but only random.
268262
"""
@@ -273,8 +267,6 @@ def _randbelow_without_getrandbits(self, n, maxsize=1<<BPF):
273267
"enough bits to choose from a population range this large.\n"
274268
"To remove the range limitation, add a getrandbits() method.")
275269
return _floor(random() * n)
276-
if n == 0:
277-
return 0
278270
rem = maxsize % n
279271
limit = (maxsize - rem) / maxsize # int(limit * maxsize) % n == 0
280272
r = random()
@@ -303,10 +295,10 @@ def randbytes(self, n):
303295
## -------------------- integer methods -------------------
304296

305297
def randrange(self, start, stop=None, step=_ONE):
306-
"""Choose a random item from range(start, stop[, step]).
298+
"""Choose a random item from range(stop) or range(start, stop[, step]).
307299
308-
This fixes the problem with randint() which includes the
309-
endpoint; in Python this is usually not what you want.
300+
Roughly equivalent to ``choice(range(start, stop, step))`` but
301+
supports arbitrarily large ranges and is optimized for common cases.
310302
311303
"""
312304

@@ -387,37 +379,24 @@ def randint(self, a, b):
387379

388380
def choice(self, seq):
389381
"""Choose a random element from a non-empty sequence."""
390-
# raises IndexError if seq is empty
391-
return seq[self._randbelow(len(seq))]
392-
393-
def shuffle(self, x, random=None):
394-
"""Shuffle list x in place, and return None.
395382

396-
Optional argument random is a 0-argument function returning a
397-
random float in [0.0, 1.0); if it is the default None, the
398-
standard random.random will be used.
383+
# As an accommodation for NumPy, we don't use "if not seq"
384+
# because bool(numpy.array()) raises a ValueError.
385+
if not len(seq):
386+
raise IndexError('Cannot choose from an empty sequence')
387+
return seq[self._randbelow(len(seq))]
399388

400-
"""
389+
def shuffle(self, x):
390+
"""Shuffle list x in place, and return None."""
401391

402-
if random is None:
403-
randbelow = self._randbelow
404-
for i in reversed(range(1, len(x))):
405-
# pick an element in x[:i+1] with which to exchange x[i]
406-
j = randbelow(i + 1)
407-
x[i], x[j] = x[j], x[i]
408-
else:
409-
_warn('The *random* parameter to shuffle() has been deprecated\n'
410-
'since Python 3.9 and will be removed in a subsequent '
411-
'version.',
412-
DeprecationWarning, 2)
413-
floor = _floor
414-
for i in reversed(range(1, len(x))):
415-
# pick an element in x[:i+1] with which to exchange x[i]
416-
j = floor(random() * (i + 1))
417-
x[i], x[j] = x[j], x[i]
392+
randbelow = self._randbelow
393+
for i in reversed(range(1, len(x))):
394+
# pick an element in x[:i+1] with which to exchange x[i]
395+
j = randbelow(i + 1)
396+
x[i], x[j] = x[j], x[i]
418397

419398
def sample(self, population, k, *, counts=None):
420-
"""Chooses k unique random elements from a population sequence or set.
399+
"""Chooses k unique random elements from a population sequence.
421400
422401
Returns a new list containing elements from the population while
423402
leaving the original population unchanged. The resulting list is
@@ -470,13 +449,8 @@ def sample(self, population, k, *, counts=None):
470449
# causing them to eat more entropy than necessary.
471450

472451
if not isinstance(population, _Sequence):
473-
if isinstance(population, _Set):
474-
_warn('Sampling from a set deprecated\n'
475-
'since Python 3.9 and will be removed in a subsequent version.',
476-
DeprecationWarning, 2)
477-
population = tuple(population)
478-
else:
479-
raise TypeError("Population must be a sequence. For dicts or sets, use sorted(d).")
452+
raise TypeError("Population must be a sequence. "
453+
"For dicts or sets, use sorted(d).")
480454
n = len(population)
481455
if counts is not None:
482456
cum_counts = list(_accumulate(counts))
@@ -580,7 +554,7 @@ def triangular(self, low=0.0, high=1.0, mode=None):
580554
low, high = high, low
581555
return low + (high - low) * _sqrt(u * c)
582556

583-
def normalvariate(self, mu, sigma):
557+
def normalvariate(self, mu=0.0, sigma=1.0):
584558
"""Normal distribution.
585559
586560
mu is the mean, and sigma is the standard deviation.
@@ -601,7 +575,7 @@ def normalvariate(self, mu, sigma):
601575
break
602576
return mu + z * sigma
603577

604-
def gauss(self, mu, sigma):
578+
def gauss(self, mu=0.0, sigma=1.0):
605579
"""Gaussian distribution.
606580
607581
mu is the mean, and sigma is the standard deviation. This is
@@ -833,15 +807,15 @@ class SystemRandom(Random):
833807
"""
834808

835809
def random(self):
836-
"""Get the next random number in the range [0.0, 1.0)."""
837-
return (int.from_bytes(_urandom(7), 'big') >> 3) * RECIP_BPF
810+
"""Get the next random number in the range 0.0 <= X < 1.0."""
811+
return (int.from_bytes(_urandom(7)) >> 3) * RECIP_BPF
838812

839813
def getrandbits(self, k):
840814
"""getrandbits(k) -> x. Generates an int with k random bits."""
841815
if k < 0:
842816
raise ValueError('number of bits must be non-negative')
843817
numbytes = (k + 7) // 8 # bits / 8 and rounded up
844-
x = int.from_bytes(_urandom(numbytes), 'big')
818+
x = int.from_bytes(_urandom(numbytes))
845819
return x >> (numbytes * 8 - k) # trim excess bits
846820

847821
def randbytes(self, n):

Lib/test/test_random.py

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,11 @@ def __hash__(self):
5252
self.gen.seed(arg)
5353

5454
for arg in [1+2j, tuple('abc'), MySeed()]:
55-
with self.assertWarns(DeprecationWarning):
55+
with self.assertRaises(TypeError):
5656
self.gen.seed(arg)
5757

5858
for arg in [list(range(3)), dict(one=1)]:
59-
with self.assertWarns(DeprecationWarning):
60-
self.assertRaises(TypeError, self.gen.seed, arg)
59+
self.assertRaises(TypeError, self.gen.seed, arg)
6160
self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4)
6261
self.assertRaises(TypeError, type(self.gen), [])
6362

@@ -110,22 +109,28 @@ def test_shuffle(self):
110109
self.assertTrue(lst != shuffled_lst)
111110
self.assertRaises(TypeError, shuffle, (1, 2, 3))
112111

113-
def test_shuffle_random_argument(self):
114-
# Test random argument to shuffle.
115-
shuffle = self.gen.shuffle
116-
mock_random = unittest.mock.Mock(return_value=0.5)
117-
seq = bytearray(b'abcdefghijk')
118-
with self.assertWarns(DeprecationWarning):
119-
shuffle(seq, mock_random)
120-
mock_random.assert_called_with()
121-
122112
def test_choice(self):
123113
choice = self.gen.choice
124114
with self.assertRaises(IndexError):
125115
choice([])
126116
self.assertEqual(choice([50]), 50)
127117
self.assertIn(choice([25, 75]), [25, 75])
128118

119+
def test_choice_with_numpy(self):
120+
# Accommodation for NumPy arrays which have disabled __bool__().
121+
# See: https://github.com/python/cpython/issues/100805
122+
choice = self.gen.choice
123+
124+
class NA(list):
125+
"Simulate numpy.array() behavior"
126+
def __bool__(self):
127+
raise RuntimeError
128+
129+
with self.assertRaises(IndexError):
130+
choice(NA([]))
131+
self.assertEqual(choice(NA([50])), 50)
132+
self.assertIn(choice(NA([25, 75])), [25, 75])
133+
129134
def test_sample(self):
130135
# For the entire allowable range of 0 <= k <= N, validate that
131136
# the sample is of the correct length and contains only unique items
@@ -169,7 +174,7 @@ def test_sample_on_dicts(self):
169174
self.assertRaises(TypeError, self.gen.sample, dict.fromkeys('abcdef'), 2)
170175

171176
def test_sample_on_sets(self):
172-
with self.assertWarns(DeprecationWarning):
177+
with self.assertRaises(TypeError):
173178
population = {10, 20, 30, 40, 50, 60, 70}
174179
self.gen.sample(population, k=5)
175180

@@ -391,23 +396,6 @@ def test_pickling(self):
391396
restoredseq = [newgen.random() for i in range(10)]
392397
self.assertEqual(origseq, restoredseq)
393398

394-
@test.support.cpython_only
395-
def test_bug_41052(self):
396-
# _random.Random should not be allowed to serialization
397-
import _random
398-
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
399-
r = _random.Random()
400-
self.assertRaises(TypeError, pickle.dumps, r, proto)
401-
402-
@test.support.cpython_only
403-
def test_bug_42008(self):
404-
# _random.Random should call seed with first element of arg tuple
405-
import _random
406-
r1 = _random.Random()
407-
r1.seed(8675309)
408-
r2 = _random.Random(8675309)
409-
self.assertEqual(r1.random(), r2.random())
410-
411399
# TODO: RUSTPYTHON AttributeError: 'super' object has no attribute 'getstate'
412400
@unittest.expectedFailure
413401
def test_bug_1727780(self):
@@ -445,6 +433,10 @@ def test_randbytes(self):
445433
self.assertRaises(ValueError, self.gen.randbytes, -1)
446434
self.assertRaises(TypeError, self.gen.randbytes, 1.0)
447435

436+
def test_mu_sigma_default_args(self):
437+
self.assertIsInstance(self.gen.normalvariate(), float)
438+
self.assertIsInstance(self.gen.gauss(), float)
439+
448440

449441
try:
450442
random.SystemRandom().random()
@@ -592,6 +584,25 @@ def test_randbelow_logic(self, _log=log, int=int):
592584
self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
593585

594586

587+
class TestRawMersenneTwister(unittest.TestCase):
588+
@test.support.cpython_only
589+
def test_bug_41052(self):
590+
# _random.Random should not be allowed to serialization
591+
import _random
592+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
593+
r = _random.Random()
594+
self.assertRaises(TypeError, pickle.dumps, r, proto)
595+
596+
@test.support.cpython_only
597+
def test_bug_42008(self):
598+
# _random.Random should call seed with first element of arg tuple
599+
import _random
600+
r1 = _random.Random()
601+
r1.seed(8675309)
602+
r2 = _random.Random(8675309)
603+
self.assertEqual(r1.random(), r2.random())
604+
605+
595606
class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
596607
gen = random.Random()
597608

@@ -846,10 +857,6 @@ def test_randbelow_without_getrandbits(self):
846857
maxsize+1, maxsize=maxsize
847858
)
848859
self.gen._randbelow_without_getrandbits(5640, maxsize=maxsize)
849-
# issue 33203: test that _randbelow returns zero on
850-
# n == 0 also in its getrandbits-independent branch.
851-
x = self.gen._randbelow_without_getrandbits(0, maxsize=maxsize)
852-
self.assertEqual(x, 0)
853860

854861
# This might be going too far to test a single line, but because of our
855862
# noble aim of achieving 100% test coverage we need to write a case in
@@ -1331,7 +1338,7 @@ def test__all__(self):
13311338
# tests validity but not completeness of the __all__ list
13321339
self.assertTrue(set(random.__all__) <= set(dir(random)))
13331340

1334-
@unittest.skipUnless(hasattr(os, "fork"), "fork() required")
1341+
@test.support.requires_fork()
13351342
def test_after_fork(self):
13361343
# Test the global Random instance gets reseeded in child
13371344
r, w = os.pipe()

vm/src/builtins/int.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ pub struct IntOptions {
794794
#[derive(FromArgs)]
795795
struct IntFromByteArgs {
796796
bytes: PyBytesInner,
797+
#[pyarg(any, default = "ArgByteOrder::Big")]
797798
byteorder: ArgByteOrder,
798799
#[pyarg(named, optional)]
799800
signed: OptionalArg<ArgIntoBool>,

0 commit comments

Comments
 (0)