$ sudo teach IT
Модуль 7 · Инструменты Python · Урок 7.2

Модуль random

Учимся управлять случайностью: числа, выбор из вариантов, перемешивание — и почему эта случайность на самом деле не совсем случайна

Теория~25 минутНовичокrandom.randint()random.choice()random.shuffle()random.seed()ГПСЧ

Представьте, что вы пишете игру: враги должны появляться в неожиданных местах, а из сундука — выпадать случайный предмет. Или собираете тест, где вопросы должны каждый раз перемешиваться. Всё это — задачи на случайность, и в Python за неё отвечает встроенный модуль random. Устанавливать его не нужно, он уже есть в стандартной библиотеке — достаточно написать import random, как вы уже делали с другими модулями.

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

Простыми словами. Представьте рулетку, у которой на ободе бесконечно много делений от нуля до единицы — крутанули, и стрелка остановилась где-то между ними. Иногда программе нужно именно такое «где-то между» — не целое число, а дробное, с плавающей точкой.

Термин. За это отвечает функция random.random() — она не принимает аргументов и всегда возвращает float из диапазона [0.0, 1.0): ноль включён, единица — нет. Если нужен диапазон пошире, есть random.uniform(a, b), где оба конца включены.

import random

x = random.random()
print(x)

y = random.uniform(1.5, 4.5)
print(y)
  • import random — подключаем модуль, без этой строки имя random внутри кода не существует.
  • x = random.random() — вызываем функцию random из модуля random (да, они называются одинаково: слева от точки — имя модуля, справа — имя функции внутри него) и сохраняем дробное число в переменную x.
  • print(x) — напечатает что-то вроде 0.374448871...: при каждом запуске программы число будет другим.
  • y = random.uniform(1.5, 4.5) — просим дробное число от 1.5 до 4.5, оба конца возможны.

Зачем нужен именно интервал от 0 до 1? Это удобная «единица измерения» вероятности. Например, событие с шансом 30% можно смоделировать так: if random.random() < 0.3: — условие сработает примерно в 30% запусков, потому что ровно 30% всех чисел на отрезке [0.0, 1.0) меньше 0.3.

Случайное целое число: randint() и randrange()

Простыми словами. Это игральный кубик: бросили — получили целое число от 1 до 6, без дробей и без вариантов вроде 3.7. В программах такое нужно постоянно: случайный уровень сложности, случайный индекс, случайная позиция на поле.

Термин. random.randint(a, b) возвращает случайное целое число из диапазона от a до b включительно — в отличие от привычного вам range(), здесь второе число тоже может выпасть. Если нужна более гибкая настройка — шаг или диапазон, не включающий правую границу, — есть random.randrange(start, stop, step), она ведёт себя как range(), только возвращает не последовательность, а одно случайное число из неё.

import random

dice = random.randint(1, 6)
print(dice)

even = random.randrange(0, 10, 2)
print(even)
  • dice = random.randint(1, 6) — просим целое число от 1 до 6, оба конца возможны: это ровно грани кубика.
  • even = random.randrange(0, 10, 2) — строим воображаемую последовательность range(0, 10, 2), то есть 0, 2, 4, 6, 8, и берём из неё одно случайное значение; 10 в неё не входит, как и в обычном range().

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

Случайный выбор из вариантов: choice() и choices()

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

Термин. random.choice(последовательность) вытаскивает один случайный элемент из списка, кортежа или строки. random.choices(последовательность, k=n) — более сильный вариант: возвращает список из n элементов, выбранных с повторениями (после каждого выбора «бумажка» кладётся обратно), а необязательный параметр weights позволяет сделать одни варианты вероятнее других.

import random

players = ["Аня", "Боря", "Вика"]
first = random.choice(players)
print(first)

rolls = random.choices([1, 2, 3, 4, 5, 6], k=3)
print(rolls)
  • first = random.choice(players) — берёт ровно один элемент из списка players и кладёт его в переменную; сам список players при этом не меняется.
  • rolls = random.choices([1, 2, 3, 4, 5, 6], k=3) — три раза подряд выбирает случайное число из списка граней кубика; поскольку выбор идёт с повторениями, результат может выглядеть как [4, 4, 2] — четвёрка вполне может выпасть дважды.

Где эта разница важна. choice() хорош, когда нужен один результат: случайный вопрос теста, случайный противник. choices() — когда нужно много значений сразу и повторы допустимы: набор символов для пароля, серия бросков кубика, случайные оценки для теста программы.

Без повторов: sample() и shuffle()

Простыми словами. Раздать карты из колоды — это выбор без повторов: одна и та же карта не может попасть в руки дважды. А перетасовать колоду — это оставить те же карты, но перемешать их порядок.

