random — 生成伪随机数

源代码: Lib/random.py


此模块为各种分布实现了伪随机数生成器。

对于整数,可以从一个范围内进行均匀选择。对于序列,可以均匀选择一个随机元素,可以使用一个函数对列表进行原地随机排列,还可以使用一个函数进行无放回随机抽样。

在实数线上,可以使用函数来计算均匀分布、正态(高斯)分布、对数正态分布、负指数分布、伽马分布和贝塔分布。对于生成角度分布,可以使用冯·米塞斯分布。

几乎所有模块函数都依赖于基本函数 random(),该函数在半开区间 0.0 <= X < 1.0 中均匀生成一个随机浮点数。Python 使用梅森旋转算法作为核心生成器。它生成 53 位精度的浮点数,周期为 2**19937-1。C 语言的底层实现既快速又线程安全。梅森旋转算法是现存测试最广泛的随机数生成器之一。然而,由于它是完全确定的,因此它并不适用于所有目的,并且完全不适合加密目的。

此模块提供的函数实际上是 random.Random 类的隐藏实例的绑定方法。您可以实例化自己的 Random 实例,以获得不共享状态的生成器。

如果要使用自己设计的其他基本生成器,还可以将 Random 类子类化:有关详细信息,请参阅该类的文档。

random 模块还提供了 SystemRandom 类,该类使用系统函数 os.urandom() 从操作系统提供的源生成随机数。

警告

此模块的伪随机生成器不应用于安全目的。对于安全或加密用途,请参阅 secrets 模块。

另请参阅

M. Matsumoto 和 T. Nishimura,“梅森旋转算法:一种 623 维均匀分布的伪随机数生成器”,ACM 建模与计算机仿真学报,第 8 卷,第 1 期,1998 年 1 月,第 3-30 页。

互补乘以进位方法,用于实现具有长周期和相对简单的更新操作的兼容替代随机数生成器。

簿记函数

random.seed(a=None, version=2)

初始化随机数生成器。

如果省略 a 或为 None,则使用当前系统时间。如果操作系统提供了随机源,则使用它们代替系统时间(有关可用性的详细信息,请参阅 os.urandom() 函数)。

如果 a 是整数,则直接使用它。

在版本 2(默认)中,strbytesbytearray 对象将转换为 int,并使用其所有位。

在版本 1(为从旧版本 Python 重现随机序列而提供)中,strbytes 的算法生成范围更窄的种子。

版本 3.2 中的变化: 移至版本 2 方案,该方案使用字符串种子中的所有位。

版本 3.11 中的变化: seed 必须是以下类型之一:Noneintfloatstrbytesbytearray

random.getstate()

返回一个捕获生成器当前内部状态的对象。此对象可以传递给 setstate() 以恢复状态。

random.setstate(state)

state 应该从先前调用 getstate() 时获得,setstate() 将生成器的内部状态恢复为调用 getstate() 时的状态。

字节函数

random.randbytes(n)

生成 n 个随机字节。

此方法不应用于生成安全令牌。请改用 secrets.token_bytes()

3.9 版新增。

整数函数

random.randrange(stop)
random.randrange(start, stop[, step])

range(start, stop, step) 中返回一个随机选择的元素。

这大致相当于 choice(range(start, stop, step)),但支持任意大的范围,并针对常见情况进行了优化。

位置参数模式与 range() 函数匹配。

不应使用关键字参数,因为它们可能会以意想不到的方式解释。例如,randrange(start=100) 会被解释为 randrange(0, 100, 1)

在 3.2 版更改: randrange() 在生成均匀分布的值方面更加复杂。以前,它使用类似于 int(random()*n) 的样式,这可能会产生略有不均匀的分布。

在 3.12 版更改: 不再支持非整数类型的自动转换。诸如 randrange(10.0)randrange(Fraction(10, 1)) 之类的调用现在会引发 TypeError

random.randint(a, b)

返回一个随机整数 N,以便 a <= N <= b。是 randrange(a, b+1) 的别名。

random.getrandbits(k)

