Случайные числа в программах: кубики, жребий, перемешивание и опыты

Pythonобновлено 07.10.20269 мин чтенияесть тренажёр

Модуль random в Python выдаёт случайные числа и делает случайный выбор: бросает виртуальный кубик, тянет жребий из списка, перемешивает карты. Без него не обходится ни одна игра с элементом удачи. Ниже — все главные функции с примерами, а в редакторе вверху страницы их можно запустить и посмотреть, как меняется результат.

Загружаем…

Что такое модуль random

Модуль random — это часть стандартной библиотеки Python с функциями для случайных чисел и случайного выбора. Устанавливать ничего не нужно: модуль есть в каждой версии Python 3, его достаточно подключить командой import.

import random

print(random.randint(1, 6))
print(random.choice(["камень", "ножницы", "бумага"]))

Каждый запуск этой программы печатает разные результаты: то 4 и «бумага», то 1 и «камень». Именно это и нужно играм: если бы компьютер всегда выбирал одно и то же, играть с ним было бы неинтересно. Модулем называют файл с готовым кодом, который можно подключить к своей программе, — вроде набора деталей, которые не надо собирать самому.

Числа, которые выдаёт random, на самом деле не совсем случайные. Их вычисляет формула-генератор, и официальная документация Python прямо называет их псевдослучайными: модуль использует генератор под названием «вихрь Мерсенна»[1]. Для игр, рисунков и учебных опытов такой случайности более чем достаточно. Описание всех функций модуля публикует Python Software Foundation в документации к стандартной библиотеке. А вот для паролей и шифрования документация random использовать не рекомендует — об этом ниже.

Как подключить модуль random

Подключить модуль можно 2 способами: import random подключает его целиком, и тогда функции вызывают через точку, а from random import randint берёт только нужную функцию, и её вызывают без префикса.

import random
x = random.randint(1, 100)

from random import choice, shuffle
color = choice(["красный", "синий", "жёлтый"])
print(x, color)

Первый вариант понятнее новичку: по записи random.randint сразу видно, откуда взялась функция. Второй короче, но легко забыть, что choice — не ваша функция, а из модуля. Строку с import пишут в самом начале файла — так советует руководство PEP 8[2]. Если забыть import, Python остановится с ошибкой NameError: name 'random' is not defined. Не стоит называть собственный файл random.py: Python найдёт его раньше стандартного модуля, и random.randint перестанет работать.

randint и randrange: случайные целые числа

Функция randint(a, b) возвращает случайное целое число от a до b, причём обе границы входят: randint(1, 6) может дать и 1, и 6. Функция randrange устроена как range — правая граница в ней не входит, зато можно задать шаг.

import random

dice = random.randint(1, 6)
percent = random.randint(0, 100)
even = random.randrange(0, 101, 2)
digit = random.randrange(10)
print(dice, percent, even, digit)
ЗаписьКакие числа возможныГде пригодится
randint(1, 6)1, 2, 3, 4, 5, 6Игральный кубик
randint(0, 1)0 или 1Монетка, да или нет
randrange(10)0…9Случайная цифра
randrange(0, 101, 2)0, 2, 4, …, 100Случайное чётное число
randrange(5, 50, 5)5, 10, …, 45Числа, кратные 5

Разница в правой границе — главная ловушка модуля random. У randint она включена, у randrange и range — нет. Если в игре «угадай число от 1 до 100» написать randrange(1, 100), число 100 никогда не загадается. Проверить это легко: запустите генерацию тысячу раз и посмотрите на максимум.

choice, shuffle и sample: работа со списками

Для списков в модуле random есть 3 функции: choice выбирает один случайный элемент, shuffle перемешивает весь список на месте, а sample возвращает несколько разных элементов без повторов.

import random

team = ["Аня", "Боря", "Вика", "Гоша", "Даша"]
print("Капитан:", random.choice(team))
random.shuffle(team)
print("Порядок выступлений:", team)
print("Дежурные:", random.sample(team, 2))
print("Случайная буква:", random.choice("абвгд"))

