🐍 Python Course · Stage 3 · Lesson 45 of 89 · random — Random Numbers
Course Overview →

🔵 Read Beginner and Intermediate tabs. The Expert tab has the internals — worth reading once you finish Stage 3.

← math — Math Functions contextlib — with Utilities →
thecodex.expert · The Codex Family of Knowledge
Python Standard Library

random — Generate Pseudo-Random Numbers

The random module produces pseudo-random numbers for simulations, games, sampling, and shuffling — with a clear warning: it is not safe for security.

import random docs.python.org Last verified:
Canonical Definition

The random module implements pseudo-random number generators for various distributions. It uses the Mersenne Twister algorithm as its core generator. For security-sensitive randomness (tokens, passwords, keys), the secrets module must be used instead.

🟩 Beginner

Dice, shuffles, and random picks

What you will learn: the core functions for random numbers, integers, and picking from sequences — plus the critical warning that random is not for security.

How to read this tab: Run each example a few times to see the values change. Remember randint includes both ends.

⏱ 25 min📄 2 sections🔶 Prerequisite: Modules & Packages
The idea

The random module gives you dice rolls, coin flips, shuffled decks, and random picks. The numbers are not truly random — they come from a formula — but they are random enough for games, simulations, and sampling. Just never use them for passwords or security tokens.

randint includes both ends; randrange excludes the stop. randint(1, 6) can return 6; randrange(1, 6) stops at 5 (like range). Mixing these up is the most common random bug — a dice roll needs randint(1, 6).

The core functions

Pythoncore.py
import random

# A float in [0.0, 1.0)
print(random.random())          # e.g. 0.6394267984

# A float in a range
print(random.uniform(1, 10))    # e.g. 7.31...

# An integer in [a, b] — BOTH endpoints included
print(random.randint(1, 6))     # a dice roll: 1 to 6 inclusive

# An integer from a range [start, stop) — stop excluded, like range()
print(random.randrange(0, 100, 2))  # a random even number 0..98

# Pick one item from a sequence
colors = ["red", "green", "blue"]
print(random.choice(colors))    # e.g. "green"
randint includes both ends; randrange excludes the stop

random.randint(1, 6) can return 1, 2, 3, 4, 5, or 6 — both ends included. random.randrange(1, 6) returns 1 to 5 — the stop is excluded, matching how range() works. Mixing these up is the most common random bug.

shuffle returns None — it modifies in place. Write random.shuffle(deck) on its own line. deck = random.shuffle(deck) sets deck to None, a classic mistake. Same pattern as list.sort().

Working with sequences

Pythonsequences.py
import random

deck = ["A", "K", "Q", "J", "10", "9"]

# Shuffle IN PLACE (modifies the list, returns None)
random.shuffle(deck)
print(deck)              # e.g. ['Q', '9', 'A', 'K', '10', 'J']

# Pick k UNIQUE items (no repeats) — sampling without replacement
hand = random.sample(deck, 3)
print(hand)              # e.g. ['K', 'A', '9']

# Pick k items WITH possible repeats — sampling with replacement
rolls = random.choices(deck, k=5)
print(rolls)             # e.g. ['A', 'A', 'Q', 'J', 'A']

# Weighted choices — 'A' three times as likely as others
weighted = random.choices(["A", "B", "C"], weights=[3, 1, 1], k=10)
print(weighted)          # mostly A's
shuffle returns None

random.shuffle(deck) shuffles the list in place and returns None. Writing deck = random.shuffle(deck) sets deck to None — a classic mistake. Just call random.shuffle(deck) on its own line.

✅ Beginner tab complete

  • I can generate random floats with random() and uniform()
  • I know randint(1,6) includes both 1 and 6
  • I can pick with choice() and shuffle a list in place
  • I know random is NOT safe for passwords or tokens

Continue to argparse →

🔵 Intermediate

Distributions and reproducible seeding

What you will learn: drawing from statistical distributions, seeding for reproducible results, and using independent Random() instances.

How to read this tab: Seed with a fixed number, run twice, and confirm you get identical sequences — that is reproducibility for tests.

⏱ 25 min📄 2 sections🔶 Prerequisite: Beginner tab
💡

sample vs choices: sample picks unique items (dealing cards, no repeats); choices picks with replacement (rolling dice, repeats allowed) and supports weights. Cards need sample; dice need choices.

Statistical distributions

Beyond uniform randomness, the module provides draws from common probability distributions — useful for simulations and modelling.

Pythondistributions.py
import random

# Normal (Gaussian) distribution — mean=0, std dev=1
print(random.gauss(0, 1))          # e.g. -0.47
print(random.normalvariate(100, 15))  # IQ-like: mean 100, sd 15

