The random Module
Three functions, and the whole difficulty is that two of them look almost identical and cover different numbers. randint(1, 6) can give you a 6. randrange(1, 6) never will. Nothing in the code says so, which is why examiners like it and why dice programs are so often quietly wrong.
1random() — a decimal between 0 and 1
import random
print(random.random())
print(random.random())
print(random.random())0.09257646839864098 0.6501665598866379 0.5743197615487686
random() takes no arguments and always gives a float from 0 up to (but never reaching) 1. On its own that is rarely what you want — it is a building block. Multiply it to stretch it, add to shift it:
# 0-1 stretched into other ranges
import random
print(random.random() * 10)
print(random.random() * 5 + 20)4.51715653708381 20.246290398300456
The first line lands somewhere in 0 to 10, the second somewhere in 20 to 25. For whole numbers there is a better tool, which is the next one.
2randint(a, b) — a whole number, both ends included
# six rolls of a die
import random
for i in range(6):
print(random.randint(1, 6), end=' ')5 3 2 3 4 2
randint(1, 6) can give 1, 2, 3, 4, 5 or 6. Both ends are included. This makes it the one to reach for whenever the range is described the way a human describes it — “a number from 1 to 6”, “a page between 1 and 200”.That is unusual for Python. range(1, 6) stops at 5, and so does slicing, and so does almost everything else in the language. randint is the odd one out — which is exactly why it needs remembering.
3randrange() — the same shape as range()
randrange() takes the same arguments as range() and obeys the same rule: the stop value is not included.
import random
print(random.randrange(5)) # 0, 1, 2, 3 or 4
print(random.randrange(1, 6)) # 1, 2, 3, 4 or 5 -- never 6
print(random.randrange(0, 101, 10)) # 0, 10, 20, ... 1001 4 30
The third form takes a step, which randint cannot do at all. It is how you pick a random multiple of ten, or a random even number, without any arithmetic of your own.
4Watch the 6 go missing
The gap is easy to agree with and hard to believe until you see it. Draw from both a few dozen times:
Both calls name 1 and 6. Draw a few dozen and watch one column keep a gap that never fills.
random.randint(1, 6)random.randrange(1, 6)The right-hand column never fills its last bar, however long you keep going. That is the entire difference between the two functions, and it is the reason a dice program written with randrange(1, 6) looks fine, runs fine, and rolls a six roughly never.
| Call | Possible answers | Type |
|---|---|---|
random.random() | 0.0 up to but not including 1.0 | float |
random.randint(1, 6) | 1, 2, 3, 4, 5, 6 | int |
random.randrange(5) | 0, 1, 2, 3, 4 | int |
random.randrange(1, 6) | 1, 2, 3, 4, 5 | int |
random.randrange(0, 101, 10) | 0, 10, 20 … 100 | int |
5Program 1 — two dice
import random
a = random.randint(1, 6)
b = random.randint(1, 6)
print('Dice:', a, 'and', b)
print('Total:', a + b)Dice: 5 and 3 Total: 8
a = b = random.randint(1, 6) rolls once and reports the same face twice — every single time. Each throw needs its own call.6Program 2 — ten coin tosses
# 1 is heads, 0 is tails
import random
heads = 0
tails = 0
for i in range(10):
if random.randint(0, 1) == 1:
heads = heads + 1
else:
tails = tails + 1
print('Heads:', heads)
print('Tails:', tails)Heads: 8 Tails: 2
Eight heads out of ten, on this run. That is not a bug and not a bias — it is what ten tosses of a fair coin look like sometimes. Raise the 10 to 1000 and the two counts come out close to each other, which is the honest way to check that a random program is fair.
7Program 3 — pick one item at random
Pick a random colour out of a tuple of colours.
The trick is to pick a random position rather than a random item — and the last valid position is len(colours) - 1:
import random
colours = ('red', 'green', 'blue', 'yellow')
i = random.randint(0, len(colours) - 1)
print('Picked:', colours[i])Picked: green
- 1 is not optional. The tuple has four items at positions 0, 1, 2, 3. randint(0, 4) would sometimes return 4 and colours[4] raises IndexError: tuple index out of range — a crash that only happens on some runs, which is the worst kind of bug to hunt.random.randrange(len(colours)) avoids the trap entirely, because excluding the stop is exactly right here.8Program 4 — a six-digit OTP
# build the code one digit at a time, as a string
import random
otp = ''
for i in range(6):
otp = otp + str(random.randint(0, 9))
print('Your OTP is', otp)Your OTP is 920537
Built as a string, not a number, so that a leading zero survives — as a number, 075129 would print as 75129 and be five digits long. This is why PINs, phone numbers and PIN codes are stored as text in real systems.
9Program 5 — three different lucky numbers
Pick three different numbers between 1 and 20 — no repeats.
Three calls to randint can easily give the same number twice. The fix is a while loop that keeps drawing until it has enough, and throws away anything already picked:
# keep drawing until three different numbers are in
import random
picked = ()
while len(picked) < 3:
n = random.randint(1, 20)
if n not in picked:
picked = picked + (n,)
print('Lucky numbers:', picked)Lucky numbers: (1, 6, 11)
while len(picked) < 3: rather than for i in range(3): — because a round that draws a repeat makes no progress, and a for loop would count it anyway and finish with two numbers.
10Program 6 — guess the number
# the computer thinks of a number and gives hints
import random
secret = random.randint(1, 100)
tries = 0
while True:
guess = int(input('Your guess: '))
tries = tries + 1
if guess == secret:
print('Correct! You took', tries, 'tries.')
break
elif guess < secret:
print('Too low')
else:
print('Too high')Your guess: 50 Too high Your guess: 25 Too low Your guess: 37 Too low Your guess: 43 Too high Your guess: 40 Too low Your guess: 41 Too low Your guess: 42 Correct! You took 7 tries.
while True: with a break, because nobody knows in advance how many guesses it will take. Seven is not luck — halving the range each time finds any number from 1 to 100 in at most seven tries.
11Recap
The only common Python function that does. randint(1, 6) is a fair die.
Like range() and like slicing. randrange(1, 6) never gives 6 — and takes a step, which randint cannot.
Never quite 1, and no arguments. Multiply and add to stretch it into another range.
randint(0, len(t) - 1), or randrange(len(t)), which cannot be off by one.
So test the shape of the answer, not the digits — and check fairness over a thousand draws, not ten.
- 1
Roll a die 20 times and count how many sixes you get.
Hint · A counter, and
randint(1, 6)— notrandrange(1, 6), or the count is always 0. - 2
Roll two dice 100 times and tally the totals with a dictionary.
Hint ·
freq[a + b] = freq.get(a + b, 0) + 1. Walksorted(freq)to print it — 7 should be the tallest. - 3
Generate a random 4-digit PIN that may start with 0.
Hint · Build it as a string, exactly like the OTP.
randrange(10)works as well asrandint(0, 9). - 4
Pick a random even number between 10 and 50.
Hint ·
randrange(10, 51, 2). The 51 is because the stop is excluded and 50 must be reachable. - 5
Play the guessing game the other way round: you think of a number, the computer guesses.
Hint · Keep
lowandhigh, guess the middle, and move whichever end the answer rules out.
Which call is right for a dice roll?
colours has 4 items. Why is random.randint(0, len(colours)) wrong?
Why does the lottery program use while rather than for?