返回一个具有 k 个随机位的非负 Python 整数。此方法随 Mersenne Twister 生成器一起提供,其他一些生成器也可能将其作为 API 的可选部分提供。如果可用,getrandbits() 使 randrange() 能够处理任意大的范围。

在 3.9 版更改: 此方法现在接受 k 为零。

序列函数

random.choice(seq)

从非空序列 seq 中返回一个随机元素。如果 seq 为空,则引发 IndexError

random.choices(population, weights=None, *, cum_weights=None, k=1)

返回一个大小为 k 的列表,其中包含从 population 中随机选择(可重复)的元素。如果 population 为空,则引发 IndexError

如果指定了 weights 序列,则根据相对权重进行选择。或者,如果给出了 cum_weights 序列,则根据累积权重进行选择(可以使用 itertools.accumulate() 计算)。例如,相对权重 [10, 5, 30, 5] 等价于累积权重 [10, 15, 45, 50]。在内部,相对权重在进行选择之前会转换为累积权重,因此提供累积权重可以节省工作量。

如果既未指定 weights 也未指定 cum_weights,则以相等的概率进行选择。如果提供了权重序列,则其长度必须与 population 序列的长度相同。同时指定 weightscum_weightsTypeError

weightscum_weights 可以使用任何与 random() 返回的 float 值互操作的数字类型(包括整数、浮点数和分数,但不包括小数)。权重假定为非负且有限。如果所有权重均为零,则引发 ValueError

对于给定的种子,具有相等权重的 choices() 函数通常会生成与重复调用 choice() 不同的序列。choices() 使用的算法使用浮点运算来确保内部一致性和速度。choice() 使用的算法默认使用整数运算和重复选择来避免舍入误差带来的小偏差。

3.6 版新增。

在 3.9 版更改: 如果所有权重均为零,则引发 ValueError

random.shuffle(x)

原地打乱序列 x

要打乱不可变序列并返回一个新的打乱列表,请改用 sample(x, k=len(x))

请注意,即使对于较小的 len(x)x 的排列总数也会很快超过大多数随机数生成器的周期。这意味着永远无法生成长序列的大多数排列。例如,长度为 2080 的序列是 Mersenne Twister 随机数生成器周期内可以容纳的最大序列。

在 3.11 版更改: 删除了可选参数 random

random.sample(population, k, *, counts=None)

从总体序列中选择 *k* 个唯一元素的列表。用于无放回随机抽样。

返回一个包含来自总体元素的新列表,同时保持原始总体不变。结果列表按选择顺序排列,因此所有子切片也将是有效的随机样本。这允许将抽奖获奖者(样本)划分为大奖和二等奖获奖者(子切片)。

总体的成员不需要是 可哈希的 或唯一的。如果总体包含重复项,则每次出现都是样本中可能的选择。

可以一次指定一个重复元素,也可以使用可选的仅限关键字的 *counts* 参数。例如,sample(['red', 'blue'], counts=[4, 2], k=5) 等效于 sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5)

要从一定范围的整数中选择样本,请使用 range() 对象作为参数。这对于从大量总体中采样特别快速且节省空间:sample(range(10000000), k=60)

如果样本量大于总体量,则会引发 ValueError

在 3.9 版更改: 添加了 *counts* 参数。

在 3.11 版更改: *population* 必须是一个序列。不再支持将集合自动转换为列表。

离散分布

以下函数生成离散分布。

random.binomialvariate(n=1, p=0.5)

二项分布。返回 *n* 次独立试验的成功次数,每次试验的成功概率为 *p*

数学上等价于

sum(random() < p for i in range(n))

试验次数 *n* 应为非负整数。成功概率 *p* 应介于 0.0 <= p <= 1.0 之间。结果是 0 <= X <= n 范围内的整数。

3.12 版新增。

实值分布

以下函数生成特定的实值分布。函数参数以分布方程中相应变量命名,如常见的数学实践中所用;这些方程中的大多数都可以在任何统计文本中找到。

random.random()

返回 0.0 <= X < 1.0 范围内的下一个随机浮点数

random.uniform(a, b)

