Использование генераторов псевдослучайных чисел#
Начнём рассмотрение вопроса с помощью стандартного модуля random . Абсолютно всю информацию о нём вы найдёте в официальной документации.
Основы#
import random as rand
Сгенерируем 5 псевдослучайных чисел (ПСЧ):
for _ in range(5): print(rand.random())
0.7412132144650633 0.5039554430348208 0.013889044215187552 0.1344307957219092 0.2200671076853502
Функция random() генерирует ПСЧ, распределённое равномерно на полуотрезке [0, 1).
Запустив этот код несколько раз, вы заметите, что каждый раз получаете различный результат. Ниже приведён тот же код, однако результат отличается.
for _ in range(5): print(rand.random())
0.37045684064536566 0.6109772760595191 0.15120576325286195 0.8649281330918606 0.09137726759086207
Состояние генератора#
На самом деле в предыдущем примере мы вызвали не функцию random() , а метод random() экземпляра класса Random . Конкретный экземпляр класса Random имеет определённое состояние, которое по умолчанию случайно, но может определяется параметром seed (на русский часто переводится как “затравка”) или методом setstate(. ) в любой момент работы программы.
Повторим предыдущий пример, задав определённое значение seed :
# Затравка может быть любым целым числом. # Рекомендуют назначать его очень большим числом, # чтобы уменьшить вероятность совпадения затравок, # назначенных разными людьми rand.seed(1700077) for _ in range(5): print(rand.random())
0.7020746348844183 0.6233161107388304 0.12425768533206094 0.29613478392527026 0.5273751569843022
И ещё раз выполним этот же код:
rand.seed(1700077) for _ in range(5): print(rand.random())
0.7020746348844183 0.6233161107388304 0.12425768533206094 0.29613478392527026 0.5273751569843022
Сколько бы раз мы не запустили код, мы будем получать одну и ту же последовательность значений. Таким образом наглядно показано свойство обеспечения воспроизводимости результатов ГПСЧ.
Изменим затравку — получим другой результат:
rand.seed(777) for _ in range(5): print(rand.random())
0.22933408950153078 0.44559617334521107 0.36859824937216046 0.269835098321503 0.3361436466700177
Но теперь этот результат всё так же от запуска к запуску будет повторяться:
rand.seed(777) for _ in range(5): print(rand.random())
0.22933408950153078 0.44559617334521107 0.36859824937216046 0.269835098321503 0.3361436466700177
Рассмотрим пример, показывающий неудобство использования метода seed() напрямую.
Пусть есть две функции, использующие внутри себя ГПСЧ, причём получают ПСЧ, распределённое по одинаковым законам. Пусть, например, функция f() генерирует ПСЧ, используя random() . То же делает и функция g() :
def f(): return rand.random() def g(): return rand.random()
Вызовем последовательно эти функции:
print(f(), g())
0.7523163560031157 0.9226950812763804
При каждом запуске получаем разные результаты. Но попробуем реализовать возможность воспроизведения результатов. Для этого вызовем seed(. ) в глобальной области видимости:
rand.seed(666) print(f(), g())
0.45611964897696833 0.9033231539802643
Мы добились своего — результат от запуска к запуску не меняется. Заметим ещё одно, а именно то, что результат не изменится, если мы поменяем местами вызовы функций:
rand.seed(666) print(g(), f())
0.45611964897696833 0.9033231539802643
Заметьте, в каждой ячейке мы должны вызвать seed(. ) , чтобы каждый раз переустанавливать ГПСЧ в начальное состояние. В противном случае будут генерироваться новые случайные числа, которые тем не менее всё так же воспроизводимы. Иными словами, если после вызова seed(. ) мы сгенерировали \(m\) чисел, то при повторном запуске для генерации \(n\) чисел, \(n > m\) , будут сгенерированы те же самые \(m\) чисел, плюс \(n — m\) новых чисел.
Убедимся в этом так. Опишем функцию генерации заданного количества ПСЧ:
def gen_randoms(n): return [ rand.random() for _ in range(n) ]
И вызовем её для генерации сначала трёх, а затем двух чисел:
rand.seed(2023) # Три случайных числа print(gen_randoms(3)) # плюс два новых случайных числа print(gen_randoms(2))
[0.3829219244542088, 0.9718620884907823, 0.8438174232038365] [0.32028063842069, 0.5710257361341048]
А теперь сбросим ГПСЧ в то же самое начальное состояние и сгенерируем сразу пять чисел. В результате должна получиться та же самая последовательность ПСЧ:
rand.seed(2023) print(gen_randoms(5))
[0.3829219244542088, 0.9718620884907823, 0.8438174232038365, 0.32028063842069, 0.5710257361341048]
Как видите, результат полностью предсказуем.
Параметр seed(. ) является глобальным. В программах, состоящих из нескольких модулей, это может стать проблемой.
В реальных программах со множеством модулей и большим объёмом кода рассмотренный подход имеет некоторые недостатки. В ряде задач, например, может потребоваться ГПСЧ, способный корректно работать в условиях параллельных вычислений. В условиях модульности программы может быть удобнее передавать между функциями и объектами экземпляры генератора.
Существует удобный способ справиться с описанными проблемами — создание экземпляра ГПСЧ определённого типа и с заданным состоянием. Например, так:
rg = rand.Random(2023) # Переданный параметр по сути своей есть seed
Наш экзепляр ГПСЧ rg имеет все те же методы и возможности, что есть в модуле random , который мы использовали под псевдонимом rand . Перепишем нашу функцию gen_randoms(. ) следующим образом:
def gen_randoms(n, rg): # Параметр rg - объект ГПСЧ return [ rg.random() for _ in range(n) ]
Теперь, помимо количества генерируемых чисел, функция принимает на вход объект ГПСЧ rg . Сгенерируем пять чисел:
print(gen_randoms(5, rg))
[0.3829219244542088, 0.9718620884907823, 0.8438174232038365, 0.32028063842069, 0.5710257361341048]
Как видите, результат тот же, что и раньше при seed = 2023 .
Визуализация распределений#
Для визуализации случайных чисел часто используется гистограмма. Подключим matplotlib, сформируем два массива ПСЧ для двух разных распределений и построим их гистограммы. При формировании массивов воспользуемся методами normalvariate(. ) (нормальное распределение) и uniform(. ) (равномерное распределение) нашего экземпляра ГПСЧ rg .
import matplotlib.pyplot as plt # Сформируем массивы по 50 элементов N1 = 50 norm1 = [ # mu - математическое ожидание # sigma - среднее квадратическое отклонение rg.normalvariate(mu=10, sigma=2) for _ in range(N1) ] uniform1 = [ rg.uniform(-1, 2) for _ in range(N1) ] fig, (ax1, ax2) = plt.subplots( figsize=(6, 6), nrows=2 ) ax1.hist(norm1, bins=20) ax1.set_title("Нормальное распределение") ax2.hist(uniform1, bins=20) ax2.set_title("Равномерное распределение");

