The random module generates random numbers, picks items from sequences, and shuffles lists. It’s perfect for simulations, games, and shuffling training data.
For cryptographic purposes (passwords, tokens, security), use the secrets module instead — random is fast but predictable.
Random numbers
import random
print(random.random()) # 0.0 ≤ x < 1.0
print(random.uniform(1.0, 5.0)) # 1.0 ≤ x ≤ 5.0 (float)
print(random.randint(1, 10)) # 1 ≤ n ≤ 10 (integer, inclusive!)
print(random.randrange(10)) # 0 ≤ n < 10 (matches range)
print(random.randrange(0, 100, 5)) # 0, 5, 10, ..., 95
A common gotcha: randint(a, b) is inclusive on both ends, but randrange(a, b) is exclusive on the upper end. Pay attention to which one you want.
Picking from a sequence
items: list[str] = ["apple", "banana", "cherry", "date"]
print(random.choice(items)) # one random item
print(random.choices(items, k=3)) # 3 items, with replacement (can repeat)
print(random.sample(items, k=2)) # 2 items, no repeats
choice— one item.choices— many items, can repeat.sample— many items, no repeats.
Weighted choice
random.choices accepts weights:
events: list[str] = ["green", "yellow", "red"]
weights: list[float] = [0.7, 0.2, 0.1] # 70% green, 20% yellow, 10% red
picks = random.choices(events, weights=weights, k=10)
print(picks)
# e.g. ['green', 'green', 'yellow', 'green', 'green', 'red', 'green', 'green', 'yellow', 'green']
Great for biased simulations — generating events according to a probability distribution.
Shuffling
deck: list[int] = list(range(1, 11))
random.shuffle(deck) # in place
print(deck)
# [4, 9, 1, 7, 3, 10, 6, 2, 8, 5]
random.shuffle modifies the list in place. If you want a new list shuffled, use random.sample(items, k=len(items)):
shuffled = random.sample(deck, k=len(deck))
Distributions
For ML and simulations, you sometimes want values from a specific shape:
random.gauss(mu=0, sigma=1) # normal (Gaussian)
random.normalvariate(mu=0, sigma=1) # same idea, slightly different impl.
random.expovariate(lambd=1.5) # exponential
random.betavariate(alpha=2, beta=5) # beta
random.triangular(low=0, high=10, mode=3)
Each of these returns one sample. For thousands of samples, NumPy’s np.random is much faster.
Seeding — reproducible randomness
If you want the “random” sequence to be the same every run (essential for debugging, tests, and reproducible ML experiments), set the seed:
random.seed(42)
print(random.random()) # always the same number after seeding with 42
print(random.random()) # always the same second number
Without a seed, Python seeds with the current time. With a seed, the sequence is deterministic.
In ML projects you’ll often see:
random.seed(42)
np.random.seed(42)
torch.manual_seed(42)
Set every relevant seed so the run is fully reproducible.
A practical example — train/test split
A toy version of the classic ML preprocessing step:
import random
def train_test_split(items: list[int], test_size: float = 0.2) -> tuple[list[int], list[int]]:
shuffled = items.copy()
random.shuffle(shuffled)
cut: int = int(len(shuffled) * test_size)
return shuffled[cut:], shuffled[:cut]
random.seed(42)
data: list[int] = list(range(20))
train, test = train_test_split(data, test_size=0.25)
print("train:", train)
print("test:", test)
Setting the seed makes the split repeatable — same train/test sets every run.
When to use secrets instead
For anything security-sensitive (tokens, passwords, session IDs), the random module is not safe:
import secrets
token: str = secrets.token_hex(16) # 32 hex chars
url_safe: str = secrets.token_urlsafe(16)
print(token)
print(url_safe)
secrets uses the OS’s cryptographic random source. Fast enough for almost every use case, and safe.
What’s next
You can roll dice. Next — dates and times with datetime.