Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-40346: Random subclass randbytes uses getrandbits #19700

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

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 2 additions & 0 deletions Doc/library/random.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ basic generator of your own devising: in that case, override the :meth:`~Random.
:meth:`~Random.seed`, :meth:`~Random.getstate`, and :meth:`~Random.setstate` methods.
Optionally, a new generator can supply a :meth:`~Random.getrandbits` method --- this
allows :meth:`randrange` to produce selections over an arbitrarily large range.
In subclasses, :meth:`randbytes` is implemented with the
:meth:`~Random.getrandbits` method, unless :meth:`randbytes` is overriden.

The :mod:`random` module also provides the :class:`SystemRandom` class which
uses the system function :func:`os.urandom` to generate random numbers
Expand Down
22 changes: 22 additions & 0 deletions Lib/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ def __init_subclass__(cls, /, **kwargs):
cls._randbelow = cls._randbelow_without_getrandbits
break

if (cls.randbytes == _random.Random.randbytes
and cls.getrandbits != _random.Random.getrandbits):
# Subclasses of random.Random implement randbytes()
# using getrandbits() if getrandbits() is overriden.
cls.randbytes = cls._randbytes_getrandbits

def seed(self, a=None, version=2):
"""Initialize internal state from a seed.

Expand Down Expand Up @@ -189,6 +195,13 @@ def setstate(self, state):
"Random.setstate() of version %s" %
(version, self.VERSION))

## -------------------- bytes -----------------------------

# Implementation used by Random and SystemRandom subclasses
def _randbytes_getrandbits (self, n):
"""Generate n random bytes."""
return self.getrandbits(n * 8).to_bytes(n, 'little')

## ---- Methods below this point do not need to be overridden when
## ---- subclassing for the purpose of using a different core generator.

Expand Down Expand Up @@ -732,6 +745,15 @@ class SystemRandom(Random):
Not available on all systems (see os.urandom() for details).
"""

def __init_subclass__(cls, /, **kwargs):
super().__init_subclass__(**kwargs)

if (cls.randbytes == SystemRandom.randbytes
and cls.getrandbits != SystemRandom.getrandbits):
# Subclasses of random.SystemRandom implement randbytes()
# using getrandbits() if getrandbits() is overriden.
cls.randbytes = cls._randbytes_getrandbits

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
Expand Down
59 changes: 59 additions & 0 deletions Lib/test/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,65 @@ def test_randbytes(self):
self.assertRaises(ValueError, self.gen.randbytes, -1)
self.assertRaises(TypeError, self.gen.randbytes, 1.0)

def test_randbytes_subclass(self):
# For random.Random and random.SystemRandom subclasses,
# randbytes() is implemented with getrandbits()

# Subclass implements getrandbits() but not randbytes()
class Subclass(type(self.gen)):
def __init__(self):
super().__init__()
self.getrandbits_calls = []

def getrandbits(self, n):
self.getrandbits_calls.append(n)
return 0

subclass = Subclass()
for n in range(10):
self.assertEqual(subclass.randbytes(n), b'\0' * n)
self.assertEqual(subclass.getrandbits_calls,
[n * 8 for n in range(10)])

# SubSubclass override explicitly randbytes():
# getrandbits() implemented in Subclass is not called.
class SubSubclass(Subclass):
def __init__(self):
super().__init__()
self.randbytes_calls = []

def randbytes(self, n):
self.randbytes_calls.append(n)
return b'\x04' * n

subsubclass = SubSubclass()
for n in range(10):
self.assertEqual(subsubclass.randbytes(n), b'\x04' * n)
self.assertEqual(subsubclass.randbytes_calls, list(range(10)))
self.assertEqual(subsubclass.getrandbits_calls, [])

# Subclass2 implements getrandbits() and randbytes():
# randbytes() doesn't use getrandbits().
class Subclass2(type(self.gen)):
def __init__(self):
super().__init__()
self.getrandbits_calls = []

def getrandbits(self, n):
self.getrandbits_calls.append(n)
return 0

randbytes_calls = []
def randbytes(self, n):
self.randbytes_calls.append(n)
return b'\x07' * n

subclass2 = Subclass2()
for n in range(10):
self.assertEqual(subclass2.randbytes(n), b'\x07' * n)
self.assertEqual(subclass2.randbytes_calls, list(range(10)))
self.assertEqual(subclass2.getrandbits_calls, [])


try:
random.SystemRandom().random()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Subclasses of the :class:`random.Random` and :class:`random.SystemRandom`
classes now get a ``randbytes()`` method implementation which uses the
``getrandbits()`` method.