# Other distributions
print(random.expovariate(1.5))     # exponential
print(random.triangular(0, 10, 3)) # triangular: low, high, mode
print(random.betavariate(2, 5))    # beta distribution

# Simulate 1000 dice rolls and check the distribution
from collections import Counter
rolls = [random.randint(1, 6) for _ in range(1000)]
print(Counter(rolls))   # roughly even: each face ~166 times

random is NOT secure — this is the most important fact on the page. Never use it for passwords, tokens, API keys, or session IDs. Its output is predictable from its internal state. Use the secrets module for everything security-related.

Seeding for reproducibility

Seeding the generator makes the "random" sequence repeatable — essential for tests, debugging, and reproducible simulations.

Pythonseeding.py
import random

# Seed -> same sequence every run (reproducible)
random.seed(42)
print(random.randint(1, 100))   # always 82 with seed 42
print(random.random())          # always the same float

random.seed(42)                 # reset to the same seed
print(random.randint(1, 100))   # 82 again — identical sequence

# Independent generators — don't share global state
rng1 = random.Random(42)        # a private generator instance
rng2 = random.Random(99)
print(rng1.randint(1, 10))      # independent of the global random
print(rng2.randint(1, 10))      # and independent of rng1

# Use a dedicated Random() instance in libraries so you don't
# disturb the application's global random state.
Commonly confused
sample vs choices. random.sample picks unique items (no repeats, like dealing cards). random.choices picks with replacement (repeats allowed, like rolling dice) and supports weights. Cards → sample; dice → choices.
random is NOT secure. Never use the random module for passwords, tokens, API keys, or anything security-related. Its output is predictable if an attacker learns the internal state. Use the secrets module for all security purposes.

✅ Intermediate tab complete

  • I can draw from gauss/normalvariate distributions
  • I can seed for reproducible sequences
  • I know sample picks unique items; choices allows repeats and weights
  • I can create independent Random() instances

Continue to argparse →

🔴 Expert

Mersenne Twister and cryptographic safety

What you will learn: the MT19937 generator, why it is reproducible but predictable, SystemRandom, and why secrets must be used for security.

How to read this tab: Read to understand exactly why random is unsafe for secrets — the same property that makes it testable makes it predictable.

⏱ 20 min📄 1 section🔶 Prerequisite: After Stage 3

The Mersenne Twister, state, and why it is not cryptographic

The random module's core generator is the Mersenne Twister (MT19937), a pseudo-random number generator with an extremely long period of 219937−1. It is fast, well-distributed, and excellent for simulations — but it is deterministic and its entire future output can be reconstructed from observing 624 consecutive 32-bit outputs, which is exactly why it must never be used for security.

Pythonmt_state.py
import random

# The full generator state can be captured and restored
state = random.getstate()
a = random.random()
random.setstate(state)     # rewind
b = random.random()
print(a == b)              # True — restored to the exact same point

# SystemRandom — uses os.urandom (the OS entropy source) instead of MT
# This IS suitable for security, though 'secrets' is the preferred API
secure_rng = random.SystemRandom()
print(secure_rng.randint(1, 100))   # not reproducible, not seedable
# secure_rng.seed() does nothing — it draws from OS entropy

# For security, prefer the secrets module:
import secrets
token = secrets.token_hex(16)        # 32-char secure hex token
pick  = secrets.choice(["a", "b"])   # secure choice
num   = secrets.randbelow(100)       # secure int in [0, 100)

# random.randbytes (3.9+) — random bytes from the MT generator
print(random.randbytes(4))           # NOT secure — for simulations only

The distinction in implementation is concrete: random.random() draws from the seeded, reproducible Mersenne Twister, while random.SystemRandom and the entire secrets module draw from os.urandom(), which pulls from the operating system's cryptographically secure entropy pool. The reproducibility that makes random perfect for testing — same seed, same sequence — is precisely the property that makes it unsafe for secrets, because reproducible means predictable.

✅ Expert tab complete

  • I know random uses the Mersenne Twister (MT19937)
  • I know its output is predictable from observed state
  • I know SystemRandom and secrets draw from os.urandom
  • I always use secrets for tokens, passwords, and keys

Continue to argparse →

Sources

1
Python Standard Library — random. docs.python.org/3/library/random.html.
2
Python Standard Library — secrets (for security). docs.python.org/3/library/secrets.html.
3
Python Standard Library — random.SystemRandom. docs.python.org/3/library/random.html#random.SystemRandom.
Source confidence: High Last verified: Primary source: docs.python.org random