Using the seed

Published

2023-08-02

Pseudo-randomness in more detail

I mentioned earlier that the numbers generated and choices made by the random module aren’t truly random, they’re pseudo-random, but what does this mean?

Computers compute. They can’t pluck a random number out of thin air. You might think that computation by your computer is deterministic and you’d be right.

So how do we use a deterministic computing device to produce something that appears random, something that has all the statistical properties we need?

Deep down, the random module makes use of an algorithm called the Mersenne twister (what a lovely name for an algorithm!).1 You don’t need to understand how this algorithm works, but it’s useful to understand that it does require an input used as a starting point for its calculations. This input is called a seed, and from this, the algorithm produces a sequence of pseudo-random numbers. At each request, we get a new pseudo-random number.

>>> import random
>>> random.random()
0.16558225561225903
>>> random.random()
0.20717009610984627
>>> random.random()
0.2577426786448077
>>> random.random()
0.5173312574262303
>>> 

Try this out. (The sequence of numbers you’ll get will differ.)

So where does the seed come in? By default, the algorithm gets a seed from your computer’s operating system. Modern operating systems provide a special source for this, and if a seed is not supplied in your code, the random module will ask the operating system to supply one.2

Using the seed

Most of the time we want unpredictability from our pseudo-random number generator (or choices). However, sometimes we wish to control the process a little more, for comparability of results.

For example, it would be difficult, if not impossible, to test a program whose output is unpredictable. This is why the random module allows us to provide our own seed. If we start the process from the same seed, the sequence of random numbers generated or the sequence of choices made is the same. For example,

>>> import random
>>> random.seed(42)  # Set the seed.
>>> random.random()
0.6394267984578837
>>> random.random()
0.025010755222666936
>>> random.random()
0.27502931836911926
>>> random.seed(42)  # Set the seed again, to the same value.
>>> random.random()
0.6394267984578837
>>> random.random()
0.025010755222666936
>>> random.random()
0.27502931836911926

Notice that the sequence of numbers generated by successive calls to random.random() are identical: 0.6394267984578837, 0.025010755222666936, 0.27502931836911926,

Here’s another example:

>>> import random
>>> results = []
>>> random.seed('walrus')
>>> for _ in range(10):
...    results.append(random.choice(['a', 'b', 'c']))
...
>>> results
['b', 'a', 'c', 'b', 'a', 'a', 'a', 'c', 'a', 'b']
>>> results = []
>>> random.seed('walrus')
>>> for _ in range(10):
...    results.append(random.choice(['a', 'b', 'c']))
...
>>> results
['b', 'a', 'c', 'b', 'a', 'a', 'a', 'c', 'a', 'b']

Notice that the results are identical in both instances. If we were to perform this experiment 1,000,000 with the same seed, we’d always get the same result. It looks random, but deep down it isn’t.

By setting the seed, we can make the behavior of calls to random methods entirely predictable. As you might imagine, this allows us to test programs that incorporate pseudo-random number generation or choices.

Try something similar with random.shuffle(). Start with a short list, set the seed, and shuffle it. Then re-initialize the list to its original value, set the seed again—with the same value—and shuffle it. Is the shuffled list the same in both cases?

Original author: Clayton Cafiero < [given name] DOT [surname] AT uvm DOT edu >

No generative AI was used in producing this material. This was written the old-fashioned way.

This material is for free use under either the GNU Free Documentation License or the Creative Commons Attribution-ShareAlike 3.0 United States License (take your pick).

Footnotes

  1. M. Matsumoto and T. Nishimura, 1998, “Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator”, ACM Transactions on Modeling and Computer Simulation, 8(1).↩︎

  2. If you’re curious, try this:

    >>> import os       # interface to the operating system
    >>> os.urandom(8)   # request a bytestring of size 8
    b'\xa6t\x08=\xa5\x19\xde\x94'

    This is where the random module gets its seed by default. This service itself requires a seed, which the OS gets from a variety of hardware sources. The objective is for the seed to be as unpredictable as possible.↩︎