Call the random module twice and each run gives different numbers, then seed it once and the same numbers return like they never left.

The module is deterministic by design, which means the surprise most readers hit is not a bug. I spent this refresh seeding, shuffling, and sampling on Python 3.11.16, so you can replay sequences on demand and see exactly where the module stops being the right tool.

Random Numbers in Python Are Deterministic by Design

Behind the module sits the Mersenne Twister, a generator with a period so long you will never exhaust it. Every call derives from that single stream, so the numbers only look unpredictable until you fix the starting point.

Deterministic cuts both ways, and I confirmed each edge by running it. Reproducibility makes debugging possible, while the same determinism disqualifies the module for anything security-shaped, a boundary the docs state plainly.

Need Function family Section
Replay the same numbers seed, getstate, setstate Seed and state below
Integers in a range randint, randrange, getrandbits Integers below
Pick from a collection choice, choices, sample, shuffle Sequences below
Floats and curves random, uniform, gauss, triangular Floats below

What You Need Before You Generate Anything

Import the module and check your interpreter, since behavior below was captured on 3.11.16. One import covers everything in this guide except the security boundary, which lives in a different module entirely.

import random
import sys

print(sys.version.split()[0])
Situation Setup
Following this guide import random, nothing else
Replaying exact outputs Copy the seed call above each snippet
Generating tokens or secrets Skip to the secrets boundary section

Generate Exactly the Randomness You Asked For

One seeded pipeline carries this section, so every output below replays exactly if you copy the seed. Each heading answers one job, in the order you meet them.

Seed Once to Replay a Sequence

Seeding fixes the stream, so I ran the same three draws twice under one seed and both runs agreed digit for digit.

import random

random.seed(42)
first = [random.randint(1, 100) for _ in range(3)]

random.seed(42)
second = [random.randint(1, 100) for _ in range(3)]

print(first)
print(second)
print("identical:", first == second)
[82, 15, 4]
[82, 15, 4]
identical: True

Anyone with your seed replays your stream, which is another reason secrets stay out of this module. Skip the seed and each run diverges, the behavior you want for play but not for tests.

Seeded replay gives identical draws, and shuffle returns None, captured live.

Save and Restore State Mid-Stream

Seed sets the start, while getstate and setstate bookmark any point inside, and I drew three numbers, rewound to the bookmark, and watched the same three come out again.

import random

random.seed(1)
state = random.getstate()

run1 = [random.randint(1, 1000) for _ in range(3)]

random.setstate(state)
run2 = [random.randint(1, 1000) for _ in range(3)]

print(run1)
print(run2)
print("identical:", run1 == run2)
[138, 583, 868]
[138, 583, 868]
identical: True

State objects let long simulations checkpoint without restarting from the seed. Your own Random instance isolates this further, so library code never disturbs the global stream.

import random

mine = random.Random(99)
print([mine.randint(1, 10) for _ in range(3)])

Integers With randint and randrange

randint includes both ends, while randrange stops before the end like range does. I assumed they were interchangeable once, and the off-by-one in a dice roller corrected me.

import random

random.seed(3)
print([random.randint(1, 6) for _ in range(5)])

random.seed(3)
print([random.randrange(0, 10, 2) for _ in range(5)])
[2, 5, 5, 2, 3]
[2, 8, 8, 2, 4]

The docs confirm randint is an alias for randrange with the top raised by one. Reversed bounds raise ValueError instead of guessing, which the edge section shows.

import random

random.seed(7)
print(random.getrandbits(16))

One Pick, Weighted Picks, and Unique Samples

choice takes one element, choices takes several with optional weights, and sample takes several with no repeats. Mixing these up is the most common reader error I see in questions.

import random

random.seed(11)
print([random.choice(["red", "green", "blue"]) for _ in range(3)])

random.seed(11)
print(random.choices(["red", "green", "blue"], weights=[70, 20, 10], k=10))
['green', 'blue', 'green']
['red', 'red', 'blue', 'red', 'red', 'red', 'red', 'red', 'red', 'green']

Weights tilt the draw without excluding anything, since blue still appears once above. sample instead guarantees uniqueness, and asking for more than the population holds fails loudly.

import random