返回一个随机浮点数 *N*,使得当 a <= b 时,a <= N <= b,当 b < a 时,b <= N <= a

端点值 b 是否包含在范围内取决于表达式 a + (b-a) * random() 中的浮点舍入。

random.triangular(low, high, mode)

返回一个随机浮点数 *N*,使得 low <= N <= high,并在这些边界之间具有指定的 *mode*。*low* 和 *high* 边界默认为零和一。*mode* 参数默认为边界之间的中点,给出对称分布。

random.betavariate(alpha, beta)

β 分布。参数的条件是 alpha > 0beta > 0。返回值介于 0 和 1 之间。

random.expovariate(lambd=1.0)

指数分布。*lambd* 是 1.0 除以期望的均值。它应该是非零的。(该参数应该称为“lambda”,但这是 Python 中的保留字。)如果 *lambd* 为正,则返回值范围从 0 到正无穷大,如果 *lambd* 为负,则返回值范围从负无穷大到 0。

在 3.12 版更改: 添加了 lambd 的默认值。

random.gammavariate(alpha, beta)

伽马分布。(*不是* 伽马函数!)形状和尺度参数 *alpha* 和 *beta* 必须具有正值。(调用约定有所不同,有些来源将“beta”定义为尺度的倒数)。

概率分布函数为

          x ** (alpha - 1) * math.exp(-x / beta)
pdf(x) =  --------------------------------------
            math.gamma(alpha) * beta ** alpha
random.gauss(mu=0.0, sigma=1.0)

正态分布,也称为高斯分布。*mu* 是均值,*sigma* 是标准差。这比下面定义的 normalvariate() 函数稍快。

多线程注意事项:当两个线程同时调用此函数时,它们可能会收到相同的返回值。可以通过三种方式避免这种情况。1) 让每个线程使用不同的随机数生成器实例。2) 对所有调用加锁。3) 使用速度较慢但线程安全的 normalvariate() 函数。

版本 3.11 中的变化: musigma 现在有默认参数。

random.lognormvariate(mu, sigma)

对数正态分布。如果取此分布的自然对数,将得到一个均值为 mu、标准差为 sigma 的正态分布。mu 可以是任何值,sigma 必须大于零。

random.normalvariate(mu=0.0, sigma=1.0)

正态分布。mu 是均值,sigma 是标准差。

版本 3.11 中的变化: musigma 现在有默认参数。

random.vonmisesvariate(mu, kappa)

mu 是平均角度,以介于 0 到 2*pi 之间的弧度表示,kappa 是浓度参数,必须大于或等于零。如果 kappa 等于零,则此分布将简化为 0 到 2*pi 范围内的均匀随机角度。

random.paretovariate(alpha)

帕累托分布。alpha 是形状参数。

random.weibullvariate(alpha, beta)

威布尔分布。alpha 是尺度参数,beta 是形状参数。

备用生成器

class random.Random([seed])

实现 random 模块使用的默认伪随机数生成器的类。

版本 3.11 中的变化: 以前 seed 可以是任何可哈希对象。现在它仅限于:Noneintfloatstrbytesbytearray

如果 Random 的子类希望使用不同的基本生成器,则应覆盖以下方法

seed(a=None, version=2)

在子类中覆盖此方法以自定义 Random 实例的 seed() 行为。

getstate()

在子类中覆盖此方法以自定义 Random 实例的 getstate() 行为。

setstate(state)

在子类中覆盖此方法以自定义 Random 实例的 setstate() 行为。

random()

在子类中覆盖此方法以自定义 Random 实例的 random() 行为。

(可选)自定义生成器子类还可以提供以下方法

getrandbits(k)

在子类中覆盖此方法以自定义 Random 实例的 getrandbits() 行为。

class random.SystemRandom([seed])

使用 os.urandom() 函数从操作系统提供的源生成随机数的类。并非在所有系统上都可用。不依赖于软件状态,并且序列不可重现。因此,seed() 方法无效,将被忽略。如果调用 getstate()setstate() 方法,则会引发 NotImplementedError

关于可重复性的说明

