Skip to content

Commit ec1622d

Browse files
bpo-33144: Fix choosing random.Random._randbelow implementation. (GH-6563)
random() takes precedence over getrandbits() if defined later in the class tree.
1 parent d54cfb1 commit ec1622d

File tree

2 files changed

+74
-26
lines changed

2 files changed

+74
-26
lines changed

Lib/random.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,16 @@ def __init_subclass__(cls, **kwargs):
102102
ranges.
103103
"""
104104

105-
if (cls.random is _random.Random.random) or (
106-
cls.getrandbits is not _random.Random.getrandbits):
107-
# The original random() builtin method has not been overridden
108-
# or a new getrandbits() was supplied.
109-
# The subclass can use the getrandbits-dependent implementation
110-
# of _randbelow().
111-
cls._randbelow = cls._randbelow_with_getrandbits
112-
else:
113-
# There's an overridden random() method but no new getrandbits(),
114-
# so the subclass can only use the getrandbits-independent
115-
# implementation of _randbelow().
116-
cls._randbelow = cls._randbelow_without_getrandbits
105+
for c in cls.__mro__:
106+
if '_randbelow' in c.__dict__:
107+
# just inherit it
108+
break
109+
if 'getrandbits' in c.__dict__:
110+
cls._randbelow = cls._randbelow_with_getrandbits
111+
break
112+
if 'random' in c.__dict__:
113+
cls._randbelow = cls._randbelow_without_getrandbits
114+
break
117115

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

Lib/test/test_random.py

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import time
66
import pickle
77
import warnings
8-
import logging
98
from functools import partial
109
from math import log, exp, pi, fsum, sin, factorial
1110
from test import support
@@ -940,6 +939,7 @@ def test_betavariate_return_zero(self, gammavariate_mock):
940939
gammavariate_mock.return_value = 0.0
941940
self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))
942941

942+
943943
class TestRandomSubclassing(unittest.TestCase):
944944
def test_random_subclass_with_kwargs(self):
945945
# SF bug #1486663 -- this used to erroneously raise a TypeError
@@ -958,30 +958,80 @@ def test_subclasses_overriding_methods(self):
958958
# randrange
959959
class SubClass1(random.Random):
960960
def random(self):
961-
return super().random()
961+
called.add('SubClass1.random')
962+
return random.Random.random(self)
962963

963964
def getrandbits(self, n):
964-
logging.getLogger('getrandbits').info('used getrandbits')
965-
return super().getrandbits(n)
966-
with self.assertLogs('getrandbits'):
967-
SubClass1().randrange(42)
965+
called.add('SubClass1.getrandbits')
966+
return random.Random.getrandbits(self, n)
967+
called = set()
968+
SubClass1().randrange(42)
969+
self.assertEqual(called, {'SubClass1.getrandbits'})
968970

969971
# subclass providing only random => can only use random for randrange
970972
class SubClass2(random.Random):
971973
def random(self):
972-
logging.getLogger('random').info('used random')
973-
return super().random()
974-
with self.assertLogs('random'):
975-
SubClass2().randrange(42)
974+
called.add('SubClass2.random')
975+
return random.Random.random(self)
976+
called = set()
977+
SubClass2().randrange(42)
978+
self.assertEqual(called, {'SubClass2.random'})
976979

977980
# subclass defining getrandbits to complement its inherited random
978981
# => can now rely on getrandbits for randrange again
979982
class SubClass3(SubClass2):
980983
def getrandbits(self, n):
981-
logging.getLogger('getrandbits').info('used getrandbits')
982-
return super().getrandbits(n)
983-
with self.assertLogs('getrandbits'):
984-
SubClass3().randrange(42)
984+
called.add('SubClass3.getrandbits')
985+
return random.Random.getrandbits(self, n)
986+
called = set()
987+
SubClass3().randrange(42)
988+
self.assertEqual(called, {'SubClass3.getrandbits'})
989+
990+
# subclass providing only random and inherited getrandbits
991+
# => random takes precedence
992+
class SubClass4(SubClass3):
993+
def random(self):
994+
called.add('SubClass4.random')
995+
return random.Random.random(self)
996+
called = set()
997+
SubClass4().randrange(42)
998+
self.assertEqual(called, {'SubClass4.random'})
999+
1000+
# Following subclasses don't define random or getrandbits directly,
1001+
# but inherit them from classes which are not subclasses of Random
1002+
class Mixin1:
1003+
def random(self):
1004+
called.add('Mixin1.random')
1005+
return random.Random.random(self)
1006+
class Mixin2:
1007+
def getrandbits(self, n):
1008+
called.add('Mixin2.getrandbits')
1009+
return random.Random.getrandbits(self, n)
1010+
1011+
class SubClass5(Mixin1, random.Random):
1012+
pass
1013+
called = set()
1014+
SubClass5().randrange(42)
1015+
self.assertEqual(called, {'Mixin1.random'})
1016+
1017+
class SubClass6(Mixin2, random.Random):
1018+
pass
1019+
called = set()
1020+
SubClass6().randrange(42)
1021+
self.assertEqual(called, {'Mixin2.getrandbits'})
1022+
1023+
class SubClass7(Mixin1, Mixin2, random.Random):
1024+
pass
1025+
called = set()
1026+
SubClass7().randrange(42)
1027+
self.assertEqual(called, {'Mixin1.random'})
1028+
1029+
class SubClass8(Mixin2, Mixin1, random.Random):
1030+
pass
1031+
called = set()
1032+
SubClass8().randrange(42)
1033+
self.assertEqual(called, {'Mixin2.getrandbits'})
1034+
9851035

9861036
class TestModule(unittest.TestCase):
9871037
def testMagicConstants(self):

0 commit comments

Comments
 (0)