random — генератор псевдо-случайных чисел

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

  • генерация случайных целых и вещественных чисел, в том числе и из некоторых вероятностных распределений;
  • генерация случайных перестановок и выборок;
  • создание объектов-генераторов и работа с их внутренним состоянием.

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

Генератор и его внутреннее состояние

random.Random()
класс, который позволяет создать экземпляр генератора псевдослучайных чисел.

Иногда бывает удобно, а иногда необходимо иметь собственный экземпляр генератора:

>>> import random
>>> 
>>> random.random()
0.010536187184681856
>>> 
>>> rng = random.Random()
>>> rng.random()
0.21005898777401233

Более того, таких экземпляров может быть несколько:

>>> rng_1 = random.Random()
>>> rng_2 = random.Random()
>>> 
>>> rng_1.randint(0, 10)
9
>>> rng_2.random()
0.6955269794131647

Причем все эти генераторы могут иметь разное внутреннее состояние, что очень удобно для воспроизводимости результатов:

>>> rng_1.seed(0)
>>> rng_1.randint(0, 100)
49
>>> 
>>> rng_2.seed(1)
>>> rng_2.randint(0, 100)
17
>>> rng_1.seed(0)
>>> rng_1.randint(0, 100)
49
>>> 
>>> rng_2.seed(1)
>>> rng_2.randint(0, 100)
17
random.SystemRandom([seed])
класс, который позволяет создать экземпляр генератора, использующим в качестве источника случайности (энтропии) ресурсы операционной системы.
>>> rng = random.SystemRandom()
>>> rng.random()
0.42523087065862786

Данный клас основан на функции os.urandom(n), которая возвращает n случайных байтов. Эта функция доступна не на всех операционных системах, но в зависимости от реализации источника энтропии в используемой системе, полученные данные могут быть использованы для криптографических целей (с определенными ограничениями, конечно же). В то же время, использование данной функции может быть не совсем удобно в плане того, что она нечувствительна к начальному состоянию seed:

>>> rng = random.SystemRandom(0)
>>> rng.randint(0, 1000)
496
>>> rng.randint(0, 1000)
765
>>> 
>>> 
>>> rng = random.SystemRandom(0)
>>> rng.randint(0, 1000)
877
>>> rng.randint(0, 1000)
74
>>> 
>>> 
>>> rng.seed(0)
>>> rng.randint(0, 1000)
893
>>> rng.randint(0, 1000)
541
>>> 
>>> rng.seed(0)
>>> rng.randint(0, 1000)
684
>>> rng.randint(0, 1000)
900

А попытка узнать внутреннее состояние генератора (или установить его) вообще приведет к ошибке:

>>> rng.setstate()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NotImplementedError: System entropy source does not have state.
random.seed(a=None, version=2)
инициализирует генератор, или, простыми словами, задает его начальное состояние.

Данный метод очень удобен для воспроизводимости результатов, т.е. после инициализации одним и тем же значением seed, вы будете наблюдать одни и теже случайные данные. Например, если мне важно, что бы вы могли воспроизвести мои результаты на своем компьютере, то я обязательно укажу в примере кода начальное состояние:

>>> import random
>>> 
>>> random.seed(777)
>>> [random.randint(0, 100) for i in range(10)]
[29, 57, 57, 47, 73, 34, 43, 68, 96, 5]
>>> 
>>> [random.randint(0, 100) for i in range(10)]
[57, 85, 79, 70, 49, 76, 36, 15, 14, 88]

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

>>> random.seed(777)
>>> [random.randint(0, 100) for i in range(10)]
[29, 57, 57, 47, 73, 34, 43, 68, 96, 5]
>>> 
>>> [random.randint(0, 100) for i in range(10)]
[57, 85, 79, 70, 49, 76, 36, 15, 14, 88]

Зачастую такая воспроизводимость необходима в тестировании всякого рода алгоритмов и их сравнении.

Если параметр a не указан или равен None, то в качестве seed используется текущее системное время. Но в случае если используется системный генератор random.SystemRandom(), то начальное состояние определяется реализацией источника этропии самой системы.

Праметр version может быть установлен только в два значения: version = 1 — все биты объектов str, bytes и bytearray используются для их преобразования в объект int; version = 2 — преобразует str и bytes в более узкий диапазон объектов int:

