Введение: предсказуемая случайность модуля random
В работе программиста часто нужны случайные числа: перемешать элементы списка, выбрать случайного победителя или сгенерировать тестовые данные. Для всего этого в Python есть встроенный модуль random
.
Главное, что нужно о нем знать: числа, которые он генерирует, на самом деле не случайны. Они псевдослучайны. Это значит, что их создает предсказуемый алгоритм. Если дать ему одну и ту же "отправную точку" (называемую seed
), он всегда будет создавать одну и ту же последовательность чисел.
Это не баг, а полезная особенность. Благодаря этому вы можете получать одинаковые "случайные" результаты снова и снова, что критически важно для отладки программ и воспроизводимости тестов.
Важное правило: никогда не используйте random
для задач, связанных с безопасностью, например, для генерации паролей, токенов или ключей шифрования. Он предсказуем, а значит, небезопасен. Для таких целей в Python есть специальный модуль secrets
.
В этой статье мы разберем:
Как работает
random.seed()
и почему это важно.Основные функции: от
randint
доchoice
иshuffle
.Практические примеры, где это пригодится.
В конце статьи вас ждет домашнее задание на GitHub с автоматической проверкой.
2. Под капотом: как random.seed() управляет хаосом
Как мы уже выяснили, «случайность» в модуле random
— это иллюзия, создаваемая алгоритмом. Ключ к управлению этой иллюзией — функция random.seed()
.
Что такое seed
?
Представьте, что генератор случайных чисел — это огромная книга с последовательностями чисел. seed
(в переводе «семя» или «зерно») — это номер страницы, с которой алгоритм начинает читать числа.
Если
seed
не указан, Python использует в качестве него какое-то постоянно меняющееся значение, например, текущее системное время. Поэтому при каждом запуске программы вы получаете новую, непредсказуемую последовательность.Если вы задаете
seed
вручную, например,random.seed(42)
, вы приказываете алгоритму каждый раз начинать с одной и той же «страницы». В результате вы всегда будете получать одинаковую последовательность «случайных» чисел.
Зачем это нужно?
Воспроизводимость — это не недостаток, а мощный инструмент для разработчика.
Отладка: Если ваша программа падает из-за определённой комбинации случайных данных, вы можете задать
seed
, чтобы гарантированно воспроизвести эту ошибку и исправить её.Тестирование: При написании тестов вы хотите, чтобы они были стабильными. Зафиксировав
seed
, вы обеспечиваете, что тесты всегда будут работать с одинаковым набором «случайных» входных данных.Научные расчёты: В машинном обучении или научных симуляциях
seed
позволяет другим исследователям в точности повторить ваши эксперименты и проверить результаты.
Давайте посмотрим на код:
import random
# --- Пример 1: Без seed ---
# При каждом запуске этого кода результат будет разным
print("Без использования seed:")
print(random.randint(1, 100))
print(random.randint(1, 100))
print("-" * 20)
# --- Пример 2: С одинаковым seed ---
# Этот блок кода всегда будет выводить одни и те же числа: 82 и 15
print("С использованием seed=42:")
random.seed(42)
print(random.randint(1, 100)) # Всегда 82
print(random.randint(1, 100)) # Всегда 15
print("-" * 20)
# Если мы сбросим seed на то же значение, последовательность начнется заново
print("Снова используем seed=42:")
random.seed(42)
print(random.randint(1, 100)) # Опять 82
print(random.randint(1, 100)) # Опять 15
Как видите, random.seed()
дает вам полный контроль над псевдослучайностью. Используйте это для создания предсказуемого и тестируемого кода.
3. Инструментарий random: Обзор основных функций
Теперь, когда мы разобрались с seed
, давайте рассмотрим основные инструменты, которые предоставляет модуль random
. Их можно условно разделить на две категории: функции для генерации чисел и функции для работы с последовательностями (например, списками).
Часть 1: Генерация чисел
Эти функции создают числа в заданных диапазонах.
-
random.random()
Возвращает случайное число с плавающей точкой в полуинтервале[0.0, 1.0)
. Это значит, что оно может быть равно0.0
, но всегда будет строго меньше1.0
. Это одна из базовых функций, на основе которой работают многие другие.# Получаем случайное число, например, 0.7634... value = random.random() print(value)
-
random.randint(a, b)
Возвращает случайное целое число из отрезка[a, b]
. Важно: оба конца диапазона,a
иb
, включаются в выборку. Это, пожалуй, самая часто используемая функция.# Бросаем шестигранный кубик dice_roll = random.randint(1, 6) print(f"Выпало: {dice_roll}")
-
random.uniform(a, b)
Аналогrandint
, но для чисел с плавающей точкой. Возвращает случайное число в диапазоне[a, b]
или[b, a]
.# Получаем случайную температуру в диапазоне temperature = random.uniform(15.5, 25.5) print(f"Температура: {temperature:.2f} °C")
Часть 2: Работа с последовательностями
Эти функции помогают выбирать элементы из последовательностей, перемешивать их или создавать случайные выборки.
-
random.choice(sequence)
Выбирает и возвращает один случайный элемент из непустой последовательности (например, списка или кортежа).players = ["Анна", "Борис", "Виктор", "Дарья"] winner = random.choice(players) print(f"Победитель: {winner}")
-
random.shuffle(x)
Перемешивает элементы последовательностиx
случайным образом. Внимание: функция изменяет саму последовательность (работает "in-place") и ничего не возвращает (возвращаетNone
). Не пытайтесь присвоить её результат переменной.cards = ["Туз", "Король", "Дама", "Валет"] print(f"До перемешивания: {cards}") random.shuffle(cards) # Перемешиваем сам список cards print(f"После перемешивания: {cards}")
-
random.sample(population, k)
Возвращает новый список длинойk
, содержащий уникальные элементы, выбранные из последовательностиpopulation
. Исходная последовательность при этом не меняется. Отлично подходит, когда нужно выбрать несколько элементов без повторений.# Выбираем 3 случайных уникальных приза prizes = ["Кружка", "Футболка", "Блокнот", "Ручка", "Стикер"] winners_prizes = random.sample(prizes, k=3) print(winners_prizes) # ['Ручка', 'Кружка', 'Футболка'] - порядок случайный
-
random.choices(population, weights=None, k=1)
Самая гибкая из функций выбора. Возвращает список изk
элементов, при этом элементы могут повторяться.
Самая интересная её возможность — необязательный параметрweights
. Он позволяет задать «вес» или вероятность для каждого элемента. Списокweights
должен иметь ту же длину, что иpopulation
.# Имитация выпадения предмета разной редкости loot = ["Обычный", "Редкий", "Эпический"] # Шансы: 80% для обычного, 15% для редкого, 5% для эпического chances = [0.80, 0.15, 0.05] # "Открываем" 10 сундуков result = random.choices(loot, weights=chances, k=10) print(result) # ['Обычный', 'Обычный', 'Редкий', 'Обычный', 'Обычный', 'Обычный', 'Эпический', 'Обычный', 'Редкий', 'Обычный']
Эта функция идеально подходит для имитации любых систем, где у разных исходов разные вероятности. Эта функция идеально подходит для имитации любых систем, где у разных исходов разные вероятности.
4. От теории к практике: Нескучные примеры
Знать функции — это хорошо, но настоящая сила модуля random
раскрывается, когда мы начинаем комбинировать их для решения реальных задач. Давайте рассмотрим несколько практических примеров.
Пример 1: Генератор паролей
Напишем функцию, которая создает надежный случайный пароль заданной длины, включающий буквы, цифры и специальные символы. Для этой задачи идеально подходит random.choices
, так как символы в пароле могут повторяться.
import random
import string
def generate_password(length: int) -> str:
"""Генерирует случайный пароль заданной длины."""
# 1. Создаем "алфавит" - строку со всеми возможными символами
all_chars = string.ascii_letters + string.digits + string.punctuation
# 2. Выбираем 'length' символов из нашего алфавита.
# random.choices позволяет символам повторяться, что нам и нужно.
password_list = random.choices(all_chars, k=length)
# 3. Соединяем список символов в одну строку
return "".join(password_list)
# Генерируем 16-значный пароль
new_password = generate_password(16)
print(f"Сгенерированный пароль: {new_password}")
Этот простой скрипт использует string
для получения готовых наборов символов и random.choices
для эффективного выбора. Результат — надежный и уникальный пароль при каждом запуске.
Пример 2: Симуляция броска костей
Представим, что мы пишем игру, где нужно имитировать бросок двух шестигранных кубиков (2d6). Здесь нам на помощь придет random.randint
.
import random
def roll_dice() -> int:
"""Имитирует бросок двух шестигранных кубиков и возвращает сумму."""
die1 = random.randint(1, 6)
die2 = random.randint(1, 6)
total = die1 + die2
print(f"Бросок: {die1} + {die2} = {total}")
return total
# Совершаем бросок
roll_result = roll_dice()
Эта функция не только возвращает результат, но и наглядно показывает, из чего он сложился. Просто и эффективно для моделирования случайных событий с равномерным распределением.
Пример 3 (продвинутый): Оценка числа π методом Монте-Карло
Этот пример демонстрирует, как с помощью случайных чисел можно решать математические задачи. Идея проста:
Представим себе квадрат со стороной 1 (площадь = 1).
Внутрь него впишем четверть круга с радиусом 1 (площадь = π * 1² / 4 = π/4).
Начнем наугад "бросать дротики" в этот квадрат, генерируя случайные точки (x, y) внутри него.
Соотношение числа дротиков, попавших в четверть круга, к общему числу бросков будет примерно равно соотношению их площадей:
(π/4) / 1
.Отсюда мы можем выразить π:
π ≈ 4 * (точки_в_круге / все_точки)
.
Для генерации координат точек идеально подходит random.random()
.
import random
def estimate_pi(num_points: int) -> float:
"""Оценивает число Пи методом Монте-Карло."""
points_in_circle = 0
for _ in range(num_points):
# Генерируем случайную точку (x, y) в квадрате 1x1
x = random.random()
y = random.random()
# Проверяем, находится ли точка внутри круга с радиусом 1
# по формуле x² + y² <= r² (где r=1)
if x**2 + y**2 <= 1:
points_in_circle += 1
# Вычисляем Пи по формуле
return 4 * points_in_circle / num_points
# Чем больше точек, тем точнее результат
pi_estimate = estimate_pi(1_000_000)
print(f"Примерная оценка числа Пи: {pi_estimate}")
Этот пример показывает, что модуль random
— это не просто игрушка, а мощный инструмент для численного моделирования и решения сложных задач.
5. Практика: Закрепляем знания на GitHub
Теория — это важно, но без практики она быстро забывается. Настоящее понимание приходит только тогда, когда вы начинаете применять знания для решения реальных задач.
Поэтому я подготовил для вас репозиторий на GitHub, где вас ждут 5 практических задач по темам, которые мы только что разобрали.
Ссылку оставил тут.
Анонс новых статей, полезные материалы, а так же если в процессе решения возникнут сложности, обсудить их или задать вопрос по статье можно в моём Telegram-сообществе.
Уверен, у вас все получится. Вперед, к практике!