choice работает с любой последовательностью, в том числе со строкой: так можно выбрать случайную букву. shuffle ничего не возвращает и меняет сам список, поэтому запись team = random.shuffle(team) положит в переменную None. Если исходный порядок нужно сохранить, сначала сделайте копию: mixed = team[:], а потом перемешайте её. sample(team, 2) гарантирует, что два дежурных будут разными людьми, а два вызова choice подряд могут выбрать одного и того же. Как устроены списки и их копии, разобрано в уроке про списки в Python.

random() и uniform: случайные дробные числа

Функция random() без аргументов возвращает дробное число от 0 до 1, причём 1 не входит, а uniform(a, b) — дробное число между a и b. Дробные случайные числа нужны для вероятностей и плавных движений.

import random

p = random.random()
print(round(p, 3))
if random.random() < 0.3:
    print("Выпал бонус! Шанс был 30%")
temperature = random.uniform(18.0, 25.0)
print("Температура:", round(temperature, 1))

Приём if random.random() < 0.3 — это способ сказать «с вероятностью 30%». Число от 0 до 1 падает в отрезок от 0 до 0,3 примерно в трёх случаях из десяти. Так в играх задают шанс выпадения редкого предмета или промаха. Для вывода дробей используйте round или f-строки с форматом, иначе на экране окажется 16 знаков после запятой.

seed: повторяемая случайность

Функция seed(n) задаёт начальное значение генератора: после одинакового seed программа выдаёт одну и ту же последовательность случайных чисел. Это нужно для отладки и проверки — чтобы ошибку можно было воспроизвести.

import random

random.seed(42)
print(random.randint(1, 100), random.randint(1, 100))
random.seed(42)
print(random.randint(1, 100), random.randint(1, 100))

Обе строки вывода совпадут: генератор начал с одной точки и прошёл тот же путь. Именно так поведение seed описано в официальной документации модуля. Без seed начальное значение берётся из системного времени или других источников, поэтому каждый запуск разный. Важная оговорка: одинаковый seed гарантирует одинаковые числа только в одном и том же интерпретаторе. Редактор на нашем сайте работает на Skulpt, а не на стандартном Python, поэтому при том же seed числа могут отличаться от тех, что покажет IDLE на компьютере. О возможностях и ограничениях редактора — на странице про онлайн-редактор Python.

Игры на модуле random

Почти каждая учебная игра на Python начинается со случайности: компьютер загадывает число, бросает кубик или выбирает ход. Вот 3 короткие заготовки, которые легко развить в полноценные игры.

import random

secret = random.randint(1, 20)
print("Я загадал число от 1 до 20")

move = random.choice(["камень", "ножницы", "бумага"])
print("Мой ход:", move)

total = 0
for i in range(3):
    total += random.randint(1, 6)
print("Сумма трёх кубиков:", total)

Первая строка — основа игры «Угадай число»: дальше нужен цикл с input и подсказками «больше» и «меньше», пошаговый разбор есть в статье об игре «Угадай число». Вторая — ход компьютера в «Камень, ножницы, бумага». Третья — бросок нескольких кубиков для настольной игры. Любая такая игра становится интереснее, когда в ней есть счёт, жизни и уровни, а случайность лишь добавляет неожиданность. Случайность можно добавить и в рисунки: если черепашке задавать случайный цвет и угол, каждый запуск рисует новый узор. Идея рисовать с помощью программы и исследовать, что получится, восходит к языку Logo, который описал Сеймур Пейперт[3].

Симуляции: проверяем вероятность опытом

Симуляция — это опыт, который программа повторяет тысячи раз, чтобы увидеть закономерность. С модулем random можно за секунду подбросить монетку 10 000 раз и проверить, правда ли орлов выпадает примерно половина.

import random

heads = 0
for i in range(10000):
    if random.randint(0, 1) == 1:
        heads += 1
print("Орлов:", heads, "из 10000")
print("Доля:", round(heads / 10000, 2))

Ответ каждый раз немного разный, но доля почти всегда будет близка к 0,5. Попробуйте уменьшить число бросков до 10: разброс станет гораздо больше. Это и есть закон больших чисел в действии — его проходят в курсе теории вероятностей, а здесь его можно увидеть своими глазами. Таким же способом проверяют, какая сумма двух кубиков выпадает чаще всего: заведите список счётчиков на 13 ячеек и считайте суммы.