有时,能够重现伪随机数生成器给出的序列非常有用。通过重复使用种子值,只要没有多个线程在运行,就可以在每次运行时重现相同的序列。

大多数 random 模块的算法和种子函数都可能在不同的 Python 版本之间发生变化,但保证有两个方面不会改变:

  • 如果添加了新的种子生成方法,则将提供向后兼容的种子生成器。

  • 当兼容的种子生成器获得相同的种子时,生成器的 random() 方法将继续生成相同的序列。

示例

基本示例

>>> random()                          # Random float:  0.0 <= x < 1.0
0.37444887175646646

>>> uniform(2.5, 10.0)                # Random float:  2.5 <= x <= 10.0
3.1800146073117523

>>> expovariate(1 / 5)                # Interval between arrivals averaging 5 seconds
5.148957571865031

>>> randrange(10)                     # Integer from 0 to 9 inclusive
7

>>> randrange(0, 101, 2)              # Even integer from 0 to 100 inclusive
26

>>> choice(['win', 'lose', 'draw'])   # Single random element from a sequence
'draw'

>>> deck = 'ace two three four'.split()
>>> shuffle(deck)                     # Shuffle a list
>>> deck
['four', 'two', 'ace', 'three']

>>> sample([10, 20, 30, 40, 50], k=4) # Four samples without replacement
[40, 10, 50, 30]

模拟

>>> # Six roulette wheel spins (weighted sampling with replacement)
>>> choices(['red', 'black', 'green'], [18, 18, 2], k=6)
['red', 'green', 'black', 'black', 'red', 'black']

>>> # Deal 20 cards without replacement from a deck
>>> # of 52 playing cards, and determine the proportion of cards
>>> # with a ten-value:  ten, jack, queen, or king.
>>> deal = sample(['tens', 'low cards'], counts=[16, 36], k=20)
>>> deal.count('tens') / 20
0.15

>>> # Estimate the probability of getting 5 or more heads from 7 spins
>>> # of a biased coin that settles on heads 60% of the time.
>>> sum(binomialvariate(n=7, p=0.6) >= 5 for i in range(10_000)) / 10_000
0.4169

>>> # Probability of the median of 5 samples being in middle two quartiles
>>> def trial():
...     return 2_500 <= sorted(choices(range(10_000), k=5))[2] < 7_500
...
>>> sum(trial() for i in range(10_000)) / 10_000
0.7958

使用带有放回的重采样来估计样本均值的置信区间的 统计自助法 示例

# https://www.thoughtco.com/example-of-bootstrapping-3126155
from statistics import fmean as mean
from random import choices

data = [41, 50, 29, 37, 81, 30, 73, 63, 20, 35, 68, 22, 60, 31, 95]
means = sorted(mean(choices(data, k=len(data))) for i in range(100))
print(f'The sample mean of {mean(data):.1f} has a 90% confidence '
      f'interval from {means[5]:.1f} to {means[94]:.1f}')

使用 重采样排列检验 来确定观察到的药物与安慰剂效果之间差异的统计显着性或 p 值 的示例

# Example from "Statistics is Easy" by Dennis Shasha and Manda Wilson
from statistics import fmean as mean
from random import shuffle

drug = [54, 73, 53, 70, 73, 68, 52, 65, 65]
placebo = [54, 51, 58, 44, 55, 52, 42, 47, 58, 46]
observed_diff = mean(drug) - mean(placebo)

n = 10_000
count = 0
combined = drug + placebo
for i in range(n):
    shuffle(combined)
    new_diff = mean(combined[:len(drug)]) - mean(combined[len(drug):])
    count += (new_diff >= observed_diff)

print(f'{n} label reshufflings produced only {count} instances with a difference')
print(f'at least as extreme as the observed difference of {observed_diff:.1f}.')
print(f'The one-sided p-value of {count / n:.4f} leads us to reject the null')
print(f'hypothesis that there is no difference between the drug and the placebo.')

多服务器队列的到达时间和服务交付模拟

from heapq import heapify, heapreplace
from random import expovariate, gauss
from statistics import mean, quantiles