>>> random.seed('abcdefg', version = 2)
>>> random.random()
0.900810533538886
>>> 
>>> random.seed('abcdefg', version = 1)
>>> random.random()
0.5159636669042958
random.getstate()
возвращает кортеж с параметрами внутреннего состояния генератора, который может быть использован для воссоздания этого состояния.

Возвращаемый кортеж очень велик:

>>> import random
>>> 
>>> state = random.getstate()
>>> state    #  часть элементов вырезана для краткости
(3, (89565862, 3425189215, 3154482850, 1979658548,
3546463336, 3865449041, 477897072, 319114088, 3764338129, ...,
2095044635, 3045981056, 910926435, 1691969743, 2), None)

Данный кортеж может быть передан функции random.setstate() для восстановления исходного состояния генератора.

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

Данный метод отличается от random.seed() тем что фиксирует состояние генератора, что называется "здесь и сейчас", т.е. состояние генератора в тот самый момент, когда оно было получено с помощью метода random.getstate(). Например, мы начали пользоваться генератором без инициализации начальным значением seed:

>>> import random
>>> 
>>> [random.randint(0, 100) for i in range(10)]
[95, 5, 22, 90, 27, 49, 62, 75, 20, 92]

Теперь, мы уверены, что заполучив внутреннее состояние и установив его снова, мы увидим те же самые цифры, но нет!

>>> state = random.getstate()
>>> 
>>> random.setstate(state)
>>> 
>>> [random.randint(0, 100) for i in range(10)]
[60, 49, 32, 30, 51, 88, 75, 9, 10, 22]

Мы будем видеть те цифры, которые аблюдали после вызова random.getstate():

>>> random.setstate(state)
>>> [random.randint(0, 100) for i in range(10)]
[60, 49, 32, 30, 51, 88, 75, 9, 10, 22]
random.getrandbits(k)
возвращает целое число состоящее из k случайных бит.
>>> import random
>>> 
>>> random.getrandbits(3)
1
>>> random.getrandbits(3)
7
>>> random.getrandbits(3)
2
>>>
>>>
>>> random.getrandbits(2*8)
20425
>>> random.getrandbits(2*16)
1256392503
>>> random.getrandbits(2*100)
217643563769053521856457505009674952430517737944457746563079

Благодаря данному методу random.randrange() может работать со сколь угодно большими диапазонами:

>>> random.randrange(2**1000)
1275865695067716764847144236357211378824489026543888189269754106761215675815687309044399156167662412487161365998414469225688961475002315924475570093277111915996233296820305466645284270717085423682019647036048158154856125754228676320329006044129943939292592731194259682760148444911313023330939159997159

Случайные целые числа

random.randint(a, b)
возвращает случайное целое число из интервала \([a, b]\).
>>> import random
>>> 
>>> random.randint(0, 2)
0
>>> random.randint(0, 2**100)
538942565163219819900091791967

Данная функция является эквивалентной команде random.randrange(a, b+1)

random.randrange()
возвращает случайное целое число из указанного диапазона.

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

>>> import random
>>> 
>>> random.randrange(10)    #  случайное из интервала [0; 10)
3
>>> 
>>> random.randrange(10, 100)    #  случайное из интервала [10; 100)
77
>>> 
>>> random.randrange(10, 100, 2)    #  случайное четное из интервала [10; 100)
98

Передача аргументов по ключу, неоднозначна, поэтому передавайте аргументы по их позиции: random.randrange(0, 2) вместо random.randrange(start = 0, stop = 2).


Случайные последовательности

random.choice(seq)
возвращает случайный элемент из непустой последовательности seq, если seq пуст, то будет вызвано исключение IndexError.
>>> import random
>>> 
>>> random.choice('abcdefg')    #   случайный символ из строки
'c'
>>> random.choice([13, 'a', 111, 'cd', 8])    #  случайный объект из списка
'cd'
random.shuffle(x[, random])
Перемешивает элементы последовательности x. Последовательность должна быть изменяемой,
>>> import random
>>> 
>>> l = [1, 2, 3, 4, 5, 6]
>>> 
>>> random.shuffle(l)
>>> l
[2, 4, 3, 5, 1, 6]

Данная функция способна работать только с изменяемыми последовательностями, т.е. получить перестановку из строки или кортежа не получится, но если это необходимо, то можно воспользоваться преобразованием типов данных. Главное помнить, что данная функция ничего не возвращает, а изменяет непосредственно сам объект последовательности. Например, получить перестановку элементов кортежа можно как-то так:

>>> l = (1, 2, 3, 4, 5, 6)
>>> l = list(l)
>>> random.shuffle(l)
>>> l = tuple(l)
>>> 
>>> l
(3, 2, 1, 5, 6, 4)

Перестановка из строки может быть получена похожим образом:

>>> s = 'ABCDEFG'
>>> s = list(s)
>>> 
>>> random.shuffle(s)
>>> 
>>> s = ''.join(s)
>>> s
'ECFGDAB'

Необязательный параметр random принимает имя функции которая выдает случайные числа с плавающей точкой в диапазоне [0.0, 1.0), с единственным условием – данная функция не должна принимать параметры. По умолчанию это функция random():

>>> l = [1, 2, 3, 4]
>>> random.shuffle(l, random.random)
>>> l
[2, 3, 4, 1]

Но мы можем использовать собственные функции:

>>> def my_triangular():
...     return random.triangular(0, 1, 0.5)
... 
>>> my_triangular()
0.6832905977433554
>>> 
>>> l = [1, 2, 3, 4]
>>> random.shuffle(l, my_triangular)
>>> l
[4, 1, 2, 3]

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

random.sample(population, k)
возвращает список, который состоит из k элементов взятых случайным образом из последовательности population. При этом, сама последовательность остается без изменений.
>>> import random
>>> 
>>> s = 'ABCDEFG'
>>> 
>>> random.sample(s, 4)
['B', 'E', 'F', 'C']
>>> 
>>> s
'ABCDEFG'

Количество возвращаемых элементов в выборке не должно превышать размер самой выборки, т.е. k находится в интервале [0, len(population)]:

>>> len(s)
7
>>> random.sample(s, 0)    #  вернет пустой список
[]
>>> random.sample(s, 7)    #  выполнит перестановку эоементов строки
['F', 'E', 'B', 'G', 'C', 'D', 'A']

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

>>> random.sample([1, 2, 1, 1, 2, 3, 1], 4)
[1, 1, 1, 2]

В качестве population могуть быть любые итерируемые объекты:

>>> random.sample(range(1000, 9999), 10)    #  выборка из итератора
[8224, 9247, 4318, 2721, 4208, 5291, 9472, 4145, 9407, 9131]
>>> 
>>> random.sample([i**2 for i in range(33)], 10)    #  выборка из генератора
[121, 729, 529, 196, 0, 100, 16, 784, 324, 961]

В принципе, параметры population и k могут быть довольно большими, но имейте ввиду, что при создании выборок необходимо запоминать либо индексы выбранных элементов, либо индексы тех элементов, которые могут быть выбраны. Так что при очень больших значениях k и длины population функция может работать довольно медленно и потреблять много памяти.


Случайные вещественные числа

random.random()
возвращает случайное вещественное число из интервала \([0.0, 1.0)\).
>>> import random
>>> 
>>> random.random()
0.019933795228524787
random.uniform(a, b)
возвращает случайное вещественное число из интервала \([a, b]\) если \(a \lt b\) или из интервала \([b, a]\) если \(b \lt a\).
>>> import random
>>> 
>>> random.uniform(17, 20)
19.539089430036622
>>> 
>>> random.uniform(1, -2)
-0.08600207366904256

Вероятностные распределения

random.triangular(low, high, mode)
возвращает случайное вещественное число из треугольного вероятностного распределения.

По умолчанию, минимальное и максимальное значения (левый и правый угол) равны \(0\) и \(1\), а координата вершины (mode) находится по середине, т.е. равна \(0.5\):

>>> import random
>>> 
>>> random.triangular()
0.5489339773275408
>>> 
>>> [round(random.triangular(), 2) for i in range(10)]
[0.92, 0.44, 0.47, 0.54, 0.25, 0.63, 0.75, 0.21, 0.31, 0.41]

Но параметры распределения могут быть установлены в необходимые значения:

>>> for i in range(10):
...     x = random.triangular(low = 10, high = 100, mode = 95)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
96.06, 93.37, 68.12, 44.23, 90.94, 82.6, 72.86, 42.21, 56.09, 80.45
random.betavariate(alpha, beta)
возвращает случайное вещественное число из бета-распределения.

