Python random Module
Learn Python's random module: generate random numbers, make weighted choices, shuffle sequences, seed for reproducibility, and use cryptographic randomness.
Python's built-in random module generates pseudo-random numbers and makes random selections. It is based on the Mersenne Twister algorithm, which produces high-quality pseudo-random sequences suitable for simulations, games, sampling, and testing — but not for security-sensitive purposes like password generation or cryptographic keys. This chapter covers:
- Generating random floats, integers, and ranges
- Picking random elements:
choice,choices,sample - Shuffling sequences in-place with
shuffle - Seeding for reproducible results
- Continuous distributions:
uniform,gauss,triangular - When to use
secretsinstead ofrandom
No installation is needed — import random is all it takes.
Importing the module
import randomAll examples below assume this import is in scope. You can also import individual functions to avoid the random. prefix, though the prefix makes it clear where each function comes from:
from random import randint, choice, shuffleGenerating random floats
random()
random.random() returns a float in [0.0, 1.0) — including 0.0 but never reaching 1.0.
import random
print(random.random()) # e.g. 0.6394267984578837
print(random.random()) # e.g. 0.025010755222666936The value changes on every call because the generator advances its internal state.
uniform(a, b)
random.uniform(a, b) returns a float N such that a <= N <= b:
import random
random.seed(42)
print(random.uniform(1.5, 10.5)) # 7.254841186120954
print(random.uniform(-1.0, 1.0)) # e.g. -0.82 (some float in [-1, 1])Unlike random(), uniform lets you specify both ends of the range, and the upper bound can be returned.
Generating random integers
randint(a, b)
random.randint(a, b) returns a random integer N such that a <= N <= b. Both endpoints are inclusive:
import random
random.seed(42)
die = random.randint(1, 6)
print(die) # 6This is the most common way to simulate dice rolls, card draws, or any discrete integer range.
randrange(start, stop[, step])
random.randrange mirrors Python's range() — stop is exclusive and you can specify a step:
import random
random.seed(42)
# Pick an even number from 0 to 98 (0, 2, 4, ..., 98)
print(random.randrange(0, 100, 2)) # e.g. a random even integer
# Pick a multiple of 5 from 0 to 95
random.seed(42)
print(random.randrange(0, 100, 5)) # 15When you need random integers from a range that does not start at 0, or needs a step, randrange is more expressive than randint.
getrandbits(k)
random.getrandbits(k) returns a random non-negative integer with exactly k random bits:
import random
random.seed(42)
print(random.getrandbits(8)) # 163 (an integer in 0..255)
print(random.getrandbits(16)) # an integer in 0..65535This is useful when you need random integers larger than what randint is called with, or when working at the bit level.
Choosing from sequences
choice(seq)
random.choice(seq) picks one element at random from a non-empty sequence. It works with lists, tuples, and strings:
import random
random.seed(42)
fruits = ['apple', 'banana', 'cherry']
print(random.choice(fruits)) # cherry
random.seed(42)
print(random.choice('ABCDE')) # A (picks one character)If the sequence is empty, a IndexError is raised.
choices(population, weights=None, k=1)
random.choices picks k elements with replacement (the same element can appear more than once). You can bias the selection with weights:
import random
colors = ['red', 'blue', 'green']
# Equal probability — each run may differ
print(random.choices(colors, k=3))
# e.g. ['blue', 'red', 'red']
# Weighted: green is 3× more likely than red, blue is 2×
random.seed(42)
print(random.choices(colors, weights=[1, 2, 3], k=4))
# ['green', 'red', 'blue', 'blue']choices always returns a list, even when k=1. The weights argument is relative — [1, 2, 3] gives the same distribution as [10, 20, 30].
sample(population, k)
random.sample picks k elements without replacement — each element can only appear once in the result. It does not modify the original sequence:
import random
random.seed(42)
# Pick 4 unique numbers from 0-9
print(random.sample(range(10), 4)) # [1, 0, 4, 9]
# Simulate a lottery: 6 unique numbers from 1-49
random.seed(7)
ticket = sorted(random.sample(range(1, 50), 6))
print(ticket) # [4, 5, 10, 21, 26, 42]If k is larger than the population size, random.sample raises a ValueError:
import random
try:
random.sample([1, 2, 3], 5)
except ValueError as e:
print(e) # Sample larger than population or is negativechoices vs sample at a glance:
choices | sample | |
|---|---|---|
| Replacement | Yes | No |
| Duplicate results | Possible | Never |
| Modifies original | No | No |
| Supports weights | Yes | No (use choices) |
Shuffling sequences
random.shuffle(seq) shuffles the list in-place and returns None. The original list is modified:
import random
random.seed(42)
deck = [1, 2, 3, 4, 5]
random.shuffle(deck)
print(deck) # [4, 2, 3, 5, 1] (original list is modified)If you need a shuffled copy without touching the original, use random.sample:
import random
random.seed(42)
original = [1, 2, 3, 4, 5]
shuffled = random.sample(original, len(original))
print(original) # [1, 2, 3, 4, 5] (unchanged)
print(shuffled) # [4, 2, 3, 5, 1]Seeding for reproducible results
By default, the random module seeds itself from the system time (or os.urandom()), so each run produces different values. Calling random.seed() with a fixed value locks the sequence so you get identical output every time — essential for debugging, unit tests, and reproducible simulations:
import random
random.seed(42)
print(random.random()) # 0.6394267984578837
print(random.randint(1, 10)) # 1
print(random.choice(['a', 'b', 'c'])) # c
# Reset to the same seed — identical sequence
random.seed(42)
print(random.random()) # 0.6394267984578837 (same as before)
print(random.randint(1, 10)) # 1
print(random.choice(['a', 'b', 'c'])) # crandom.seed() accepts an integer, a string, a float, bytes, or None (which re-seeds from the system). Seeding is a global operation — it affects all subsequent calls in the same program, so set the seed once at the start of a reproducible block.
Generating reproducible test data
import random
random.seed(999)
test_scores = [round(random.uniform(0, 100), 2) for _ in range(5)]
print(test_scores) # [78.13, 8.01, 87.25, 57.38, 49.06]Every run with seed(999) produces the exact same list, making this useful for unit tests that assert specific values.
Continuous distributions
The random module includes several continuous probability distributions, useful in simulations and statistical modelling.
uniform(a, b)
Already covered above — a flat distribution where every value in [a, b] is equally likely.
triangular(low, high, mode)
Returns a float from a triangular distribution where mode is the most common value:
import random
random.seed(42)
# Most values cluster near 5, between 0 and 10
t = random.triangular(0, 10, 5)
print(t) # 5.753983033817951Useful when you know the min, max, and most-likely value but not the exact distribution — common in project estimation and risk modelling.
gauss(mu, sigma)
Returns a float from a Gaussian (normal) distribution with mean mu and standard deviation sigma:
import random
random.seed(42)
# Heights in cm: mean 170, std 10
heights = [round(random.gauss(170, 10), 1) for _ in range(5)]
print(heights) # [168.6, 168.3, 168.9, 177.0, 168.7]random.normalvariate(mu, sigma) is equivalent but thread-safe; use normalvariate in multi-threaded programs.
expovariate(lambd)
Returns a float from an exponential distribution, where lambd is 1 / mean. Useful for modelling time between events (e.g. customer arrivals, network packets):
import random
random.seed(42)
# Mean inter-arrival time = 5 minutes; lambd = 1/5
wait = random.expovariate(1 / 5)
print(round(wait, 2)) # 5.1 (minutes until next arrival)Practical examples
Simulating 60 dice rolls
import random
random.seed(42)
counts = {face: 0 for face in range(1, 7)}
for _ in range(60):
counts[random.randint(1, 6)] += 1
print(counts)
# {1: 15, 2: 10, 3: 8, 4: 7, 5: 10, 6: 10}With more rolls the distribution approaches the theoretical 1/6 probability for each face.
Picking a random team
import random
random.seed(42)
players = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank']
random.shuffle(players)
team_a = players[:3]
team_b = players[3:]
print('Team A:', team_a)
print('Team B:', team_b)Simple random password
For non-security contexts (demos, throwaway tokens), random.choices works well:
import random
import string
random.seed(42)
chars = string.ascii_letters + string.digits
password = ''.join(random.choices(chars, k=12))
print(password) # NbrnTP3fAbnFFor actual user passwords or tokens, use the secrets module instead (see below).
When to use secrets instead of random
The random module is not cryptographically secure. An attacker who observes enough output can predict future values. For anything security-sensitive, use the secrets module (Python 3.6+), which draws from the operating system's cryptographic random source:
import secrets
# Cryptographically secure random integer in [0, 100)
print(secrets.randbelow(100))
# Secure random token for a password-reset link (URL-safe base64)
token = secrets.token_urlsafe(32)
print(token) # e.g. 'gIg9XzExxFkHLAH9JCK8Zxb1aBEFZ...'
# Secure random hex string for an API key
api_key = secrets.token_hex(16)
print(api_key) # e.g. 'a3f1c2d4e5b6...'Rule of thumb: if you are generating tokens, session IDs, passwords, or any value an adversary must not be able to guess, use secrets. Use random for everything else (games, simulations, test fixtures, sampling).
Quick reference
| Function | Description | Returns |
|---|---|---|
random.random() | Float in [0.0, 1.0) | float |
random.uniform(a, b) | Float in [a, b] | float |
random.randint(a, b) | Integer in [a, b] (both inclusive) | int |
random.randrange(start, stop[, step]) | Integer from range(start, stop, step) | int |
random.getrandbits(k) | Non-negative integer with k random bits | int |
random.choice(seq) | One random element | element type |
random.choices(pop, weights, k) | k elements with replacement | list |
random.sample(pop, k) | k elements without replacement | list |
random.shuffle(seq) | Shuffle list in-place | None |
random.seed(a) | Set the random seed | None |
random.gauss(mu, sigma) | Gaussian (normal) variate | float |
random.normalvariate(mu, sigma) | Normal variate (thread-safe) | float |
random.triangular(low, high, mode) | Triangular distribution variate | float |
random.expovariate(lambd) | Exponential distribution variate | float |
Related chapters
- Python Math — mathematical functions and constants
- Python Modules — how to import and organize modules
- Python Numbers — integer, float, and complex types
- Python Lists — working with ordered sequences
- Python For Loops — iterating to generate random data
- Python Try Except — handling errors like
ValueErrorfromsample