random.seed(5)
print(random.sample(range(100), 5))

try:
    random.sample([1, 2, 3], 5)
except ValueError as e:
    print("ValueError:", e)
[79, 32, 94, 45, 88]
ValueError: Sample larger than population or is negative

Shuffle In Place or Copy

shuffle rearranges the list you hand it and returns nothing, which surprises everyone exactly once. I watched the return value come back None before believing the docs.

import random

deck = list("ABCD")
random.seed(1)
result = random.shuffle(deck)
print(deck)
print("returns:", result)

random.seed(1)
print(random.sample(list("ABCD"), k=4))
['D', 'A', 'C', 'B']
returns: None
['B', 'C', 'A', 'D']

Need the original order kept, sample with k set to the full length hands back a shuffled copy. The two runs above share a seed yet differ because shuffle and sample consume the stream differently.

Floats and Distributions

random gives a float from zero inclusive to one exclusive, and I verified the bounds across ten thousand draws in the snippet below.

import random

random.seed(13)
print([round(random.uniform(1.0, 10.0), 3) for _ in range(3)])

random.seed(13)
print([round(random.gauss(0, 1), 3) for _ in range(4)])

random.seed(17)
print([round(random.triangular(1.0, 10.0, 5.0), 3) for _ in range(3)])
[3.331, 7.167, 7.157]
[-0.086, 1.518, -0.783, -1.781]
[5.362, 7.051, 8.667]

uniform stretches the interval anywhere you like, while gauss centers draws on a mean and triangular centers them on a mode you choose.

import random

random.seed(21)
vals = [random.random() for _ in range(10000)]
print("min", round(min(vals), 6), "max", round(max(vals), 6))
print("all inside zero inclusive, one exclusive:", all(0.0 <= v < 1.0 for v in vals))
min 6.8e-05
max 0.999918
all inside zero inclusive, one exclusive: True

When the random Module Is the Wrong Tool

Determinism disqualifies this module wherever an adversary watches. Passwords, tokens, and anything security-shaped belong to the secrets module, whose output also runs fine on this machine.

import secrets

tok = secrets.token_hex(8)
print("length:", len(tok))
print("hex only:", all(c in "0123456789abcdef" for c in tok))
length: 16
hex only: True
Mistake What happens Do this instead
randrange with start above stop ValueError, empty range Order the bounds low to high
sample bigger than the population ValueError Use choices when repeats are fine
Expecting shuffle to return the list None, list changed in place Use the list after the call, or sample a copy
Tokens from random Replayable by anyone with the seed Use secrets
import random

try:
    random.randrange(10, 1)
except ValueError as e:
    print("ValueError:", e)
ValueError: empty range for randrange() (10, 1, -9)

Which Function to Reach For

One question picks the function, namely whether repeats are allowed and whether the draw must replay. Answer that and the table below finishes the job.

Job Function
Replay a whole stream seed, or getstate and setstate mid-stream
Integer, both ends included randint
Integer from a range with steps randrange
Single element choice
Several elements, repeats fine, weights wanted choices
Several unique elements sample
Reorder a list shuffle, or sample for a copy
Float in an interval uniform
Anything an adversary must not predict secrets, not random

Frequently Asked Questions

Direct answers to the random questions readers keep asking. Each one points back at the section that proves it.

How do I get the same random numbers every run in Python?

Call random.seed with a fixed value before drawing, and the stream replays identically. Use getstate and setstate to bookmark a point mid-stream instead of restarting from the seed.

What is the difference between randint and randrange?

randint includes both ends, so randint 1, 6 can return 6. randrange stops before the end like range does, and it also accepts a step. randint is an alias for randrange with the top raised by one.

Should I use choice, choices, or sample?

choice draws one element. choices draws several with replacement and accepts weights. sample draws several unique elements and raises ValueError when you ask for more than the population holds.

Why does shuffle return None?

shuffle rearranges the list in place and returns nothing by design. Read the list after the call, or use sample with k set to the full length when you need a shuffled copy.

Can I use random for passwords or tokens?

No. The module is deterministic, so anyone with your seed replays your stream. Use the secrets module for passwords, tokens, and anything security-shaped.

Share.
Leave A Reply