average_arrival_interval = 5.6
average_service_time = 15.0
stdev_service_time = 3.5
num_servers = 3

waits = []
arrival_time = 0.0
servers = [0.0] * num_servers  # time when each server becomes available
heapify(servers)
for i in range(1_000_000):
    arrival_time += expovariate(1.0 / average_arrival_interval)
    next_server_available = servers[0]
    wait = max(0.0, next_server_available - arrival_time)
    waits.append(wait)
    service_duration = max(0.0, gauss(average_service_time, stdev_service_time))
    service_completed = arrival_time + wait + service_duration
    heapreplace(servers, service_completed)

print(f'Mean wait: {mean(waits):.1f}   Max wait: {max(waits):.1f}')
print('Quartiles:', [round(q, 1) for q in quantiles(waits)])

另请参阅

黑客统计学Jake Vanderplas 制作的视频教程,介绍了如何仅使用模拟、抽样、洗牌和交叉验证等几个基本概念进行统计分析。

经济学模拟Peter Norvig 对市场进行的模拟,展示了如何有效地使用本模块提供的许多工具和分布(高斯、均匀、样本、贝塔、选择、三角形和随机范围)。

概率论的具体介绍(使用 Python)Peter Norvig 编写的教程,涵盖了概率论的基础知识、如何编写模拟以及如何使用 Python 进行数据分析。

方法

这些方法展示了如何从 itertools 模块中的组合迭代器中高效地进行随机选择

def random_product(*args, repeat=1):
    "Random selection from itertools.product(*args, **kwds)"
    pools = [tuple(pool) for pool in args] * repeat
    return tuple(map(random.choice, pools))

def random_permutation(iterable, r=None):
    "Random selection from itertools.permutations(iterable, r)"
    pool = tuple(iterable)
    r = len(pool) if r is None else r
    return tuple(random.sample(pool, r))

def random_combination(iterable, r):
    "Random selection from itertools.combinations(iterable, r)"
    pool = tuple(iterable)
    n = len(pool)
    indices = sorted(random.sample(range(n), r))
    return tuple(pool[i] for i in indices)

def random_combination_with_replacement(iterable, r):
    "Choose r elements with replacement.  Order the result to match the iterable."
    # Result will be in set(itertools.combinations_with_replacement(iterable, r)).
    pool = tuple(iterable)
    n = len(pool)
    indices = sorted(random.choices(range(n), k=r))
    return tuple(pool[i] for i in indices)

默认的 random()0.0 ≤ x < 1.0 范围内返回 2⁻⁵³ 的倍数。所有这些数字都均匀分布,并且可以精确表示为 Python 浮点数。但是,该区间内许多其他可表示的浮点数都不是可能的选项。例如,0.05954861408025609 不是 2⁻⁵³ 的整数倍。

以下方法采用不同的方法。区间内的所有浮点数都是可能的选项。尾数来自 2⁵² ≤ 尾数 < 2⁵³ 范围内的整数的均匀分布。指数来自几何分布,其中小于 -53 的指数出现的频率是下一个较大指数的一半。

from random import Random
from math import ldexp

class FullRandom(Random):

    def random(self):
        mantissa = 0x10_0000_0000_0000 | self.getrandbits(52)
        exponent = -53
        x = 0
        while not x:
            x = self.getrandbits(32)
            exponent += x.bit_length() - 32
        return ldexp(mantissa, exponent)

类中的所有 实值分布 都将使用新方法

>>> fr = FullRandom()
>>> fr.random()
0.05954861408025609
>>> fr.expovariate(0.25)
8.87925541791544

该方法在概念上等效于从 0.0 ≤ x < 1.0 范围内 2⁻¹⁰⁷⁴ 的所有倍数中进行选择的算法。所有这些数字都均匀分布,但大多数必须向下舍入到最接近的可表示 Python 浮点数。(值 2⁻¹⁰⁷⁴ 是最小的正非规范化浮点数,等于 math.ulp(0.0)。)

另请参阅

生成伪随机浮点值 是 Allen B. Downey 撰写的一篇论文,描述了如何生成比 random() 通常生成的更精细的浮点数。