diff -r bc322469a7a8 Lib/random.py --- a/Lib/random.py Mon Dec 10 20:22:55 2012 -0800 +++ b/Lib/random.py Wed Dec 12 21:32:52 2012 +0200 @@ -31,8 +31,7 @@ * The period is 2**19937-1. * It is one of the most extensively tested generators in existence. -* The random() method is implemented in C, executes in a single Python step, - and is, therefore, threadsafe. +* random() and getrandbits() method are threadsafe. """ @@ -43,6 +42,11 @@ from os import urandom as _urandom from collections.abc import Set as _Set, Sequence as _Sequence from hashlib import sha512 as _sha512 +from struct import pack as _pack, unpack as _unpack +try: + from threading import RLock as _RLock +except ImportError: + from dummy_threading import RLock as _RLock __all__ = ["Random","seed","random","uniform","randint","choice","sample", "randrange","shuffle","normalvariate","lognormvariate", @@ -59,13 +63,160 @@ RECIP_BPF = 2**-BPF +class _Random: + """Mersenne Twister core generator.""" + # See more comments in Modules/_randommodule.c. + + def __new__(cls, *args, **kwargs): + if cls == _Random and kwargs: + raise TypeError('Random() does not take keyword arguments') + self = super(_Random, cls).__new__(cls) + self.__lock = _RLock() + self.seed(*args) + return self + + def random(self): + """Generates a float in the interval [0, 1).""" + with self.__lock: + genrand_int32 = self.__genrand_int32 + a = genrand_int32() >> 5 # 27 bits + b = genrand_int32() >> 6 # 26 bits + return ((a << 26) + b) * (1 / 0x20000000000000) # 1 / 2**53 + + def seed(self, n=None): + """Initialize internal state from hashable object. + + None or no argument seeds from current time. + + If *n* is not an int, all bits are used, otherwise the hash() of *n* + is used instead. + """ + with self.__lock: + if n is None: + self.__init_genrand(int(time.time())) + return None + if isinstance(n, int): + n = abs(n) + else: + n = hash(n) + if n < 0: + n, = _unpack('N', _pack('n', n)) + # Now split n into 32-bit chunks, from the right. + bits = n.bit_length() + keysize = ((bits - 1) >> 5) + 1 if bits else 1 + key = _unpack('<%dI' % keysize, n.to_bytes(keysize * 4, 'little')) + self.__init_by_array(key) + + def getstate(self): + """Return tuple containing the current state.; + can be passed to setstate() later.""" + with self.__lock: + return tuple(self.__state) + (self.__index,) + + def setstate(self, state): + """Restore internal state from tuple returned by getstate().""" + with self.__lock: + if not isinstance(state, tuple): + raise TypeError('state vector must be a tuple') + if len(state) != len(self.__state) + 1: + raise ValueError('state vector is the wrong size') + self.__state = [x & 0xffffffff for x in state[:-1]] + self.__index = int(state[-1]) + + def getrandbits(self, k): + """Generates an int with k random bits.""" + with self.__lock: + if k <= 0: + raise ValueError('number of bits must be greater than zero') + + if k <= 32: + return self.__genrand_int32() >> (32 - k) + + # Fill-out whole words, byte-by-byte to avoid endianness issues + genrand_int32 = self.__genrand_int32 + words = [genrand_int32() for i in range(((k - 1) >> 5) + 1)] + k &= 31 + if k: + words[-1] >>= 32 - k + # little endian order to match bytearray assignment order + return int.from_bytes(_pack('<%dI' % len(words), *words), 'little') + + # Generates a random number on [0,0xffffffff]-interval + def __genrand_int32(self): + try: + y = self.__state[self.__index] + self.__index += 1 + except IndexError: + mt = self.__state + N = len(mt) + # generate N words at one time + for kk in range(N): + y = (mt[kk] & 0x80000000) | (mt[kk + 1 - N] & 0x7fffffff) + x = mt[kk + 397 - N] ^ (y >> 1) + if y & 1: + x ^= 0x9908b0df + mt[kk] = x + y = mt[0] + self.__index = 1 + + y ^= (y >> 11) + y ^= (y << 7) & 0x9d2c5680 + y ^= (y << 15) & 0xefc60000 + y ^= (y >> 18) + return y + + # Initialize by an integer seed + def __init_genrand(self, s): + x = s & 0xffffffff + mt = [x] + append = mt.append + for i in range(1, 624): + x ^= x >> 30 + x = (1812433253 * x + i) & 0xffffffff + append(x) + # See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. + # In the previous versions, MSBs of the seed affect + # only MSBs of the array mt[]. + # 2002/01/09 modified by Makoto Matsumoto + self.__state = mt + self.__index = len(mt) + + # Initialize by an integer array + def __init_by_array(self, init_key): + self.__init_genrand(19650218) + mt = self.__state + N = len(self.__state) + i = 1 + j = 0 + x = mt[0] + for k in range(max(N, len(init_key))): + x ^= x >> 30 + mt[i] = x = ((mt[i] ^ (x * 1664525)) + init_key[j] + j) & 0xffffffff + i += 1 + if i >= N: + i = 1 + j += 1 + if j >= len(init_key): + j = 0 + for k in range(N - 1): + x ^= x >> 30 + mt[i] = x = ((mt[i] ^ (x * 1566083941)) - i) & 0xffffffff + i += 1 + if i >= N: + i = 1 + mt[0] = 0x80000000 # MSB is 1; assuring non-zero initial array + +try: + from _random import Random as _Random +except ImportError: + pass + + # Translated by Guido van Rossum from C source provided by # Adrian Baddeley. Adapted by Raymond Hettinger for use with # the Mersenne Twister and os.urandom() core generators. -import _random - -class Random(_random.Random): +class Random(_Random): """Random number generator base class used by bound module functions. Used to instantiate instances of Random to get generators that don't diff -r bc322469a7a8 Lib/test/test_random.py --- a/Lib/test/test_random.py Mon Dec 10 20:22:55 2012 -0800 +++ b/Lib/test/test_random.py Wed Dec 12 21:32:52 2012 +0200 @@ -1,13 +1,16 @@ #!/usr/bin/env python3 import unittest -import random import time import pickle +import sys import warnings from math import log, exp, pi, fsum, sin from test import support +py_random = support.import_fresh_module('random', blocked=['_random']) +c_random = support.import_fresh_module('random', fresh=['_random']) + class TestBasicOps(unittest.TestCase): # Superclass with tests common to all generators. # Subclasses must arrange for self.gen to retrieve the Random instance @@ -148,11 +151,23 @@ self.assertEqual(y1, y2) def test_pickling(self): - state = pickle.dumps(self.gen) - origseq = [self.gen.random() for i in range(10)] - newgen = pickle.loads(state) - restoredseq = [newgen.random() for i in range(10)] - self.assertEqual(origseq, restoredseq) + with support.CleanImport('random'): + sys.modules['random'] = self.module + state = pickle.dumps(self.gen) + origseq = [self.gen.random() for i in range(10)] + newgen = pickle.loads(state) + restoredseq = [newgen.random() for i in range(10)] + self.assertEqual(origseq, restoredseq) + + if c_random: + # Test interchangeability + if self.module != c_random: + sys.modules['random'] = c_random + else: + sys.modules['random'] = py_random + newgen = pickle.loads(state) + restoredseq = [newgen.random() for i in range(10)] + self.assertEqual(origseq, restoredseq) def test_bug_1727780(self): # verify that version-2-pickles can be loaded @@ -175,9 +190,8 @@ k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n)) self.assertTrue(0.30 < k/n < .37, (k/n)) + class SystemRandom_TestBasicOps(TestBasicOps): - gen = random.SystemRandom() - def test_autoseed(self): # Doesn't need to do anything except not fail self.gen.seed() @@ -271,10 +285,17 @@ self.assertEqual(k, numbits) # note the stronger assertion self.assertTrue(2**k > n > 2**(k-1)) # note the stronger assertion +class SystemRandom_TestBasicOpsPython(SystemRandom_TestBasicOps): + module = py_random + gen = module.SystemRandom() + +@unittest.skipUnless(c_random, 'requires _random') +class SystemRandom_TestBasicOpsC(SystemRandom_TestBasicOps): + module = c_random + gen = module.SystemRandom() + class MersenneTwister_TestBasicOps(TestBasicOps): - gen = random.Random() - def test_guaranteed_stable(self): # These sequences are guaranteed to stay the same across versions of python self.gen.seed(3456147, version=1) @@ -446,6 +467,16 @@ self.assertTrue(stop < x <= start) self.assertEqual((x+stop)%step, 0) +class MersenneTwister_TestBasicOpsPython(MersenneTwister_TestBasicOps): + module = py_random + gen = module.Random() + +@unittest.skipUnless(c_random, 'requires _random') +class MersenneTwister_TestBasicOpsC(MersenneTwister_TestBasicOps): + module = c_random + gen = module.Random() + + def gamma(z, sqrt2pi=(2.0*pi)**0.5): # Reflection to right half of complex plane if z < 0.5: @@ -467,7 +498,7 @@ class TestDistributions(unittest.TestCase): def test_zeroinputs(self): # Verify that distributions can handle a series of zero inputs' - g = random.Random() + g = self.module.Random() x = [g.random() for i in range(50)] + [0.0]*5 g.random = x[:].pop; g.uniform(1,10) g.random = x[:].pop; g.paretovariate(1.0) @@ -486,7 +517,7 @@ def test_avg_std(self): # Use integration to test distribution average and standard deviation. # Only works for distributions which do not consume variates in pairs - g = random.Random() + g = self.module.Random() N = 5000 x = [i/float(N) for i in range(1,N)] for variate, args, mu, sigmasqrd in [ @@ -512,36 +543,53 @@ self.assertAlmostEqual(s1/N, mu, places=2) self.assertAlmostEqual(s2/(N-1), sigmasqrd, places=2) +class TestDistributionsPython(TestDistributions): + module = py_random + +@unittest.skipUnless(c_random, 'requires _random') +class TestDistributionsC(TestDistributions): + module = c_random + + class TestModule(unittest.TestCase): def testMagicConstants(self): - self.assertAlmostEqual(random.NV_MAGICCONST, 1.71552776992141) - self.assertAlmostEqual(random.TWOPI, 6.28318530718) - self.assertAlmostEqual(random.LOG4, 1.38629436111989) - self.assertAlmostEqual(random.SG_MAGICCONST, 2.50407739677627) + self.assertAlmostEqual(self.module.NV_MAGICCONST, 1.71552776992141) + self.assertAlmostEqual(self.module.TWOPI, 6.28318530718) + self.assertAlmostEqual(self.module.LOG4, 1.38629436111989) + self.assertAlmostEqual(self.module.SG_MAGICCONST, 2.50407739677627) def test__all__(self): # tests validity but not completeness of the __all__ list - self.assertTrue(set(random.__all__) <= set(dir(random))) + self.assertTrue(set(self.module.__all__) <= set(dir(self.module))) def test_random_subclass_with_kwargs(self): # SF bug #1486663 -- this used to erroneously raise a TypeError - class Subclass(random.Random): + module = self.module + class Subclass(module.Random): def __init__(self, newarg=None): - random.Random.__init__(self) + module.Random.__init__(self) Subclass(newarg=1) +class TestModulePython(TestModule): + module = py_random + +@unittest.skipUnless(c_random, 'requires _random') +class TestModuleC(TestModule): + module = c_random + def test_main(verbose=None): - testclasses = [MersenneTwister_TestBasicOps, - TestDistributions, - TestModule] + testclasses = [ + MersenneTwister_TestBasicOpsPython, MersenneTwister_TestBasicOpsC, + TestDistributionsPython, TestDistributionsC, + TestModulePython, TestModuleC] try: - random.SystemRandom().random() + py_random.SystemRandom().random() except NotImplementedError: pass else: - testclasses.append(SystemRandom_TestBasicOps) + testclasses.extend([SystemRandom_TestBasicOpsPython, SystemRandom_TestBasicOpsC]) support.run_unittest(*testclasses)