Полное руководство по модулю random в Python

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

1. Введение в псевдослучайные числа: инициализация, seed и базовая функция random

Введение в псевдослучайные числа: инициализация, seed и базовая функция random

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

В этой статье мы разберем концепцию псевдослучайности, познакомимся с генератором Вихрь Мерсенна, научимся управлять хаосом с помощью зерна (seed) и изучим фундамент всего модуля — функцию random().

Парадокс случайности в детерминированном мире

Компьютеры детерминированы. Это означает, что если вы дадите компьютеру одни и те же входные данные и попросите выполнить одну и ту же программу, он всегда выдаст одинаковый результат. всегда равно . Это свойство делает компьютеры надежными, но создает огромную проблему, когда нам нужна непредсказуемость.

Истинная случайность (True Randomness) в природе существует — например, распад радиоактивного атома или атмосферный шум. Но для обычного процессора создание случайного числа — это невыполнимая задача без внешнего оборудования.

Поэтому программисты используют псевдослучайные числа (Pseudo-Random Numbers). Это последовательности чисел, которые выглядят случайными и проходят статистические тесты на случайность, но на самом деле генерируются с помощью строгого математического алгоритма.

!Иллюстрация различия между истинной случайностью природных явлений и алгоритмической природой псевдослучайных чисел.

Как работают генераторы псевдослучайных чисел (PRNG)

Генератор псевдослучайных чисел (PRNG — Pseudo-Random Number Generator) — это алгоритм, который начинает с исходного числа (называемого seed или зерно) и производит последовательность чисел, используя математические операции.

Чтобы понять принцип, рассмотрим простейший пример алгоритма — Линейный конгруэнтный метод. Хотя Python использует более сложный алгоритм, этот пример идеально подходит для понимания сути процесса.

Формула генерации следующего числа выглядит так:

Где:

  • — следующее псевдослучайное число, которое мы хотим получить.
  • — текущее число (или предыдущее сгенерированное).
  • — множитель (константа).
  • — приращение (константа).
  • — модуль (делитель, определяющий диапазон значений).
  • — операция взятия остатка от деления на .
  • Суть проста: мы берем число, умножаем его, прибавляем другое число, делим и берем остаток. Полученный результат становится входным данным для следующего шага. Если мы знаем начальное число () и коэффициенты, мы можем предсказать всю последовательность наперед.

    Вихрь Мерсенна (Mersenne Twister)

    Модуль random в Python использует алгоритм под названием Вихрь Мерсенна (Mersenne Twister), а именно его версию MT19937.

    Его ключевые особенности:

  • Огромный период: . Это означает, что последовательность начнет повторяться только через невообразимо огромное количество шагов.
  • Высокая скорость: Он очень быстро генерирует числа.
  • Статистическое качество: Числа, созданные этим алгоритмом, отлично подходят для моделирования и симуляций (метод Монте-Карло).
  • Однако важно помнить: Вихрь Мерсенна не является криптографически стойким. Это значит, что если злоумышленник соберет достаточное количество сгенерированных чисел, он сможет вычислить внутреннее состояние генератора и предсказать все будущие числа.

    > Никогда не используйте модуль random для генерации паролей, токенов безопасности или ключей шифрования. Для этих целей в Python существует модуль secrets.

    Базовая функция: random.random()

    Теперь перейдем к практике. Основой всего модуля является функция random(). Почти все остальные функции модуля (генерация целых чисел, выбор элемента из списка и т.д.) внутри себя используют random().

    Импорт и использование

    Функция random.random() возвращает случайное число с плавающей точкой (float) в диапазоне .

    Обратите внимание на скобки в математической записи диапазона:

  • Квадратная скобка [ означает, что включено в диапазон.
  • Круглая скобка ) означает, что исключено из диапазона.
  • То есть, теоретически вы можете получить 0.0, но никогда не получите ровно 1.0 (максимальное значение будет очень близко к единице, например, 0.999999...).

    Зачем нам число от 0 до 1?

    Может показаться, что число от 0 до 1 не очень полезно, если вам нужно бросить кубик (1-6). Однако, имея число в этом диапазоне, мы можем легко масштабировать его до любого другого интервала.

    Например, если мы хотим получить число от 0 до 100:

    Где:

  • — итоговое число в диапазоне от 0 до 100.
  • — результат функции random.random() (от 0 до 1).
  • В Python это выглядело бы так:

    Управление хаосом: random.seed()

    Мы выяснили, что псевдослучайные числа генерируются на основе формул. Это приводит нас к важному инструменту — инициализации генератора, или seeding.

    Функция random.seed(a=None) инициализирует генератор случайных чисел.

    Автоматическая инициализация

    Если вы не вызываете seed() явно, Python делает это за вас при первом импорте модуля. В качестве зерна (начального значения) обычно используется текущее системное время. Поскольку время постоянно меняется, при каждом запуске программы вы получаете разные последовательности чисел.

    Ручная инициализация (Воспроизводимость)

    Иногда нам нужно, чтобы «случайная» последовательность была одинаковой при каждом запуске программы. Это критически важно для:

  • Отладки (Debugging): Если ошибка в программе возникает при определенном «случайном» значении, вы хотите воспроизвести её снова.
  • Научных экспериментов: Чтобы другие ученые могли проверить ваши результаты моделирования.
  • Обучения нейросетей: Чтобы сравнивать эффективность разных архитектур на одних и тех же данных.
  • Если мы передадим в seed() конкретное число (или строку, или байты), генератор перейдет в определенное состояние и будет выдавать одну и ту же последовательность.

    ![Иллюстрация принципа детерминизма при использовании одинакового seed: разные машины выдают одинаковый результат.

    Рассмотрим пример:

    Результат выполнения этого кода будет примерно таким:

    Как видите, последовательности идентичны. Число 42 здесь выбрано произвольно, это может быть любое целое число, строка или байтовый объект. Главное — оно должно быть неизменным.

    Локальный и глобальный Random

    Важно понимать, что функции random.random(), random.seed() и другие, которые мы вызываем напрямую из модуля, используют один общий глобальный экземпляр генератора Random.

    Если вы пишете большую программу с множеством потоков или модулей, изменение seed в одном месте повлияет на генерацию случайных чисел во всей программе. В продвинутых сценариях (о которых мы поговорим в будущих статьях) лучше создавать изолированные экземпляры класса random.Random.

    Резюме

    * Компьютеры используют псевдослучайные числа (PRNG), которые генерируются детерминированными алгоритмами. * Python использует алгоритм Вихрь Мерсенна (Mersenne Twister). * Функция random.random() возвращает float в диапазоне . * Функция random.seed() позволяет зафиксировать начальное состояние генератора, обеспечивая воспроизводимость результатов. * Модуль random не подходит для криптографии. Для паролей используйте модуль secrets.

    В следующей статье мы отойдем от теории и начнем работать с целыми числами, изучая функции randint, randrange и их особенности.

    2. Работа с целыми числами: нюансы использования randint и randrange

    Работа с целыми числами: нюансы использования randint и randrange

    В предыдущей статье мы разобрали фундамент генерации случайных чисел — функцию random(), которая возвращает дробное число от 0.0 до 1.0. Это мощный инструмент, но в повседневном программировании нам гораздо чаще требуются целые числа. Бросок игральной кости, выбор победителя лотереи по номеру билета, генерация случайного индекса массива — всё это требует работы с int.

    В модуле random есть два основных инструмента для этих задач: randrange и randint. На первый взгляд они делают одно и то же, но дьявол кроется в деталях, а именно — в границах диапазонов. В этой статье мы разберем их различия, внутреннее устройство и математику, стоящую за преобразованием дробного хаоса в целочисленный порядок.

    Функция randrange: Python-стиль

    Если вы уже знакомы с циклом for и встроенной функцией range(), то random.randrange() покажется вам родным братом. Логика работы с границами здесь абсолютно идентична стандартному range().

    Функция имеет три формы вызова:

  • randrange(stop)
  • randrange(start, stop)
  • randrange(start, stop, step)
  • Главное правило: правая граница исключена

    Самое важное, что нужно запомнить: верхняя граница (stop) никогда не включается в результат. Это называется полуоткрытым интервалом.

    Математически диапазон для randrange(a, b) записывается так:

    Где:

  • — множество возможных значений.
  • — начальное значение (включительно).
  • — конечное значение (исключительно).
  • — квадратная скобка означает включение границы.
  • — круглая скобка означает исключение границы.
  • Примеры использования

    Использование randrange идеально подходит, когда вы работаете со списками или индексами, так как длина списка len(list) всегда на 1 больше последнего индекса, что отлично ложится на логику исключения верхней границы.

    Функция randint: Интуитивный подход

    Функция random.randint(a, b) была создана для тех случаев, когда логика range() кажется неудобной. Например, когда мы говорим «загадай число от 1 до 10», мы обычно подразумеваем, что 10 тоже может быть загадано.

    Главное отличие: обе границы включены

    В randint верхняя граница включается в результат. Это закрытый интервал.

    Математическая запись для randint(a, b):

    Где:

  • — множество возможных значений.
  • — начальное значение (включительно).
  • — конечное значение (включительно).
  • Пример:

    Если бы мы использовали randrange для кубика, нам пришлось бы писать randrange(1, 7), что выглядит менее очевидно для человека, читающего код.

    !Визуальное сравнение интервалов: randrange исключает верхнюю границу, а randint включает её.

    Что происходит «под капотом»?

    Вам может показаться, что randint и randrange — это два совершенно разных механизма. На самом деле, randint — это просто удобная обертка (alias) над randrange.

    Если заглянуть в исходный код модуля random в стандартной библиотеке Python, можно увидеть примерно следующее:

    То есть, когда вы вызываете randint(1, 10), Python тихо преобразует это в randrange(1, 11). Это знание помогает избежать путаницы: randint существует только для удобства чтения кода (syntactic sugar).

    Математика преобразования float в int

    Как именно компьютер превращает случайное число 0.847... (результат random()) в целое число, например, от 1 до 10? Используется формула масштабирования и сдвига.

    Упрощенная формула генерации целого числа в диапазоне выглядит так:

    Где:

  • — итоговое целое случайное число.
  • — нижняя граница диапазона.
  • — верхняя граница диапазона (исключая её саму).
  • — случайное число с плавающей точкой от 0.0 до 1.0 (результат random()).
  • — ширина диапазона (количество возможных вариантов).
  • — операция округления вниз до ближайшего целого (floor).
  • Пример расчета: Допустим, нам нужно число от 5 до 10 (не включая 10). Ширина диапазона . Генератор выдал .

  • Умножаем на ширину: .
  • Округляем вниз: .
  • Прибавляем к началу диапазона: .
  • Результат: 8.

    Примечание: Реальная реализация в Python немного сложнее для обеспечения равномерного распределения на очень больших диапазонах, но принцип остается тем же.

    Нюанс с параметром step (Шаг)

    Уникальная особенность randrange — возможность задать шаг step. Это позволяет генерировать случайные числа, подчиняющиеся определенному паттерну, без лишних проверок.

    Задача: Получить случайное нечетное число от 1 до 100.

    Решение через randrange:

    Решение через randint (менее эффективно):

    Использование randrange с шагом делает код чище и быстрее, так как не требует циклов while или дополнительной арифметики.

    Распространенные ошибки (Exceptions)

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

    1. Пустой диапазон

    Если нижняя граница больше или равна верхней (для randrange), возникнет ошибка.

    Однако для randint(5, 5) ошибки не будет, так как верхняя граница включена, и единственным возможным результатом будет 5.

    2. Дробные аргументы

    Хотя старые версии Python могли прощать передачу float в эти функции, современные версии требуют строго int.

    Резюме

    Подводя итог, составим таблицу выбора инструмента:

    | Задача | Функция | Пример | | :--- | :--- | :--- | | Нужен индекс для списка | randrange(stop) | randrange(len(lst)) | | Диапазон с включенной верхней границей (кубик, рулетка) | randint(a, b) | randint(1, 6) | | Нужны только четные/нечетные числа или числа с шагом | randrange(start, stop, step) | randrange(0, 100, 5) |

    Теперь вы знаете, как управлять целочисленной случайностью. Но что, если нам нужно выбрать не просто число, а случайный элемент из списка, или перемешать колоду карт? Об этом мы поговорим в следующей статье, посвященной функциям choice, shuffle и sample.

    3. Операции с последовательностями: choice, shuffle и выборка с весами

    Операции с последовательностями: choice, shuffle и выборка с весами

    В предыдущих статьях мы научились генерировать случайные числа: от простых дробных значений от 0 до 1 до целых чисел в заданном диапазоне. Это фундамент, на котором строится вся работа с модулем random. Однако в реальной жизни и в программировании мы редко оперируем абстрактными числами. Чаще всего нам нужно взаимодействовать с уже существующими данными: выбрать случайного победителя из списка участников, раздать карты из колоды или сгенерировать случайный пароль из набора символов.

    В этой статье мы переходим от генерации чисел к операциям с последовательностями. Мы разберем три кита, на которых держится эта тема: одиночный выбор (choice), перемешивание (shuffle) и выборка (sample/choices). Также мы углубимся в математику вероятностей, чтобы понять, как работают веса.

    Одиночный выбор: random.choice()

    Самая простая и часто используемая операция — это выбор одного случайного элемента из непустой последовательности. Для этого используется функция random.choice(seq).

    Аргументом seq может быть любой объект, поддерживающий индексацию: список (list), кортеж (tuple) или строка (str).

    Пример использования

    Представьте, что вы пишете бота для игры «Камень, Ножницы, Бумага»:

    Как это работает внутри?

    Фактически, choice — это удобная обертка над генерацией случайного индекса. Логику этой функции можно выразить так:

    Важное ограничение

    Если вы передадите в choice пустую последовательность (например, пустой список []), Python выбросит ошибку IndexError. Всегда проверяйте, есть ли данные в вашем списке перед вызовом этой функции.

    Перемешивание: random.shuffle()

    Вторая по популярности задача — изменение порядка элементов. Это классическая задача тасования колоды карт. Функция random.shuffle(x) перемешивает последовательность x на месте (in-place).

    Особенность in-place изменения

    Это критически важный момент, на котором спотыкаются многие новички. Функция shuffle ничего не возвращает (точнее, возвращает None). Она меняет сам объект, который вы ей передали.

    !Визуализация работы функции shuffle, изменяющей исходный список.

    Алгоритм Фишера-Йетса

    Под капотом Python использует алгоритм тасования Фишера-Йетса (Fisher-Yates shuffle). Это эффективный алгоритм, который гарантирует, что каждая возможная перестановка элементов имеет равную вероятность появления. Сложность этого алгоритма — , где — количество элементов в списке.

    Ограничение неизменяемости

    Так как shuffle пытается изменить объект, он не будет работать с неизменяемыми типами данных, такими как кортежи (tuple) или строки (str). Попытка сделать random.shuffle((1, 2, 3)) приведет к ошибке TypeError.

    Если вам нужно перемешать неизменяемый объект, сначала преобразуйте его в список, перемешайте, а затем соберите обратно:

    Множественная выборка: sample vs choices

    Часто нам нужно выбрать не один элемент, а несколько. Здесь модуль random предлагает два инструмента, разница между которыми фундаментальна: sample и choices.

    1. random.sample(): Выборка без возвращения

    Функция random.sample(population, k) возвращает список длиной k из уникальных элементов последовательности population.

    Ключевая фраза — без возвращения (without replacement). Это значит, что один и тот же элемент не может быть выбран дважды. Это идеально подходит для лотерей или раздачи карт, где одна карта не может оказаться у двух игроков одновременно.

    Особенности: * Возвращает новый список. * Исходная последовательность не меняется. * Если k больше, чем размер популяции, возникнет ошибка ValueError.

    2. random.choices(): Выборка с возвращением и весами

    Функция random.choices(population, weights=None, k=1) (появилась в Python 3.6) выбирает элементы с возвращением (with replacement). Это значит, что после выбора элемента он «возвращается» в мешок и может быть выбран снова.

    Пример — генерация пароля длиной 8 символов из букв алфавита. Буква 'A' может встретиться в пароле несколько раз.

    Управление вероятностью: Веса (Weights)

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

    Мы можем задать вероятность выпадения для каждого элемента. Математически вероятность выбора элемента рассчитывается так:

    Где: * — вероятность выбора -го элемента. * — вес (weight) -го элемента, который мы задаем. * — сумма весов всех элементов в списке.

    Простыми словами: чем больше вес элемента относительно суммы всех весов, тем выше шанс, что он выпадет.

    Практический пример: Лутбокс в игре

    Допустим, у нас есть три предмета: «Монета» (часто), «Зелье» (редко), «Меч» (очень редко).

    Давайте посчитаем вероятности для этого примера:

  • Сумма весов: .
  • Вероятность выпадения Монеты: (или 62.5%).
  • Вероятность выпадения Меча: (или 6.25%).
  • Cum_weights (Кумулятивные веса)

    В функции choices есть также параметр cum_weights. Это оптимизация для математиков и высоконагруженных систем. Вместо обычных весов вы передаете накопленную сумму.

    Если weights = [10, 5, 1], то cum_weights будет [10, 15, 16] (10, 10+5, 10+5+1). Python внутри преобразует обычные веса в кумулятивные для ускорения поиска, поэтому использование cum_weights напрямую работает немного быстрее, но менее наглядно для человека.

    Сводная таблица операций

    Чтобы не запутаться, когда и что использовать, взгляните на эту таблицу:

    | Функция | Повторы элементов? | Изменяет исходный список? | Учитывает веса? | Пример использования | | :--- | :--- | :--- | :--- | :--- | | choice(seq) | Нет (выбор 1 шт) | Нет | Нет | Подброс монетки | | shuffle(seq) | - | Да | Нет | Тасование карт | | sample(seq, k) | Нет (уникальные) | Нет | Нет | Лотерея 6 из 49 | | choices(seq, k) | Да (возможны дубли) | Нет | Да | Генерация пароля, лут в играх |

    Резюме

    В этой статье мы освоили работу с коллекциями в модуле random:

    * Используйте choice, чтобы достать один элемент. * Используйте shuffle, чтобы перемешать изменяемый список (помните, что он возвращает None). * Используйте sample, если вам нужен набор уникальных элементов (выборка без возвращения). * Используйте choices, если вам нужна выборка с возвращением или если вы хотите управлять вероятностями через систему весов.

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

    4. Вещественные числа и научные вычисления: равномерное, нормальное и другие распределения

    Вещественные числа и научные вычисления: равномерное, нормальное и другие распределения

    Мы уже научились генерировать простые случайные числа и выбирать элементы из списков. Если ваша задача — написать карточную игру или генератор паролей, то изученного материала вполне достаточно. Но модуль random в Python способен на большее. Он является мощным инструментом для научного моделирования, симуляций и статистики.

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

    Равномерное распределение: random.uniform()

    Начнем с того, что нам уже знакомо, но под новым углом. Базовая функция random.random() генерирует число от 0.0 до 1.0, где каждое значение равновероятно. Это частный случай равномерного распределения (Uniform Distribution).

    Если нам нужно вещественное число (float) в произвольном диапазоне , мы используем функцию random.uniform(a, b).

    Математическая суть

    Функция плотности вероятности для равномерного распределения выглядит так:

    Где:

  • — плотность вероятности в точке .
  • — нижняя граница диапазона.
  • — верхняя граница диапазона.
  • — длина интервала.
  • Простыми словами: вероятность выпадения числа одинакова на всем отрезке от до . «Горка» вероятности плоская, как стол.

    Пример использования

    Обратите внимание: в отличие от randrange, функция uniform включает верхнюю границу (хотя вероятность выпадения точного значения для вещественных чисел ничтожно мала).

    Нормальное распределение: random.gauss()

    Самое важное распределение в природе — нормальное, или Гауссовское распределение. График его плотности вероятности напоминает колокол.

    Оно описывает величины, на которые влияет множество мелких независимых факторов. Примеры:

  • Рост и вес людей.
  • IQ тесты.
  • Ошибки в измерениях приборов.
  • Отклонение детали от идеального размера на заводе.
  • В модуле random для этого есть функция random.gauss(mu, sigma).

    !Визуальное различие между равномерным распределением (все числа равновероятны) и нормальным распределением (числа стремятся к среднему значению).

    Параметры функции

    Функция принимает два аргумента, определяющих форму «колокола»:

  • mu (, мю) — Среднее значение. Это центр колокола, то самое число, вокруг которого кучкуются результаты. Если мы моделируем рост мужчин, может быть равно 175 см.
  • sigma (, сигма) — Стандартное отклонение. Это ширина колокола. Чем больше , тем более разбросаны значения. Если маленькое, почти все числа будут очень близки к .
  • Формула Гаусса

    Для понимания, вот формула плотности вероятности нормального распределения:

    Где:

  • — вероятность появления значения .
  • — математическое ожидание (среднее значение).
  • — стандартное отклонение.
  • — число Пи (примерно 3.14159).
  • — число Эйлера (основание натурального логарифма, примерно 2.718).
  • Практика: Генератор персонажей

    Допустим, мы создаем RPG и хотим сгенерировать рост NPC (неигровых персонажей), чтобы они выглядели реалистично, а не были одного роста.

    Большинство значений будет в диапазоне (от 167 до 183). Значения за пределами () будут выпадать крайне редко.

    > Примечание: В модуле есть также функция random.normalvariate(mu, sigma). Она делает то же самое, но использует другой алгоритм. gauss работает чуть быстрее, поэтому обычно предпочитают её.

    Экспоненциальное распределение: random.expovariate()

    Если нормальное распределение отвечает на вопрос «сколько это весит?», то экспоненциальное часто отвечает на вопрос «сколько ждать?».

    Оно описывает время между событиями в пуассоновском процессе (процесс, где события происходят непрерывно и независимо друг от друга). Примеры:

  • Время между приходом двух покупателей в кассу.
  • Время до распада радиоактивного атома.
  • Срок службы лампочки до перегорания.
  • Функция: random.expovariate(lambd).

    Параметр Lambda

    Единственный аргумент — lambd (). Это интенсивность событий (количество событий в единицу времени).

    Среднее время ожидания события равно обратной величине от лямбды:

    Где:

  • — среднее значение (например, среднее время ожидания).
  • — параметр интенсивности.
  • Если покупатели приходят в среднем каждые 5 минут, то среднее время ожидания — 5. Значит, .

    Особенность этого распределения: короткие интервалы выпадают часто, а очень длинные — редко, но теоретически возможны.

    Треугольное распределение: random.triangular()

    Иногда мы не знаем точного закона распределения, но знаем границы и «наиболее вероятное» значение. В таких случаях, особенно в менеджменте и оценке рисков, используют треугольное распределение.

    Функция: random.triangular(low, high, mode).

    Параметры:

  • low: Минимальное значение.
  • high: Максимальное значение.
  • mode: Мода (вершина треугольника), значение, которое выпадает чаще всего.
  • По умолчанию mode находится ровно посередине между low и high, создавая симметричный треугольник.

    Пример: Оценка сроков проекта

    Вы спрашиваете программиста: «Сколько времени займет задача?»

  • Оптимистично: 2 дня (low).
  • Пессимистично: 10 дней (high).
  • Реалистично: 4 дня (mode).
  • Другие распределения

    Модуль random содержит и более специфические функции для научных вычислений:

    * lognormvariate(mu, sigma) — Логнормальное распределение. Если логарифм величины имеет нормальное распределение. Используется в геологии, экономике. * betavariate(alpha, beta) — Бета-распределение. Часто используется для моделирования вероятностей (от 0 до 1) в байесовской статистике. * gammavariate(alpha, beta) — Гамма-распределение. Обобщение экспоненциального распределения. * paretovariate(alpha) — Распределение Парето. Описывает правило 80/20 (распределение богатства, размеры городов).

    Когда random недостаточно: NumPy

    Важно понимать границы применимости модуля random. Он написан на чистом Python (с вставками C) и отлично подходит для генерации одиночных чисел или небольших серий.

    Однако, если вам нужно сгенерировать миллион чисел из нормального распределения для обучения нейросети или сложного моделирования, модуль random будет работать медленно.

    В мире Data Science стандартом является библиотека NumPy.

    Пример сравнения (концептуально):

    NumPy генерирует массивы чисел целиком на языке C, что в десятки раз быстрее циклов Python.

    Резюме

    В этой статье мы вышли за рамки простого «броска кубика» и научились моделировать реальность:

  • random.uniform(a, b) — Равновероятный выбор вещественного числа в диапазоне. Подходит для простых симуляций.
  • random.gauss(mu, sigma) — Нормальное распределение («колокол»). Идеально для природных величин (рост, вес, ошибки).
  • random.expovariate(lambda) — Экспоненциальное распределение. Используется для моделирования времени ожидания.
  • random.triangular(low, high, mode) — Удобно, когда известны только границы и наиболее вероятный исход.
  • Теперь вы обладаете полным арсеналом инструментов модуля random. В следующей, заключительной части курса, мы рассмотрим продвинутые техники: как расширять класс Random, создавать свои генераторы и управлять состоянием (state) для сохранения и загрузки прогресса генерации.

    5. Продвинутое использование: класс SystemRandom, управление состоянием и безопасность

    Продвинутое использование: класс SystemRandom, управление состоянием и безопасность

    Мы прошли долгий путь от генерации простых чисел до моделирования сложных вероятностных распределений. Теперь вы умеете бросать виртуальные кубики, тасовать колоды и даже создавать NPC для игр с реалистичными характеристиками. Однако, как профессор, я обязан предупредить вас: в мире случайности есть темные углы, куда не стоит заходить с обычным фонариком.

    В этой заключительной статье курса мы разберем критически важные аспекты: криптографическую безопасность, сохранение «судьбы» генератора (управление состоянием) и объектно-ориентированный подход к случайности.

    Почему модуль random небезопасен?

    Во всех предыдущих статьях мы использовали стандартный генератор Python, основанный на алгоритме Вихрь Мерсенна (Mersenne Twister). Как мы уже обсуждали, это генератор псевдослучайных чисел (PRNG). Ключевое слово — псевдо.

    Его работа полностью определяется начальным состоянием (seed). Если вы знаете состояние генератора в определенный момент времени, вы можете предсказать все будущие числа и вычислить все предыдущие.

    Внутреннее состояние Вихря Мерсенна состоит из 624 32-битных целых чисел. Это означает, что злоумышленнику достаточно собрать всего 624 последовательных значения random.randint(), чтобы полностью клонировать ваш генератор.

    > Никогда не используйте стандартные функции random для генерации паролей, токенов аутентификации, ключей шифрования или номеров выигрышных билетов в реальных лотереях.

    Класс SystemRandom: Истинная случайность

    Если нам нужна безопасность, нам нужно отказаться от детерминированных алгоритмов и обратиться к хаосу реального мира. В Python для этого существует класс random.SystemRandom.

    Этот класс имеет тот же интерфейс, что и обычный модуль random. У него есть методы randint, choice, shuffle и другие. Но внутри он работает совершенно иначе.

    Откуда берутся числа?

    SystemRandom берет данные из операционной системы (функция os.urandom()). Операционная система собирает «энтропию» (шум) из аппаратных источников: * Интервалы между нажатиями клавиш. * Движения мыши. * Сетевой шум. * Тепловой шум процессора.

    Это делает числа криптографически стойкими (CSPRNG). Их невозможно предсказать, даже имея суперкомпьютер.

    !Сравнение источников энтропии для обычного генератора и системного криптостойкого генератора.

    Как использовать SystemRandom

    Использование почти идентично стандартному, но сначала нужно создать экземпляр класса:

    Особенности SystemRandom

  • Игнорирование seed: Метод seed() у этого класса ничего не делает. Вы не можете сделать эту случайность воспроизводимой, так как она зависит от физических процессов в компьютере.
  • Скорость: SystemRandom работает значительно медленнее обычного random, так как обращение к системным источникам энтропии — дорогая операция. Используйте его только там, где важна безопасность.
  • Модуль secrets

    Начиная с Python 3.6, для задач безопасности рекомендуется использовать специальный модуль secrets. По сути, это удобная обертка над SystemRandom, предоставляющая функции специально для генерации токенов и паролей.

    Управление состоянием: getstate и setstate

    Вернемся к обычному, не криптографическому random. Иногда нам нужно не просто задать начальное зерно (seed), а буквально «заморозить» генератор, сохранить его состояние на диск, а потом, спустя неделю, загрузить и продолжить генерацию с того же места.

    Это полезно для: * Сохранения игр: Чтобы процедурно генерируемый мир не менялся после загрузки сохранения. * Длительных научных симуляций: Чтобы можно было поставить эксперимент на паузу.

    Для этого используются функции getstate() и setstate().

    Структура состояния

    Функция getstate() возвращает объект (обычно кортеж), содержащий все внутренние данные генератора.

    Вывод будет таким, что число после восстановления состояния будет идентично числу, которое выпало сразу после сохранения.

    Объектно-ориентированный подход: random.Random

    До сих пор мы использовали функции модуля напрямую: random.randint(...). Но на самом деле модуль random создает один скрытый глобальный экземпляр генератора и использует его.

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

    Лучшее решение — создавать свои собственные, изолированные генераторы. Это делается через класс random.Random.

    Этот подход позволяет иметь несколько независимых потоков случайных чисел. Например, один генератор отвечает за поведение врагов, другой — за генерацию ландшафта, третий — за погоду. Это значительно упрощает отладку.

    Математика периода генератора

    В завершение курса давайте взглянем на мощь алгоритма, который мы использовали. Период Вихря Мерсенна (количество чисел до того, как последовательность начнет повторяться) составляет:

    Где: * — период генератора. * — двойка, возведенная в степень 19937.

    Это число астрономически огромно. Если бы мы генерировали миллиард чисел в секунду, нам потребовалось бы времени больше, чем существует Вселенная, чтобы исчерпать этот цикл. Именно поэтому Вихрь Мерсенна является стандартом в индустрии для задач, не связанных с криптографией.

    Заключение курса

    Поздравляю! Вы прошли полный курс по модулю random в Python.

    Мы изучили:

  • Основы: Как компьютер создает иллюзию случайности и зачем нужен seed.
  • Целые числа: Различия между randint и randrange.
  • Последовательности: Как выбирать (choice), перемешивать (shuffle) и делать выборки (sample, choices).
  • Распределения: Как моделировать реальный мир с помощью gauss, uniform и expovariate.
  • Безопасность: Почему для паролей нужен SystemRandom и как управлять состоянием генератора.
  • Теперь хаос находится под вашим полным контролем. Используйте эти знания мудро, пишите надежный код и пусть random всегда будет на вашей стороне!