Увеличим число точек в 20 раз:
N2 = N1 * 20 norm2 = [ rg.normalvariate(mu=10, sigma=2) for _ in range(N2) ] uniform2 = [ rg.uniform(-1, 2) for _ in range(N2) ] fig, (ax1, ax2) = plt.subplots( figsize=(6, 6), nrows=2 ) ax1.hist(norm2, bins=20) ax1.set_title("Нормальное распределение") ax2.hist(uniform2, bins=20) ax2.set_title("Равномерное распределение");

Как видите, при увеличении количества ПСЧ гистограммы принимают всё более чёткие очертания конкретного распределения.
Функциональность стандартного random уже достаточна для моделирования различных стохастических процессов. Однако в ряде случаев более удобно пользоваться random ’ом из библиотеки NumPy.
Использование NumPy#
Функционал numpy.random полностью аналогичен random ’у стандартной библиотеки. Так как вся библиотека NumPy заточена под работу с массивами, методы numpy.random так же способны генерировать не одно число за раз, а сразу весь массив, матрицу или тензор заданного размера. И здесь также есть возможность пользоваться как неявно созданным экземпляром Generator , так и его явно созданным экземпляром. Создать генератор можно так:
import numpy.random as nprand # Генератор по умолчанию с состоянием по затравке rg = nprand.default_rng(seed=1234567)
Сразу сгенерируем два массива чисел, распределённых по нормальному и экспоненциальному законам:
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6, 8)) # density=True создаёт гистограмму плотности, # по смыслу соответствующую функции плотности вероятности # - нормальное ax1.hist( rg.normal(loc=10, scale=2, size=10_000), bins=20, density=True ) ax1.set(xlabel="$x$", ylabel=r"$p_<\mathrm >(x)$"); # - экспоненциальное ax2.hist( rg.exponential(scale=3, size=10_000), bins=20, density=True ) ax2.set(xlabel="$x$", ylabel=r"$p_(x)$");

