bpo-33144: Fix choosing random.Random._randbelow implementation. (GH-6563)

random() takes precedence over getrandbits() if defined later
in the class tree.
This commit is contained in:
Serhiy Storchaka 2018-05-08 15:45:15 +03:00 committed by GitHub
parent d54cfb160c
commit ec1622d56c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 26 deletions

View File

@ -102,18 +102,16 @@ class Random(_random.Random):
ranges. ranges.
""" """
if (cls.random is _random.Random.random) or ( for c in cls.__mro__:
cls.getrandbits is not _random.Random.getrandbits): if '_randbelow' in c.__dict__:
# The original random() builtin method has not been overridden # just inherit it
# or a new getrandbits() was supplied. break
# The subclass can use the getrandbits-dependent implementation if 'getrandbits' in c.__dict__:
# of _randbelow(). cls._randbelow = cls._randbelow_with_getrandbits
cls._randbelow = cls._randbelow_with_getrandbits break
else: if 'random' in c.__dict__:
# There's an overridden random() method but no new getrandbits(), cls._randbelow = cls._randbelow_without_getrandbits
# so the subclass can only use the getrandbits-independent break
# implementation of _randbelow().
cls._randbelow = cls._randbelow_without_getrandbits
def seed(self, a=None, version=2): def seed(self, a=None, version=2):
"""Initialize internal state from hashable object. """Initialize internal state from hashable object.

View File

@ -5,7 +5,6 @@ import os
import time import time
import pickle import pickle
import warnings import warnings
import logging
from functools import partial from functools import partial
from math import log, exp, pi, fsum, sin, factorial from math import log, exp, pi, fsum, sin, factorial
from test import support from test import support
@ -940,6 +939,7 @@ class TestDistributions(unittest.TestCase):
gammavariate_mock.return_value = 0.0 gammavariate_mock.return_value = 0.0
self.assertEqual(0.0, random.betavariate(2.71828, 3.14159)) self.assertEqual(0.0, random.betavariate(2.71828, 3.14159))
class TestRandomSubclassing(unittest.TestCase): class TestRandomSubclassing(unittest.TestCase):
def test_random_subclass_with_kwargs(self): def test_random_subclass_with_kwargs(self):
# SF bug #1486663 -- this used to erroneously raise a TypeError # SF bug #1486663 -- this used to erroneously raise a TypeError
@ -958,30 +958,80 @@ class TestRandomSubclassing(unittest.TestCase):
# randrange # randrange
class SubClass1(random.Random): class SubClass1(random.Random):
def random(self): def random(self):
return super().random() called.add('SubClass1.random')
return random.Random.random(self)
def getrandbits(self, n): def getrandbits(self, n):
logging.getLogger('getrandbits').info('used getrandbits') called.add('SubClass1.getrandbits')
return super().getrandbits(n) return random.Random.getrandbits(self, n)
with self.assertLogs('getrandbits'): called = set()
SubClass1().randrange(42) SubClass1().randrange(42)
self.assertEqual(called, {'SubClass1.getrandbits'})
# subclass providing only random => can only use random for randrange # subclass providing only random => can only use random for randrange
class SubClass2(random.Random): class SubClass2(random.Random):
def random(self): def random(self):
logging.getLogger('random').info('used random') called.add('SubClass2.random')
return super().random() return random.Random.random(self)
with self.assertLogs('random'): called = set()
SubClass2().randrange(42) SubClass2().randrange(42)
self.assertEqual(called, {'SubClass2.random'})
# subclass defining getrandbits to complement its inherited random # subclass defining getrandbits to complement its inherited random
# => can now rely on getrandbits for randrange again # => can now rely on getrandbits for randrange again
class SubClass3(SubClass2): class SubClass3(SubClass2):
def getrandbits(self, n): def getrandbits(self, n):
logging.getLogger('getrandbits').info('used getrandbits') called.add('SubClass3.getrandbits')
return super().getrandbits(n) return random.Random.getrandbits(self, n)
with self.assertLogs('getrandbits'): called = set()
SubClass3().randrange(42) SubClass3().randrange(42)
self.assertEqual(called, {'SubClass3.getrandbits'})
# subclass providing only random and inherited getrandbits
# => random takes precedence
class SubClass4(SubClass3):
def random(self):
called.add('SubClass4.random')
return random.Random.random(self)
called = set()
SubClass4().randrange(42)
self.assertEqual(called, {'SubClass4.random'})
# Following subclasses don't define random or getrandbits directly,
# but inherit them from classes which are not subclasses of Random
class Mixin1:
def random(self):
called.add('Mixin1.random')
return random.Random.random(self)
class Mixin2:
def getrandbits(self, n):
called.add('Mixin2.getrandbits')
return random.Random.getrandbits(self, n)
class SubClass5(Mixin1, random.Random):
pass
called = set()
SubClass5().randrange(42)
self.assertEqual(called, {'Mixin1.random'})
class SubClass6(Mixin2, random.Random):
pass
called = set()
SubClass6().randrange(42)
self.assertEqual(called, {'Mixin2.getrandbits'})
class SubClass7(Mixin1, Mixin2, random.Random):
pass
called = set()
SubClass7().randrange(42)
self.assertEqual(called, {'Mixin1.random'})
class SubClass8(Mixin2, Mixin1, random.Random):
pass
called = set()
SubClass8().randrange(42)
self.assertEqual(called, {'Mixin2.getrandbits'})
class TestModule(unittest.TestCase): class TestModule(unittest.TestCase):
def testMagicConstants(self): def testMagicConstants(self):