mirror of
https://github.com/python/cpython.git
synced 2024-11-24 02:15:30 +08:00
Teach the random module about os.urandom().
* Use it for seeding when it is available. * Provide an alternate generator based on it.
This commit is contained in:
parent
cd97da3b1d
commit
356a4599ac
@ -61,7 +61,10 @@ Bookkeeping functions:
|
||||
Optional argument \var{x} can be any hashable object.
|
||||
If \var{x} is omitted or \code{None}, current system time is used;
|
||||
current system time is also used to initialize the generator when the
|
||||
module is first imported.
|
||||
module is first imported. If hardware random sources are available,
|
||||
they are used instead of the system time (see the \function{os.urandom()}
|
||||
function for details on availability). \versionchanged[formerly,
|
||||
hardward sources were not used]{2.4}
|
||||
If \var{x} is not \code{None} or an int or long,
|
||||
\code{hash(\var{x})} is used instead.
|
||||
If \var{x} is an int or long, \var{x} is used directly.
|
||||
@ -227,7 +230,7 @@ these equations can be found in any statistics text.
|
||||
\var{beta} is the shape parameter.
|
||||
\end{funcdesc}
|
||||
|
||||
Alternative Generator
|
||||
Alternative Generators
|
||||
|
||||
\begin{classdesc}{WichmannHill}{\optional{seed}}
|
||||
Class that implements the Wichmann-Hill algorithm as the core generator.
|
||||
@ -246,6 +249,17 @@ require care that two independent random sequences do not overlap.
|
||||
yield no more than about 2**24 distinct internal states in all.
|
||||
\end{funcdesc}
|
||||
|
||||
\begin{classdesc}{HardwareRandom}{\optional{seed}}
|
||||
Class that uses the \function{os.urandom()} function for generating
|
||||
random numbers from hardware. Not available on all systems.
|
||||
Does not rely on software state and sequences are not reproducible.
|
||||
Accordingly, the \method{seed()} and \method{jumpahead()} methods
|
||||
have no effect and are ignored. The \method{getstate()} and
|
||||
\method{setstate()} methods raise \exception{NotImplementedError} if
|
||||
called.
|
||||
\versionadded{2.4}
|
||||
\end{classdesc}
|
||||
|
||||
\begin{seealso}
|
||||
\seetext{M. Matsumoto and T. Nishimura, ``Mersenne Twister: A
|
||||
623-dimensionally equidistributed uniform pseudorandom
|
||||
|
@ -49,7 +49,8 @@ __all__ = ["Random","seed","random","uniform","randint","choice","sample",
|
||||
"randrange","shuffle","normalvariate","lognormvariate",
|
||||
"expovariate","vonmisesvariate","gammavariate",
|
||||
"gauss","betavariate","paretovariate","weibullvariate",
|
||||
"getstate","setstate","jumpahead", "WichmannHill", "getrandbits"]
|
||||
"getstate","setstate","jumpahead", "WichmannHill", "getrandbits",
|
||||
"HardwareRandom"]
|
||||
|
||||
NV_MAGICCONST = 4 * _exp(-0.5)/_sqrt(2.0)
|
||||
TWOPI = 2.0*_pi
|
||||
@ -57,6 +58,15 @@ LOG4 = _log(4.0)
|
||||
SG_MAGICCONST = 1.0 + _log(4.5)
|
||||
BPF = 53 # Number of bits in a float
|
||||
|
||||
try:
|
||||
from os import urandom as _urandom
|
||||
from binascii import hexlify as _hexlify
|
||||
except ImportError:
|
||||
_urandom = None
|
||||
else:
|
||||
_tofloat = 2.0 ** (-7*8) # converts 7 byte integers to floats
|
||||
|
||||
|
||||
# Translated by Guido van Rossum from C source provided by
|
||||
# Adrian Baddeley. Adapted by Raymond Hettinger for use with
|
||||
# the Mersenne Twister core generator.
|
||||
@ -94,14 +104,19 @@ class Random(_random.Random):
|
||||
def seed(self, a=None):
|
||||
"""Initialize internal state from hashable object.
|
||||
|
||||
None or no argument seeds from current time.
|
||||
None or no argument seeds from current time or from a hardware
|
||||
randomness source if available.
|
||||
|
||||
If a is not None or an int or long, hash(a) is used instead.
|
||||
"""
|
||||
|
||||
if a is None:
|
||||
import time
|
||||
a = long(time.time() * 256) # use fractional seconds
|
||||
if _urandom is None:
|
||||
import time
|
||||
a = long(time.time() * 256) # use fractional seconds
|
||||
else:
|
||||
a = long(_hexlify(_urandom(16)), 16)
|
||||
|
||||
super(Random, self).seed(a)
|
||||
self.gauss_next = None
|
||||
|
||||
@ -593,7 +608,8 @@ class WichmannHill(Random):
|
||||
def seed(self, a=None):
|
||||
"""Initialize internal state from hashable object.
|
||||
|
||||
None or no argument seeds from current time.
|
||||
None or no argument seeds from current time or from a hardware
|
||||
randomness source if available.
|
||||
|
||||
If a is not None or an int or long, hash(a) is used instead.
|
||||
|
||||
@ -604,9 +620,11 @@ class WichmannHill(Random):
|
||||
"""
|
||||
|
||||
if a is None:
|
||||
# Initialize from current time
|
||||
import time
|
||||
a = long(time.time() * 256)
|
||||
if _urandom is None:
|
||||
import time
|
||||
a = long(time.time() * 256) # use fractional seconds
|
||||
else:
|
||||
a = long(_hexlify(_urandom(16)), 16)
|
||||
|
||||
if not isinstance(a, (int, long)):
|
||||
a = hash(a)
|
||||
@ -731,6 +749,42 @@ class WichmannHill(Random):
|
||||
z = (z + a) % 256 or 1
|
||||
self.__whseed(x, y, z)
|
||||
|
||||
## -------------------- Hardware Random Source -------------------
|
||||
|
||||
class HardwareRandom(Random):
|
||||
"""Alternate random number generator using hardware sources.
|
||||
|
||||
Not available on all systems (see os.urandom() for details).
|
||||
"""
|
||||
|
||||
def random(self):
|
||||
"""Get the next random number in the range [0.0, 1.0)."""
|
||||
if _urandom is None:
|
||||
raise NotImplementedError('Cannot find hardware entropy source')
|
||||
return long(_hexlify(_urandom(7)), 16) * _tofloat
|
||||
|
||||
def getrandbits(self, k):
|
||||
"""getrandbits(k) -> x. Generates a long int with k random bits."""
|
||||
if _urandom is None:
|
||||
raise NotImplementedError('Cannot find hardware entropy source')
|
||||
if k <= 0:
|
||||
raise ValueError('number of bits must be greater than zero')
|
||||
if k != int(k):
|
||||
raise TypeError('number of bits should be an integer')
|
||||
bytes = (k + 7) // 8 # bits / 8 and rounded up
|
||||
x = long(_hexlify(_urandom(bytes)), 16)
|
||||
return x >> (bytes * 8 - k) # trim excess bits
|
||||
|
||||
def _stub(self, *args, **kwds):
|
||||
"Stub method. Not used for a hardware random number generator."
|
||||
return None
|
||||
seed = jumpahead = _stub
|
||||
|
||||
def _notimplemented(self, *args, **kwds):
|
||||
"Method should not be called for a hardware random number generator."
|
||||
raise NotImplementedError('Hardware entropy source does not have state.')
|
||||
getstate = setstate = _notimplemented
|
||||
|
||||
## -------------------- test program --------------------
|
||||
|
||||
def _test_generator(n, func, args):
|
||||
|
@ -164,6 +164,107 @@ class WichmannHill_TestBasicOps(TestBasicOps):
|
||||
self.assertRaises(UserWarning, self.gen.randrange, 2**60)
|
||||
warnings.filters[:] = oldfilters
|
||||
|
||||
class HardwareRandom_TestBasicOps(TestBasicOps):
|
||||
gen = random.HardwareRandom()
|
||||
|
||||
def test_autoseed(self):
|
||||
# Doesn't need to do anything except not fail
|
||||
self.gen.seed()
|
||||
|
||||
def test_saverestore(self):
|
||||
self.assertRaises(NotImplementedError, self.gen.getstate)
|
||||
self.assertRaises(NotImplementedError, self.gen.setstate, None)
|
||||
|
||||
def test_seedargs(self):
|
||||
# Doesn't need to do anything except not fail
|
||||
self.gen.seed(100)
|
||||
|
||||
def test_jumpahead(self):
|
||||
# Doesn't need to do anything except not fail
|
||||
self.gen.jumpahead(100)
|
||||
|
||||
def test_gauss(self):
|
||||
self.gen.gauss_next = None
|
||||
self.gen.seed(100)
|
||||
self.assertEqual(self.gen.gauss_next, None)
|
||||
|
||||
def test_pickling(self):
|
||||
self.assertRaises(NotImplementedError, pickle.dumps, self.gen)
|
||||
|
||||
def test_53_bits_per_float(self):
|
||||
# This should pass whenever a C double has 53 bit precision.
|
||||
span = 2 ** 53
|
||||
cum = 0
|
||||
for i in xrange(100):
|
||||
cum |= int(self.gen.random() * span)
|
||||
self.assertEqual(cum, span-1)
|
||||
|
||||
def test_bigrand(self):
|
||||
# The randrange routine should build-up the required number of bits
|
||||
# in stages so that all bit positions are active.
|
||||
span = 2 ** 500
|
||||
cum = 0
|
||||
for i in xrange(100):
|
||||
r = self.gen.randrange(span)
|
||||
self.assert_(0 <= r < span)
|
||||
cum |= r
|
||||
self.assertEqual(cum, span-1)
|
||||
|
||||
def test_bigrand_ranges(self):
|
||||
for i in [40,80, 160, 200, 211, 250, 375, 512, 550]:
|
||||
start = self.gen.randrange(2 ** i)
|
||||
stop = self.gen.randrange(2 ** (i-2))
|
||||
if stop <= start:
|
||||
return
|
||||
self.assert_(start <= self.gen.randrange(start, stop) < stop)
|
||||
|
||||
def test_rangelimits(self):
|
||||
for start, stop in [(-2,0), (-(2**60)-2,-(2**60)), (2**60,2**60+2)]:
|
||||
self.assertEqual(set(range(start,stop)),
|
||||
set([self.gen.randrange(start,stop) for i in xrange(100)]))
|
||||
|
||||
def test_genrandbits(self):
|
||||
# Verify ranges
|
||||
for k in xrange(1, 1000):
|
||||
self.assert_(0 <= self.gen.getrandbits(k) < 2**k)
|
||||
|
||||
# Verify all bits active
|
||||
getbits = self.gen.getrandbits
|
||||
for span in [1, 2, 3, 4, 31, 32, 32, 52, 53, 54, 119, 127, 128, 129]:
|
||||
cum = 0
|
||||
for i in xrange(100):
|
||||
cum |= getbits(span)
|
||||
self.assertEqual(cum, 2**span-1)
|
||||
|
||||
# Verify argument checking
|
||||
self.assertRaises(TypeError, self.gen.getrandbits)
|
||||
self.assertRaises(TypeError, self.gen.getrandbits, 1, 2)
|
||||
self.assertRaises(ValueError, self.gen.getrandbits, 0)
|
||||
self.assertRaises(ValueError, self.gen.getrandbits, -1)
|
||||
self.assertRaises(TypeError, self.gen.getrandbits, 10.1)
|
||||
|
||||
def test_randbelow_logic(self, _log=log, int=int):
|
||||
# check bitcount transition points: 2**i and 2**(i+1)-1
|
||||
# show that: k = int(1.001 + _log(n, 2))
|
||||
# is equal to or one greater than the number of bits in n
|
||||
for i in xrange(1, 1000):
|
||||
n = 1L << i # check an exact power of two
|
||||
numbits = i+1
|
||||
k = int(1.00001 + _log(n, 2))
|
||||
self.assertEqual(k, numbits)
|
||||
self.assert_(n == 2**(k-1))
|
||||
|
||||
n += n - 1 # check 1 below the next power of two
|
||||
k = int(1.00001 + _log(n, 2))
|
||||
self.assert_(k in [numbits, numbits+1])
|
||||
self.assert_(2**k > n > 2**(k-2))
|
||||
|
||||
n -= n >> 15 # check a little farther below the next power of two
|
||||
k = int(1.00001 + _log(n, 2))
|
||||
self.assertEqual(k, numbits) # note the stronger assertion
|
||||
self.assert_(2**k > n > 2**(k-1)) # note the stronger assertion
|
||||
|
||||
|
||||
class MersenneTwister_TestBasicOps(TestBasicOps):
|
||||
gen = random.Random()
|
||||
|
||||
@ -391,6 +492,7 @@ class TestModule(unittest.TestCase):
|
||||
def test_main(verbose=None):
|
||||
testclasses = (WichmannHill_TestBasicOps,
|
||||
MersenneTwister_TestBasicOps,
|
||||
HardwareRandom_TestBasicOps,
|
||||
TestDistributions,
|
||||
TestModule)
|
||||
test_support.run_unittest(*testclasses)
|
||||
|
@ -79,6 +79,9 @@ Extension modules
|
||||
Library
|
||||
-------
|
||||
|
||||
- the random module now uses os.urandom() for seeding if it is available.
|
||||
Added a new generator based on os.urandom().
|
||||
|
||||
- difflib and diff.py can now generate HTML.
|
||||
|
||||
- bdist_rpm now includes version and release in the BuildRoot, and
|
||||
|
Loading…
Reference in New Issue
Block a user