Мы сразу создали массив из 10000 элементов, указав параметр size=10000 . Если size не указать, то будет возвращено одно число.
Названия некоторых методов отличаются от тех же функций в стандартном random ’ме. Отличаются и имена параметров функций. В данном случае параметры loc и scale аналогичны mu и sigma (математическое ожидание и стандартное отклонение).
fig, ax = plt.subplots() x = rg.normal(10, 5, 10_000) # Пусть y коррелирует с x y = rg.normal(3, 2, 10_000) + x/3 img = ax.hist2d(x, y, bins=50, cmap="Greys", density=True)[-1] fig.colorbar(img, ax=ax, label="$p(x, y)$") ax.set(xlabel="$x$", ylabel="$y$");

Случайную матрицу можно создать, передав в качестве size несколько значений:
# По умолчанию loc=0 и scale=1 rg.normal(size=(3, 4))
array([[ 0.83356875, -0.99563296, -1.49970249, 0.11628947], [ 0.36775022, -1.06158613, -0.27614625, 0.78144333], [ 1.0311598 , -0.64312952, -1.79305138, 1.42401372]])
Естественно, при одном и том же seed будет один и тот же результат и для чисел, и для массивов любой формы:
rg = nprand.default_rng(98765) rg.exponential(size=(3, 4))
array([[0.02466916, 1.44229712, 0.96453594, 1.16304798], [0.22589411, 2.33248878, 1.33913539, 0.17395315], [0.73634616, 1.14181883, 1.27708772, 0.36814741]])
rg = nprand.default_rng(98765) rg.exponential(size=(3, 4))
array([[0.02466916, 1.44229712, 0.96453594, 1.16304798], [0.22589411, 2.33248878, 1.33913539, 0.17395315], [0.73634616, 1.14181883, 1.27708772, 0.36814741]])
Одним из преимуществ numpy.random является возможность использования ГПСЧ в параллельных процессах. Эта возможность обеспечивается за счёт SeedSequence .
См. также#
- SeedSequence для применения ГПСЧ в параллельных вычислениях.
- Основы генерации ПСЧ можно найти в учебнике “Имитационное моделирование”.
- Книга “Python для сложных задач”.
- Под визуализацию статистических данных заточена библиотека seaborn.
- Документация numpy.random .
- Генераторы NumPy основаны на низкоуровневых генераторах ПСЧ, которые называются Bit Generators . В документации на них можно найти полный список конкретных генераторов. Тогда вместо nprand.default_rng(. ) , создающего по умолчанию битовый генератор PCG64, можно, например, создать ГПСЧ с битовым генератором SFC64: nprand.Generator(nprand.SFC64()) .
- Документация стандартного random .
Генерация псевдослучайных чисел
How to seed random number generator? [duplicate]
I was looking at the example for generating normal distributed random numbers at cppreference.com and refactored the code a bit to get this:
#include #include struct MyNormalDistribution < static double getRandomNumber(double mean,double std_dev)< return std::normal_distribution<>(mean,std_dev)(MyNormalDistribution::generator); > private: static std::random_device rand; static std::mt19937 generator; >; std::random_device MyNormalDistribution::rand; std::mt19937 MyNormalDistribution::generator = std::mt19937(MyNormalDistribution::rand()); int main(int argc, char *argv[]) < for (int t=0;t<10;t++)< std::cout << MyNormalDistribution::getRandomNumber(0,10) << std::endl; >>
However, whenever I run this I get the same sequence of numbers. Is there some stupid mistake, or does the example on cppreference not include proper seeding? How to I properly seed MyNormalDistribution ?
How to properly seed random number generator
My implementation is very slow. Seeding using time brings the same random number for a certain time, so the loop iterates again and again. How can I improve my code?
76.6k 17 17 gold badges 149 149 silver badges 195 195 bronze badges
asked Sep 7, 2012 at 15:29
2,455 2 2 gold badges 14 14 silver badges 5 5 bronze badges
The «if string(randInt(65,90))!=temp
Oct 23, 2013 at 22:37
As a side note, there is no need to convert to UTC in «time.Now().UTC().UnixNano()». Unix time is calculated since Epoch which is UTC anyway.
Aug 2, 2015 at 14:03
You should set the seed once, only one time, and never more than once. well, in case your application runs for days you could set it once a day.
Feb 9, 2017 at 13:53
You should seed once. And I think «Z» may never appear, I guess? So I prefer to use begin index inclusive and end index exclusive.
Jun 27, 2019 at 3:54
12 Answers 12
Each time you set the same seed, you get the same sequence. So of course if you’re setting the seed to the time in a fast loop, you’ll probably call it with the same seed many times.
In your case, as you’re calling your randInt function until you have a different value, you’re waiting for the time (as returned by Nano) to change.
As for all pseudo-random libraries, you have to set the seed only once, for example when initializing your program unless you specifically need to reproduce a given sequence (which is usually only done for debugging and unit testing).
After that you simply call Intn to get the next random integer.
Move the rand.Seed(time.Now().UTC().UnixNano()) line from the randInt function to the start of the main and everything will be faster. And lose the .UTC() call since:
UnixNano returns t as a Unix time, the number of nanoseconds elapsed since January 1, 1970 UTC.
Note also that I think you can simplify your string building:
package main import ( "fmt" "math/rand" "time" ) func main() < rand.Seed(time.Now().UnixNano()) fmt.Println(randomString(10)) >func randomString(l int) string < bytes := make([]byte, l) for i := 0; i < l; i++ < bytes[i] = byte(randInt(65, 90)) >return string(bytes) > func randInt(min int, max int) int
15.3k 6 6 gold badges 44 44 silver badges 58 58 bronze badges
answered Sep 7, 2012 at 15:33
Denys Séguret Denys Séguret
376k 89 89 gold badges 796 796 silver badges 765 765 bronze badges
Thanks for explaining that, i thought this need to be seeded every time.
Sep 7, 2012 at 16:03
You can also add rand.Seed(. ) to the function init() . init() is called automatically before main() . Note that you don’t need to call init() from main() !
Feb 3, 2014 at 12:30
@Jabba Right. I was keeping my answer as simple as possible and not too far from the question, but your observation is right.
Feb 3, 2014 at 12:35
Please note that none of the anwers posted so far initialize the seed in a cryptographically secure way. Depending on your application, this might not matter at all or it might result in catastrophic failure.
Feb 10, 2016 at 0:13
@IngoBlechschmidt math/rand is not cryptographically secure anyway. If that is a requirement, crypto/rand should be used.
Jul 23, 2019 at 12:58
Edit: This issue has been addressed as of Go version 1.20 and it is no longer necessary to seed the random source yourself.
I don’t understand why people are seeding with a time value. This has in my experience never been a good idea. For example, while the system clock is maybe represented in nanoseconds, the system’s clock precision isn’t nanoseconds.
This program should not be run on the Go playground but if you run it on your machine you get a rough estimate on what type of precision you can expect. I see increments of about 1000000 ns, so 1 ms increments. That’s 20 bits of entropy that are not used. All the while the high bits are mostly constant!? Roughly ~24 bits of entropy over a day which is very brute forceable (which can create vulnerabilities).
The degree that this matters to you will vary but you can avoid pitfalls of clock based seed values by simply using the crypto/rand.Read as source for your seed. It will give you that non-deterministic quality that you are probably looking for in your random numbers (even if the actual implementation itself is limited to a set of distinct and deterministic random sequences).
import ( crypto_rand "crypto/rand" "encoding/binary" math_rand "math/rand" ) func init() < var b [8]byte _, err := crypto_rand.Read(b[:]) if err != nil < panic("cannot seed math/rand package with cryptographically secure random number generator") >math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:]))) >
As a side note but in relation to your question. You can create your own rand.Source using this method to avoid the cost of having locks protecting the source. The rand package utility functions are convenient but they also use locks under the hood to prevent the source from being used concurrently. If you don’t need that you can avoid it by creating your own Source and use that in a non-concurrent way. Regardless, you should NOT be reseeding your random number generator between iterations, it was never designed to be used that way.
Edit: I used to work in ITAM/SAM and the client we built (then) used a clock based seed. After a Windows update a lot of machines in the company fleet rebooted at roughly the same time. This caused an involtery DoS attack on upstream server infrastructure because the clients was using system up time to seed randomness and these machines ended up more or less randomly picking the same time slot to report in. They were meant to smear the load over a period of an hour or so but that did not happen. Seed responsbily!
Python 3: Генерация случайных чисел (модуль random)
Python порождает случайные числа на основе формулы, так что они не на самом деле случайные, а, как говорят, псевдослучайные [1]. Этот способ удобен для большинства приложений (кроме онлайновых казино) [2].
Модуль random позволяет генерировать случайные числа. Прежде чем использовать модуль, необходимо подключить его с помощью инструкции:
import random
random.random
random.random() — возвращает псевдослучайное число от 0.0 до 1.0
random.random() 0.07500815468466127
random.seed
random.seed() — настраивает генератор случайных чисел на новую последовательность. По умолчанию используется системное время. Если значение параметра будет одиноким, то генерируется одинокое число:
random.seed(20) random.random() 0.9056396761745207 random.random() 0.6862541570267026 random.seed(20) random.random() 0.9056396761745207 random.random() 0.7665092563626442
random.uniform
random.uniform(, ) — возвращает псевдослучайное вещественное число в диапазоне от до :
random.uniform(0, 20) 15.330185127252884 random.uniform(0, 20) 18.092324756265473
random.randint
random.randint(, ) — возвращает псевдослучайное целое число в диапазоне от до :
random.randint(1,27) 9 random.randint(1,27) 22
random.choince
random.choince() — возвращает случайный элемент из любой последовательности (строки, списка, кортежа):
random.choice('Chewbacca') 'h' random.choice([1,2,'a','b']) 2 random.choice([1,2,'a','b']) 'a'
random.randrange
random.randrange(, , ) — возвращает случайно выбранное число из последовательности.
random.shuffle
random.shuffle() — перемешивает последовательность (изменяется сама последовательность). Поэтому функция не работает для неизменяемых объектов.
List = [1,2,3,4,5,6,7,8,9] List [1, 2, 3, 4, 5, 6, 7, 8, 9] random.shuffle(List) List [6, 7, 1, 9, 5, 8, 3, 2, 4]
Вероятностные распределения
random.triangular(low, high, mode) — случайное число с плавающей точкой, low ≤ N ≤ high. Mode - распределение.
random.betavariate(alpha, beta) — бета-распределение. alpha>0, beta>0. Возвращает от 0 до 1.
random.expovariate(lambd) — экспоненциальное распределение. lambd равен 1/среднее желаемое. Lambd должен быть отличным от нуля. Возвращаемые значения от 0 до плюс бесконечности, если lambd положительно, и от минус бесконечности до 0, если lambd отрицательный.
random.gammavariate(alpha, beta) — гамма-распределение. Условия на параметры alpha>0 и beta>0.
random.gauss(значение, стандартное отклонение) — распределение Гаусса.
random.lognormvariate(mu, sigma) — логарифм нормального распределения. Если взять натуральный логарифм этого распределения, то вы получите нормальное распределение со средним mu и стандартным отклонением sigma. mu может иметь любое значение, и sigma должна быть больше нуля.
random.normalvariate(mu, sigma) — нормальное распределение. mu — среднее значение, sigma — стандартное отклонение.
random.vonmisesvariate(mu, kappa) — mu — средний угол, выраженный в радианах от 0 до 2π, и kappa — параметр концентрации, который должен быть больше или равен нулю. Если каппа равна нулю, это распределение сводится к случайному углу в диапазоне от 0 до 2π.
random.paretovariate(alpha) — распределение Парето.
random.weibullvariate(alpha, beta) — распределение Вейбулла.
Примеры
Генерация произвольного пароля
Хороший пароль должен быть произвольным и состоять минимум из 6 символов, в нём должны быть цифры, строчные и прописные буквы. Приготовить такой пароль можно по следующему рецепту:
import random # Щепотка цифр str1 = '123456789' # Щепотка строчных букв str2 = 'qwertyuiopasdfghjklzxcvbnm' # Щепотка прописных букв. Готовится преобразованием str2 в верхний регистр. str3 = str2.upper() print(str3) # Выведет: 'QWERTYUIOPASDFGHJKLZXCVBNM' # Соединяем все строки в одну str4 = str1+str2+str3 print(str4) # Выведет: '123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM' # Преобразуем получившуюся строку в список ls = list(str4) # Тщательно перемешиваем список random.shuffle(ls) # Извлекаем из списка 12 произвольных значений psw = ''.join([random.choice(ls) for x in range(12)]) # Пароль готов print(psw) # Выведет: '1t9G4YPsQ5L7'
Этот же скрипт можно записать всего в две строки:
import random print(''.join([random.choice(list('123456789qwertyuiopasdfghjklzxc vbnmQWERTYUIOPASDFGHJKLZXCVBNM')) for x in range(12)]))
Данная команда является краткой записью цикла for, вместо неё можно было написать так:
import random psw = '' # предварительно создаем переменную psw for x in range(12): psw = psw + random.choice(list('123456789qwertyuiopasdfgh jklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM')) print(psw) # Выведет: Ci7nU6343YGZ
Данный цикл повторяется 12 раз и на каждом круге добавляет к строке psw произвольно выбранный элемент из списка.
Ссылки
- Официальная документация по модулю random (англ.)
- Python 3 для начинающих: Модуль random
- Модуль random — генерация случайных чисел
- Безопасность случайных чисел в Python
| [2] | Доусон М. Программируем на Python. — СПб.: Питер, 2014. — 416 с.: ил. — 3-е изд |