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
This commit is contained in:
Jeong, YunWon
2023-03-25 17:06:50 +09:00
committed by GitHub
3 changed files with 71 additions and 89 deletions

82
Lib/random.py vendored
View File

@@ -167,15 +167,11 @@ class Random(_random.Random):
elif version == 2 and isinstance(a, (str, bytes, bytearray)):
if isinstance(a, str):
a = a.encode()
a = int.from_bytes(a + _sha512(a).digest(), 'big')
a = int.from_bytes(a + _sha512(a).digest())
elif not isinstance(a, (type(None), int, float, str, bytes, bytearray)):
_warn('Seeding based on hashing is deprecated\n'
'since Python 3.9 and will be removed in a subsequent '
'version. The only \n'
'supported seed types are: None, '
'int, float, str, bytes, and bytearray.',
DeprecationWarning, 2)
raise TypeError('The only supported seed types are: None,\n'
'int, float, str, bytes, and bytearray.')
super().seed(a)
self.gauss_next = None
@@ -250,10 +246,8 @@ class Random(_random.Random):
break
def _randbelow_with_getrandbits(self, n):
"Return a random int in the range [0,n). Returns 0 if n==0."
"Return a random int in the range [0,n). Defined for n > 0."
if not n:
return 0
getrandbits = self.getrandbits
k = n.bit_length() # don't use (n-1) here because n can be 1
r = getrandbits(k) # 0 <= r < 2**k
@@ -262,7 +256,7 @@ class Random(_random.Random):
return r
def _randbelow_without_getrandbits(self, n, maxsize=1<<BPF):
"""Return a random int in the range [0,n). Returns 0 if n==0.
"""Return a random int in the range [0,n). Defined for n > 0.
The implementation does not use getrandbits, but only random.
"""
@@ -273,8 +267,6 @@ class Random(_random.Random):
"enough bits to choose from a population range this large.\n"
"To remove the range limitation, add a getrandbits() method.")
return _floor(random() * n)
if n == 0:
return 0
rem = maxsize % n
limit = (maxsize - rem) / maxsize # int(limit * maxsize) % n == 0
r = random()
@@ -303,10 +295,10 @@ 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.
"""
@@ -387,37 +379,24 @@ class Random(_random.Random):
def choice(self, seq):
"""Choose a random element from a non-empty sequence."""
# raises IndexError if seq is empty
# As an accommodation for NumPy, we don't use "if not seq"
# because bool(numpy.array()) raises a ValueError.
if not len(seq):
raise IndexError('Cannot choose from an empty sequence')
return seq[self._randbelow(len(seq))]
def shuffle(self, x, random=None):
"""Shuffle list x in place, and return None.
def shuffle(self, x):
"""Shuffle list x in place, and return None."""
Optional argument random is a 0-argument function returning a
random float in [0.0, 1.0); if it is the default None, the
standard random.random will be used.
"""
if random is None:
randbelow = self._randbelow
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = randbelow(i + 1)
x[i], x[j] = x[j], x[i]
else:
_warn('The *random* parameter to shuffle() has been deprecated\n'
'since Python 3.9 and will be removed in a subsequent '
'version.',
DeprecationWarning, 2)
floor = _floor
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = floor(random() * (i + 1))
x[i], x[j] = x[j], x[i]
randbelow = self._randbelow
for i in reversed(range(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = randbelow(i + 1)
x[i], x[j] = x[j], x[i]
def sample(self, population, k, *, counts=None):
"""Chooses k unique random elements from a population sequence or set.
"""Chooses k unique random elements from a population sequence.
Returns a new list containing elements from the population while
leaving the original population unchanged. The resulting list is
@@ -470,13 +449,8 @@ class Random(_random.Random):
# causing them to eat more entropy than necessary.
if not isinstance(population, _Sequence):
if isinstance(population, _Set):
_warn('Sampling from a set deprecated\n'
'since Python 3.9 and will be removed in a subsequent version.',
DeprecationWarning, 2)
population = tuple(population)
else:
raise TypeError("Population must be a sequence. For dicts or sets, use sorted(d).")
raise TypeError("Population must be a sequence. "
"For dicts or sets, use sorted(d).")
n = len(population)
if counts is not None:
cum_counts = list(_accumulate(counts))
@@ -580,7 +554,7 @@ class Random(_random.Random):
low, high = high, low
return low + (high - low) * _sqrt(u * c)
def normalvariate(self, mu, sigma):
def normalvariate(self, mu=0.0, sigma=1.0):
"""Normal distribution.
mu is the mean, and sigma is the standard deviation.
@@ -601,7 +575,7 @@ class Random(_random.Random):
break
return mu + z * sigma
def gauss(self, mu, sigma):
def gauss(self, mu=0.0, sigma=1.0):
"""Gaussian distribution.
mu is the mean, and sigma is the standard deviation. This is
@@ -833,15 +807,15 @@ class SystemRandom(Random):
"""
def random(self):
"""Get the next random number in the range [0.0, 1.0)."""
return (int.from_bytes(_urandom(7), 'big') >> 3) * RECIP_BPF
"""Get the next random number in the range 0.0 <= X < 1.0."""
return (int.from_bytes(_urandom(7)) >> 3) * RECIP_BPF
def getrandbits(self, k):
"""getrandbits(k) -> x. Generates an int with k random bits."""
if k < 0:
raise ValueError('number of bits must be non-negative')
numbytes = (k + 7) // 8 # bits / 8 and rounded up
x = int.from_bytes(_urandom(numbytes), 'big')
x = int.from_bytes(_urandom(numbytes))
return x >> (numbytes * 8 - k) # trim excess bits
def randbytes(self, n):

View File

@@ -52,12 +52,11 @@ class TestBasicOps:
self.gen.seed(arg)
for arg in [1+2j, tuple('abc'), MySeed()]:
with self.assertWarns(DeprecationWarning):
with self.assertRaises(TypeError):
self.gen.seed(arg)
for arg in [list(range(3)), dict(one=1)]:
with self.assertWarns(DeprecationWarning):
self.assertRaises(TypeError, self.gen.seed, arg)
self.assertRaises(TypeError, self.gen.seed, arg)
self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4)
self.assertRaises(TypeError, type(self.gen), [])
@@ -110,15 +109,6 @@ class TestBasicOps:
self.assertTrue(lst != shuffled_lst)
self.assertRaises(TypeError, shuffle, (1, 2, 3))
def test_shuffle_random_argument(self):
# Test random argument to shuffle.
shuffle = self.gen.shuffle
mock_random = unittest.mock.Mock(return_value=0.5)
seq = bytearray(b'abcdefghijk')
with self.assertWarns(DeprecationWarning):
shuffle(seq, mock_random)
mock_random.assert_called_with()
def test_choice(self):
choice = self.gen.choice
with self.assertRaises(IndexError):
@@ -126,6 +116,21 @@ class TestBasicOps:
self.assertEqual(choice([50]), 50)
self.assertIn(choice([25, 75]), [25, 75])
def test_choice_with_numpy(self):
# Accommodation for NumPy arrays which have disabled __bool__().
# See: https://github.com/python/cpython/issues/100805
choice = self.gen.choice
class NA(list):
"Simulate numpy.array() behavior"
def __bool__(self):
raise RuntimeError
with self.assertRaises(IndexError):
choice(NA([]))
self.assertEqual(choice(NA([50])), 50)
self.assertIn(choice(NA([25, 75])), [25, 75])
def test_sample(self):
# For the entire allowable range of 0 <= k <= N, validate that
# the sample is of the correct length and contains only unique items
@@ -169,7 +174,7 @@ class TestBasicOps:
self.assertRaises(TypeError, self.gen.sample, dict.fromkeys('abcdef'), 2)
def test_sample_on_sets(self):
with self.assertWarns(DeprecationWarning):
with self.assertRaises(TypeError):
population = {10, 20, 30, 40, 50, 60, 70}
self.gen.sample(population, k=5)
@@ -391,23 +396,6 @@ class TestBasicOps:
restoredseq = [newgen.random() for i in range(10)]
self.assertEqual(origseq, restoredseq)
@test.support.cpython_only
def test_bug_41052(self):
# _random.Random should not be allowed to serialization
import _random
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
r = _random.Random()
self.assertRaises(TypeError, pickle.dumps, r, proto)
@test.support.cpython_only
def test_bug_42008(self):
# _random.Random should call seed with first element of arg tuple
import _random
r1 = _random.Random()
r1.seed(8675309)
r2 = _random.Random(8675309)
self.assertEqual(r1.random(), r2.random())
# TODO: RUSTPYTHON AttributeError: 'super' object has no attribute 'getstate'
@unittest.expectedFailure
def test_bug_1727780(self):
@@ -445,6 +433,10 @@ class TestBasicOps:
self.assertRaises(ValueError, self.gen.randbytes, -1)
self.assertRaises(TypeError, self.gen.randbytes, 1.0)
def test_mu_sigma_default_args(self):
self.assertIsInstance(self.gen.normalvariate(), float)
self.assertIsInstance(self.gen.gauss(), float)
try:
random.SystemRandom().random()
@@ -592,6 +584,25 @@ class SystemRandom_TestBasicOps(TestBasicOps, unittest.TestCase):
self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion
class TestRawMersenneTwister(unittest.TestCase):
@test.support.cpython_only
def test_bug_41052(self):
# _random.Random should not be allowed to serialization
import _random
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
r = _random.Random()
self.assertRaises(TypeError, pickle.dumps, r, proto)
@test.support.cpython_only
def test_bug_42008(self):
# _random.Random should call seed with first element of arg tuple
import _random
r1 = _random.Random()
r1.seed(8675309)
r2 = _random.Random(8675309)
self.assertEqual(r1.random(), r2.random())
class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
gen = random.Random()
@@ -846,10 +857,6 @@ class MersenneTwister_TestBasicOps(TestBasicOps, unittest.TestCase):
maxsize+1, maxsize=maxsize
)
self.gen._randbelow_without_getrandbits(5640, maxsize=maxsize)
# issue 33203: test that _randbelow returns zero on
# n == 0 also in its getrandbits-independent branch.
x = self.gen._randbelow_without_getrandbits(0, maxsize=maxsize)
self.assertEqual(x, 0)
# This might be going too far to test a single line, but because of our
# noble aim of achieving 100% test coverage we need to write a case in
@@ -1331,7 +1338,7 @@ class TestModule(unittest.TestCase):
# tests validity but not completeness of the __all__ list
self.assertTrue(set(random.__all__) <= set(dir(random)))
@unittest.skipUnless(hasattr(os, "fork"), "fork() required")
@test.support.requires_fork()
def test_after_fork(self):
# Test the global Random instance gets reseeded in child
r, w = os.pipe()

View File

@@ -794,6 +794,7 @@ pub struct IntOptions {
#[derive(FromArgs)]
struct IntFromByteArgs {
bytes: PyBytesInner,
#[pyarg(any, default = "ArgByteOrder::Big")]
byteorder: ArgByteOrder,
#[pyarg(named, optional)]
signed: OptionalArg<ArgIntoBool>,