【译】Python 标准库教程——random 模块

本文翻译自 Doug Hellmann 的 PyMOTW-3项目的random模块。

原文链接:https://pymotw.com/3/random/index.html

本文使用cc-by-nc-sa 4.0协议共享。

random—伪随机数生成器

random模块提供了基于Mersenne Twister算法的快速伪随机数生成器。起初开发它是为了产生蒙特卡洛模拟的输入,Mersenne Twister生成的数近乎均匀分布并且周期大,使其适合更加广泛的应用。

生成随机数

random函数从生成序列中返回下一个随机浮点数。所有返回值都包含在0 <= n < 1.0。

1
2
3
4
5
6
7
# random_random.py

import random

for i in range(5):
print('%04.3f' % random.random(), end=' ')
print()

重复运行程序以获得不同的值序列。

1
2
3
4
5
6
7
$ python3 random_random.py

0.859 0.297 0.554 0.985 0.452

$ python3 random_random.py

0.797 0.658 0.170 0.297 0.593

为获得特定范围的数,使用uniform()

1
2
3
4
5
6
7
# random_uniform.py

import random

for i in range(5):
print('{:04.3f}'.format(random.uniform(1, 100)), end=' ')
print()jiang

传递最大值和最小值, 然后uniform()使用公式min + (max - min) * random()调整random()的返回值。

1
2
3
$ python3 random_uniform.py

12.428 93.766 95.359 39.649 88.983

种子

random()在每次调用时产生不同的值,在重复任何值前有相当大的周期。这对产生唯一的值或变量很有用,有时也对以不同的方式处理相同的数据集很有用。一种技术是使用程序生成随机值,并保存随机值到独立的步骤中处理。但是,这对大型数据集来说不切实际,因此random提供了初始化伪随机数生成器的seed()函数,以生成一组预期的值。

1
2
3
4
5
6
7
8
9
# random_seed.py

import random

random.seed(1)

for i in range(5):
print('{:04.3f}'.format(random.random()), end=' ')
print()

种子控制了伪随机数生成公式的第一个值,并当公式确定时也设置了种子改变后生成的完整序列。seed()的参数可以是任何hashable 对象。默认使用平台特定的随机资源,如果有的话。否则,使用当前时间。

保存状态

random()中使用的伪随机数算法的内部状态可以被保存下来,用以控制后续运行中生成的数字。在继续运行前保存先前的状态可减少从以前的输入重复值或值序列的可能性。getstate()返回的数据可用于稍后通过setstate()重新初始化随机数生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# random_state.py

import random
import os
import pickle

if os.path.exists('state.dat'):
#Restore the previously saved state
print('Found state.dat, initializing random module')
with open('state.dat','rb') as f:
state =pickle.load(f)
random.setstate(state)
else:
# Use a well-known start state
print('No state.dat, seeding')
random.seed(1)

#Produce random values
for i in range(3):
print('{:04.3f}'.format(random.random()), end=' ')
print()

#Save state for next time
with open('state.dat', 'wb') as f:
pickle.dump(random.getstate(), f)

#Produce more ranodm values
print('\nAfter saving state:')
for i in range(3):
print('{:04.3f}'.format(random.random()), end=' ')

getstate()返回的数据是一个实现细节,因此本例中使用pickle将数据保存在文件中,在其他地方可看作黑箱。如果程序开始时文件存在,则加载旧状态继续运行。每次运行在保存态前后生成一些数字,可说明恢复状态导致生成器再次生成相同的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python3 random_state.py

No state.dat, seeding
0.134 0.847 0.764

After saving state:
0.255 0.495 0.449

$ python3 random_state.py

Found state.dat, initializing random module
0.255 0.495 0.449

After saving state:
0.652 0.789 0.094

随机整数

random()生成浮点数。虽然将浮点数转化整数是可行的,但是使用randint()直接生成整数更加便捷。

1
2
3
4
5
6
7
8
9
10
11
12
13
# random_randint.py

import random

print('[1, 100]:', end=' ')

for i in range(3):
print(random.randint(1, 100), end=' ')

print('\n[-5, 5]:', end=' ')
for i in range(3):
print(random.randint(-5, 5), end=' ')
print()

ranint()的参数是值范围的闭区间。参数可以是正数或负数,但第一个值应该小于第二个值。

1
2
3
4
$ python3 random_randint.py