Параметры alpha и beta должны быть обязательно указаны, так как не имеют значений по умолчанию, и должны быть строго больше \(0\):

>>> import random
>>> 
>>> random.betavariate(alpha = 1, beta = 2.2)
0.08076061812597142
>>> 
>>> 
>>> for i in range(10):
...     x = random.betavariate(alpha = 1, beta = 2.2)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
0.27, 0.13, 0.22, 0.17, 0.18, 0.05, 0.29, 0.63, 0.17, 0.51
random.expovariate(lambd)
возвращает случайное вещественное число из экспоненциального (показательного) распределения.
>>> import random
>>> 
>>> random.expovariate(lambd = 1.5)
0.14022082480343487
>>> 
>>> 
>>> for i in range(10):
...     x = random.expovariate(lambd = 1.5)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
0.09, 1.31, 0.95, 1.51, 0.42, 0.99, 1.32, 0.87, 0.49, 0.14

Параметр lambd, по хорошему, должен называться lambda, но это зарезервированное слово языка Python.

random.gammavariate(alpha, beta)
возвращает случайное вещественное число из гамма-распределения.

Параметры alpha и beta должны быть обязательно указаны, так как не имеют значений по умолчанию, и должны быть строго больше \(0\):

>>> import random
>>> 
>>> random.gammavariate(alpha = 9, beta = 0.5)
2.861976870588965
>>> 
>>> 
>>> for i in range(10):
...     x = random.gammavariate(alpha = 9, beta = 0.5)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
7.42, 5.55, 6.17, 2.38, 3.2, 4.79, 4.85, 6.37, 4.52, 4.26
random.gauss(mu, sigma)
возвращает случайное вещественное число из распределения Гауса.

По сути, выполняет то же самое что и random.normalvariate(), но немного быстрее:

>>> import random
>>> 
>>> random.gauss(mu = 0, sigma = 0.2)
0.21818525063404515
random.normalvariate(mu, sigma)
возвращает случайное вещественное число из нормального распределения.
>>> import random
>>> 
>>> random.normalvariate(mu = -1, sigma = -1)
-0.7055427988874806
>>> 
>>> 
>>> for i in range(10):
...     x = random.normalvariate(mu = 0, sigma = 1)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
1.42, 0.42, -0.46, -1.11, -0.38, -1.18, 1.97, -2.33, 2.06, 0.12
random.lognormvariate(mu, sigma)
возвращает случайное вещественное число из логнормального распределения.

mu - может быть любым, а вот sigma - должна быть строго больше \(0\).

>>> import random
>>> 
>>> random.lognormvariate(mu = 0, sigma = 1)
1.0486383657788385
>>> 
>>> for i in range(10):
...     x = random.lognormvariate(mu = 0, sigma = 1)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
0.71, 0.67, 1.87, 0.96, 12.43, 2.02, 1.11, 0.33, 0.17, 1.59
random.vonmisesvariate(mu, kappa)
возвращает случайный угол из интервала \([0, 2 \ pi]\) значение которого подчиняется закону распределения фон Мизеса.

mu - средний угол выраженный в радианах из интервала \([0, 2 \ pi]\), kappa должен быть больше либо равен 0.

>>> import random
>>> 
>>> random.vonmisesvariate(3.14, 1)
2.201702941836471
>>> 
>>> for i in range(10):
...     x = random.vonmisesvariate(3.14, 1)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
2.83, 2.02, 2.62, 3.73, 3.0, 4.63, 1.45, 2.4, 4.62, 2.64
random.paretovariate(alpha)
возвращает случайное вещественное число из Парето распределения.
>>> import random
>>> 
>>> random.paretovariate(1)
4.953639576457526
>>> 
>>> for i in range(10):
...     x = random.paretovariate(1)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
1.04, 3.9, 1.74, 1.24, 1.4, 14.35, 1.84, 4.01, 3.15, 1.15
random.weibullvariate(alpha, beta)
возвращает случайное вещественное число из распределения Вейбулла.
>>> import random
>>> 
>>> random.weibullvariate(1, 1.5)
0.8977693128589646
>>> 
>>> for i in range(10):
...     x = random.weibullvariate(1, 1.5)
...     x = round(x, 2)
...     print(x, end = ', ')
... 
0.02, 1.56, 0.7, 0.55, 0.64, 0.31, 0.53, 0.99, 0.35, 1.35