Термин. random.sample(последовательность, k=n) возвращает новый список из n случайных элементов, выбранных без повторений — исходная коллекция не меняется. random.shuffle(список) работает иначе: она не возвращает новый список, а перемешивает элементы прямо внутри переданного списка и отдаёт None.

import random

deck = ["6", "7", "8", "9", "10", "J", "Q", "K"]

hand = random.sample(deck, k=3)
print(hand)
print(deck)

random.shuffle(deck)
print(deck)
  • hand = random.sample(deck, k=3) — забирает 3 случайные карты без повторов и складывает их в новый список hand.
  • print(deck) после этого напечатает исходную колоду без изменений — sample() её не трогает, а только читает.
  • random.shuffle(deck) — перемешивает сам список deck на месте; строку вида deck = random.shuffle(deck) писать не нужно и нельзя: результат вызова — None, вы потеряете колоду.

Частая ловушка. Написать deck = random.shuffle(deck) и получить None вместо перемешанной колоды. Правильно — вызвать random.shuffle(deck) отдельной строкой, без присваивания: список изменится сам, а обращаться к нему нужно по прежнему имени deck.

Повторяемая случайность: random.seed()

Простыми словами. Кажется странным словосочетание «повторяемая случайность», но представьте генератор случайных чисел как очень длинный список чисел, записанный заранее. Если каждый раз начинать читать этот список с одной и той же страницы, вы будете получать одну и ту же последовательность — хотя числа в ней выглядят совершенно случайными.

Термин. На самом деле компьютер не умеет быть по-настоящему случайным: random использует генератор псевдослучайных чисел (сокращённо ГПСЧ) — алгоритм, который по стартовому числу вычисляет длинную последовательность значений, выглядящих беспорядочно. Это стартовое число называется seed (по-русски — «зерно», «затравка»), и задать его вручную можно функцией random.seed(число). Если вызвать её перед серией случайных вызовов, результат при каждом запуске программы будет одинаковым — это удобно для тестов и для проверки решений на этой платформе.

import random

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

random.seed(42)
print(random.randint(1, 100))
print(random.randint(1, 100))
  • random.seed(42) — задаёт стартовую точку генератора; число 42 здесь ничем не особенное, подошло бы любое.
  • Первые два print(random.randint(1, 100)) напечатают какие-то два числа, например 82 и 15.
  • После повторного random.seed(42) генератор снова стартует с той же точки, поэтому следующие два print() напечатают ровно 82 и 15 ещё раз — в том же порядке.

Совет. Без random.seed() генератор запускается от текущего момента времени, и последовательность каждый раз новая — это и есть «настоящая» случайность для игры или теста. А seed() нужен, когда важна воспроизводимость: например, чтобы проверяющая система на платформе могла заранее знать, какой результат ожидать от вашей функции.

Частые ошибки

Вызвать choice() или sample() на пустой коллекции. Тянуть бумажку из пустой шляпы не получится — Python сообщит об этом ошибкой:

import random

options = []
pick = random.choice(options)
# IndexError: Cannot choose from an empty sequence

Попросить у sample() больше элементов, чем есть в коллекции. Без повторов трижды одно и то же не выбрать:

import random

colors = ["red", "green", "blue"]
picked = random.sample(colors, k=5)
# ValueError: Sample larger than population or is negative

Сохранить результат shuffle() в переменную. Функция ничего не возвращает, она меняет список на месте:

import random

numbers = [1, 2, 3, 4, 5]
numbers = random.shuffle(numbers)
print(numbers) # None, список потерян

Что важно запомнить

  • import random — модуль встроенный, устанавливать ничего не нужно
  • random.random() — дробное число из [0.0, 1.0); random.uniform(a, b) — дробное из [a, b]
  • random.randint(a, b) — целое число, оба конца включены; random.randrange(start, stop, step) — как range(), но одно случайное значение
  • random.choice(seq) — один случайный элемент; random.choices(seq, k=n) — n элементов с повторениями
  • random.sample(seq, k=n) — n элементов без повторений, возвращает новый список, исходный не трогает
  • random.shuffle(lst) — перемешивает список на месте и возвращает None, присваивать результат не нужно
  • random.seed(число) — фиксирует стартовую точку генератора, чтобы последовательность повторялась
  • Это генератор псевдослучайных чисел: для игр и тестов достаточно, для паролей и защиты данных используют модуль secrets

Проверьте себя

7 вопросов

Симулятор броска кубиков

Напишите функцию roll_dice(), которая симулирует бросок двух шестигранных кубиков. Функция должна возвращать кортеж из трёх значений: (значение_первого_кубика, значение_второго_кубика, сумма).

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

Генератор случайного пароля

Premium