[1, 100]: 98 75 34
[-5, 5]: 4 0 5

randrange()是从范围中选择值的更一般形式。

1
2
3
4
5
6
7
# random_randrange.py

import random

for i in range(3):
print(random.randrange(0, 101, 5), end=' ')
print()

randrange()除了支持start和stop参数,还支持step参数。因此它等效于从range(start, stop, step)中选择值。这更有效,因为范围不是实际构造的。

随机挑选项目

随机数生成器的一个常见用途是从枚举序列中随机挑选项目,尽管这些值可能不是数。 random提供了choice()函数来从序列中做随机挑选。此例模拟了抛硬币1000次,以计算正面朝上和反面朝上的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# random_choice.py

import random
import itertools

outcomes = {
'heads': 0,
'tails': 0,
}
sides = list(outcomes.keys())

for i in range(10000):
outcomes[random.choice(sides)] += 1

print('Heads:', outcomes['heads'])
print('Tails:', outcomes['tails'])

抛硬币有两种结果,因此不是使用数字,而是将他们转化为"head"和 "tail"再传递给choice()。使用结果名称作为键,将结果列在字典中。

1
2
3
4
$ python3 random_choice.py

Heads: 5091
Tails: 4909

排列

扑克游戏模拟需要打乱牌堆,并将牌发给玩家,不能多次发同一张牌。使用choice()会导致同一张牌被发两次,因此,可以结合使用shuffle()在发牌后移走某张牌。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# random_shuffle.py

import random
import itertools

FACE_CARDS = ('J', 'Q', 'K', 'A')
SUITS = ('H', 'D', 'C', 'S')


def new_deck():
return [
# Always use 2 places for the value, so the strings
# are a consistent width.
'{:>2}{}'.format(*c)
for c in itertools.product(
itertools.chain(range(2, 11), FACE_CARDS),
SUITS,
)
]


def show_deck(deck):
p_deck = deck[:]
while p_deck:
row = p_deck[:13]
p_deck = p_deck[13:]
for j in row:
print(j, end=' ')
print()


# Make a new deck, with the cards in order
deck = new_deck()
print('Initial deck:')
show_deck(deck)

# Shuffle the deck to randomize the order
random.shuffle(deck)
print('\nShuffled deck:')
show_deck(deck)

# Deal 4 hands of 5 cards each
hands = [[], [], [], []]

for i in range(5):
for h in hands:
h.append(deck.pop())

# Show the hands
print('\nHands:')
for n, h in enumerate(hands):
print('{}:'.format(n + 1), end=' ')
for c in h:
print(c, end=' ')
print()

# Show the remaining deck
print('\nRemaining deck:')
show_deck(deck)

纸牌用字符串表示,字符串的第一位为面值(2-11,J,Q,K,A),第二位指示花色。”发牌手“每次向四组list依次增加一张牌,然后从牌堆将其移除以使不能再发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ python3 random_shuffle.py

Initial deck:
2H 2D 2C 2S 3H 3D 3C 3S 4H 4D 4C 4S 5H
5D 5C 5S 6H 6D 6C 6S 7H 7D 7C 7S 8H 8D
8C 8S 9H 9D 9C 9S 10H 10D 10C 10S JH JD JC
JS QH QD QC QS KH KD KC KS AH AD AC AS

Shuffled deck:
QD 8C JD 2S AC 2C 6S 6D 6C 7H JC QS QC
KS 4D 10C KH 5S 9C 10S 5C 7C AS 6H 3C 9H
4S 7S 10H 2D 8S AH 9S 8H QH 5D 5H KD 8D
10D 4C 3S 3H 7D AD 4H 9D 3D 2H KC JH JS

Hands:
1: JS 3D 7D 10D 5D
2: JH 9D 3H 8D QH
3: KC 4H 3S KD 8H
4: 2H AD 4C 5H 9S

Remaining deck:
QD 8C JD 2S AC 2C 6S 6D 6C 7H JC QS QC
KS 4D 10C KH 5S 9C 10S 5C 7C AS 6H 3C 9H
4S 7S 10H 2D 8S AH

抽样

许多模拟都需要从输入值的总体随机抽样。sample()函数可在不产生重复值和不修改输入序列的情况下生成样本。下面的例子从系统辞典中打印单词的随机样本。

1
2
3
4
5
6
7
8
9
10
# random_sample.py

import random