Как пробовать примеры в редакторе

Чтобы проверить любой пример, вставьте его в редактор вверху страницы и нажмите «▶ Запустить» или Ctrl+Enter: результат появится справа в окне «Вывод». Кнопка «Сбросить» возвращает стартовую программу.

Запустите стартовый код несколько раз подряд — кубики, питомец дня и монетка будут меняться. Затем поэкспериментируйте: замените randint(1, 6) на randint(1, 20), добавьте в список своих питомцев, вставьте random.seed(7) после import и убедитесь, что результат перестал меняться. Если программа содержит input(), под выводом откроется поле ввода. Черновик сохраняется в браузере, поэтому изменения не пропадут после перезагрузки страницы. Если появится ошибка, редактор покажет сообщение и подсказку на русском.

Когда модуль random не подходит

Модуль random не подходит там, где случайность должна быть непредсказуемой для злоумышленника или где результат обязан быть одинаковым при каждом запуске. В этих 4 случаях нужен другой подход.

  • Пароль из 12 символов или секретный код. Документация Python прямо предупреждает: для безопасности есть модуль secrets, а random предсказуем.
  • Автопроверка задачи с 5 тестами. Тест не может ожидать конкретное случайное число — либо задайте seed, либо проверяйте диапазон.
  • Честная лотерея на 100 участников с призами. Результат должен быть проверяемым и прозрачным, а не зависеть от программы на одном компьютере.
  • Обход всех 6 вариантов по очереди. Если нужно перебрать всё, используйте цикл for, а не надейтесь, что случай выберет каждый вариант.

«Компьютер умеет выдавать по-настоящему случайные числа обычной функцией». Это миф: random выдаёт псевдослучайные числа по формуле, и при одинаковом seed последовательность повторяется.

«Если монетка 5 раз подряд показала орла, в 6-й раз решка выпадет с большей вероятностью». Это не подтверждается: броски независимы, и шанс каждый раз остаётся 1 к 2 — симуляция выше это показывает.

Чего делать нельзя

Нельзя путать границы: randint(1, 6) включает 6, а randrange(1, 6) — нет. Остальные правила помогают избежать странного поведения программ со случайностью.

  • Нельзя называть свой файл random.py — Python подключит его вместо стандартного модуля.
  • Не следует присваивать результат shuffle: a = random.shuffle(a) превращает список в None.
  • Не стоит генерировать пароли этим модулем — для этого существует модуль secrets.
  • Не допускается вызывать choice для пустого списка — будет ошибка IndexError.
  • Нельзя писать тесты, которые ждут конкретного случайного значения без seed: они будут то проходить, то падать.
  • Не стоит вызывать seed с одним и тем же числом внутри цикла — каждый шаг будет выдавать одно и то же «случайное» число.

Источники

[1] The Python Tutorial (официальная документация Python 3). Python Software Foundation.

[2] van Rossum G., Warsaw B., Coghlan N. PEP 8 — Style Guide for Python Code. Python Software Foundation, 2001.

[3] Papert S. Mindstorms: Children, Computers, and Powerful Ideas. New York: Basic Books, 1980.

Вопросы и ответы

Почему при каждом запуске результат разный?

Если seed не задан, генератор берёт начальное значение из системных источников, например времени. Поэтому последовательность каждый раз начинается с новой точки.

Как получить случайное число от 1 до 10 включительно?

Используйте randint(1, 10) — обе границы входят. Аналог через randrange — randrange(1, 11).

Чем выбор одного элемента отличается от выборки?

Первая функция возвращает один элемент, вторая — список из нескольких разных элементов без повторов. Повторный одиночный выбор может вернуть тот же элемент.

Можно ли выбрать случайный цвет для черепашки?

Да: составьте список названий цветов и передайте выбранное название в команду color. Каждый запуск даст новый рисунок.

Работает ли случайность в Scratch?

Да, в Scratch для этого есть блок «выдать случайное от 1 до 10» в разделе «Операторы». Он, как и аналог в Python, включает обе границы.

Сколько знаков после запятой у random()?

Число хранится с точностью около 16 знаков. Для вывода его обычно округляют функцией round до 2–3 знаков.