diff --git a/Doc/library/random.rst b/Doc/library/random.rst index 6ef5d209fcf..270518c52bb 100644 --- a/Doc/library/random.rst +++ b/Doc/library/random.rst @@ -51,18 +51,23 @@ from sources provided by the operating system. Bookkeeping functions: -.. function:: seed([x]) +.. function:: seed([x], version=2) - Initialize the basic random number generator. Optional argument *x* can be any - :term:`hashable` object. If *x* is omitted or ``None``, current system time is used; - current system time is also used to initialize the generator when the module is - first imported. If randomness sources are provided by the operating system, - they are used instead of the system time (see the :func:`os.urandom` function - for details on availability). + Initialize the random number generator. - If *x* is not ``None`` or an int, ``hash(x)`` is used instead. If *x* is an - int, *x* is used directly. + If *x* is omitted or ``None``, the current system time is used. If + randomness sources are provided by the operating system, they are used + instead of the system time (see the :func:`os.urandom` function for details + on availability). + If *x* is an int, it is used directly. + + With version 2 (the default), a :class:`str`, :class:`bytes`, or :class:`bytearray` + object gets converted to a :class:`int` and all of its bits are used. With version 1, + the :func:`hash` of *x* is used instead. + + .. versionchanged:: 3.2 + Moved to the version 2 scheme which uses all of the bits in a string seed. .. function:: getstate() diff --git a/Lib/random.py b/Lib/random.py index 592e4b899a1..4ff65ab8ea5 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -91,13 +91,17 @@ class Random(_random.Random): self.seed(x) self.gauss_next = None - def seed(self, a=None): + def seed(self, a=None, version=2): """Initialize internal state from hashable object. None or no argument seeds from current time or from an operating system specific randomness source if available. - If a is not None or an int, hash(a) is used instead. + For version 2 (the default), all of the bits are used if a is a str, + bytes, or bytearray. For version 1, the hash() of a is used instead. + + If a is an int, all bits are used. + """ if a is None: @@ -107,6 +111,11 @@ class Random(_random.Random): import time a = int(time.time() * 256) # use fractional seconds + if version == 2 and isinstance(a, (str, bytes, bytearray)): + if isinstance(a, str): + a = a.encode("utf8") + a = int(_hexlify(a), 16) + super().seed(a) self.gauss_next = None diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index 10406fe32d3..78cd4d572d7 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -39,7 +39,7 @@ class TestBasicOps(unittest.TestCase): self.gen.seed(arg) for arg in [list(range(3)), dict(one=1)]: self.assertRaises(TypeError, self.gen.seed, arg) - self.assertRaises(TypeError, self.gen.seed, 1, 2) + self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4) self.assertRaises(TypeError, type(self.gen), []) def test_sample(self): @@ -223,6 +223,21 @@ class SystemRandom_TestBasicOps(TestBasicOps): 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) + self.assertEqual([self.gen.random().hex() for i in range(4)], + ['0x1.ac362300d90d2p-1', '0x1.9d16f74365005p-1', + '0x1.1ebb4352e4c4dp-1', '0x1.1a7422abf9c11p-1']) + self.gen.seed("the quick brown fox", version=1) + self.assertEqual([self.gen.random().hex() for i in range(4)], + ['0x1.9ee265c177cdep-2', '0x1.bad51092e3c25p-1', + '0x1.85ff833f71576p-1', '0x1.87efb37462927p-1']) + self.gen.seed("the quick brown fox", version=2) + self.assertEqual([self.gen.random().hex() for i in range(4)], + ['0x1.1294009b9eda4p-2', '0x1.2ff96171b0010p-1', + '0x1.459e0989bd8e0p-5', '0x1.8b5f55892ddcbp-1']) + def test_setstate_first_arg(self): self.assertRaises(ValueError, self.gen.setstate, (1, None, None)) diff --git a/Misc/NEWS b/Misc/NEWS index 014164ea7d7..a69064e2fdd 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,9 @@ Library * Document which parts of the module are guaranteed to stay the same across versions and which parts are subject to change. + * Update the seed() method to use all of the bits in a string + instead of just the hash value. + - collections.OrderedDict now supports a new method for repositioning keys to either end.