with open('/usr/share/dict/words', 'rt') as f:
words = f.readlines()
words = [w.rstrip() for w in words]

for w in random.sample(words, 5):
print(w)

生成结果集的算法考虑了输入的大小,以使尽可能快地生成样本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python3 random_sample.py

streamlet
impestation
violaquercitrin
mycetoid
plethoretical

$ python3 random_sample.py

nonseditious
empyemic
ultrasonic
Kyurinish
amphide

多重独立生成器

除了模块级函数,random提供了Random类来管理各随机数生成器的内部状态。前面描述的函数都可以看作Random实例的方法,每个实例可以独立的初始化和使用,并且不干扰其他实例返回的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# random_random_class.py

import random
import time

print('Default initializiation:\n')

r1 = random.Random()
r2 = random.Random()

for i in range(3):
print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random()))

print('\nSame seed:\n')

seed = time.time()
r1 = random.Random(seed)
r2 = random.Random(seed)

for i in range(3):
print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random()))

当系统有良好的本地随机值种子时,实例从唯一状态出发。然而,如果平台不存在良好的随机值生成器,实例就会以当前时间为种子,因此会产生相同的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ python3 random_random_class.py

Default initializiation:

0.862 0.390
0.833 0.624
0.252 0.080

Same seed:

0.466 0.466
0.682 0.682
0.407 0.407

系统随机

许多操作系统提供随机数生成器,它们可以访问更多可以被引入生成器的熵源。random通过SystemRandom类来获得这些特性,该类与Random拥有相同的API,但使用os.urandom()来生成构成其他算法基础的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# random_system_random.py

import random
import time

print('Default initializiation:\n')

r1 = random.SystemRandom()
r2 = random.SystemRandom()

for i in range(3):
print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random()))

print('\nSame seed:\n')

seed = time.time()
r1 = random.SystemRandom(seed)
r2 = random.SystemRandom(seed)

for i in range(3):
print('{:04.3f} {:04.3f}'.format(r1.random(), r2.random()))

SystemRandom生成的序列不可重复,因为随机来自系统,而不是软件层面的状态(实际上,seed()setstate()不起任何作用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ python3 random_system_random.py

Default initializiation:

0.110 0.481
0.624 0.350
0.378 0.056

Same seed:

0.634 0.731
0.893 0.843
0.065 0.177

非均匀分布

虽然random生成的均匀分布值在很多方面有用,但是其他分布能更加准确地针对特定问题建模。random模块也提供生成这些分布的函数。函数罗列如下,但不包含细节,因为它们的用途是特定的并需要更复杂的例子。

正态

正态分布广泛应用于非均匀的连续值,例如成绩、身高、体重等等。正态分布生成的曲线形状独特,被称为“钟形曲线”。random提供了两个函数生成正态分布的函数,normalvariate()和稍快的gauss()(正态分布又叫做Guassian分布)。

相关函数lognormalvariate()生成一组伪随机数,它们的对数满足正态分布。对数正态分布对于不相互作用的随机变量的乘积很有用。

近似

三角分布用于描述小型样本的近似分布。三角分布的曲线在已知的最大值和最小值处具有最低点,在众数处具有最高点,这是基于“最可能”结果的估计(由triangular()的众数参数反映)。

指数

expovariate()生成的指数分布,用于模拟均匀泊松过程的到达时间或时间间隔值,例如放射性衰变或请求进入服务器的速率。

帕累托分布,又称幂律,匹配了许多观测到的现象,并由Chris Anderson在 The Long Tail 中推广。paretovariate()函数对于模拟个体资源的分配很有用(财富对个人,对音乐家的需求,博客的关注等等)。

角度

冯米塞斯分布,又称循环正态分布(由vonmissesvariate()生成)用于计算循环值的概率,例如角度,日历日(cm calendar days),时间。

大小(cm size)

betavariate()生成的 $\Beta$分布,广泛应用于贝叶斯统计和应用中,例如任务工期建模。

gammavariative生成的 $\Gamma$分布,用于建模计算诸如等待时间,降雨量,计算误差的大小(cm size)。

weibullvariate()计算得出的韦伯分布用于失效分析,工业设计,天气预报。他描述了粒子或其他离散物体大小(cm size)的分布。

See also


【译】Python 标准库教程——random 模块
https://guixinliu.github.io/2020/02/15/python-random/
发布于
2020年2月15日
许可协议