random — 生成偽隨機數

原始碼: Lib/random.py


此模組為各種分佈實現偽隨機數生成器。

對於整數,可以從一個範圍中進行均勻選擇。對於序列,可以均勻選擇一個隨機元素,有一個函式可以就地生成列表的隨機排列,還有一個函式用於無放回隨機抽樣。

在實數線上,有用於計算均勻分佈、正態分佈(高斯分佈)、對數正態分佈、負指數分佈、伽馬分佈和貝塔分佈的函式。對於生成角度分佈,可以使用 von Mises 分佈。

幾乎所有模組函式都依賴於基本函式 random(),它在半開區間 0.0 <= X < 1.0 中生成一個均勻分佈的隨機浮點數。Python 使用 Mersenne Twister 作為核心生成器。它生成 53 位精度的浮點數,週期為 2**19937-1。C 語言的底層實現既快速又執行緒安全。Mersenne Twister 是現有經過最廣泛測試的隨機數生成器之一。然而,由於它完全是確定性的,因此不適用於所有目的,並且完全不適用於加密目的。

此模組提供的函式實際上是 random.Random 類的一個隱藏例項的繫結方法。您可以例項化自己的 Random 例項,以獲得不共享狀態的生成器。

如果您想使用自己設計的不同基本生成器,Random 類也可以被子類化:有關更多詳細資訊,請參閱該類的文件。

random 模組還提供了 SystemRandom 類,該類使用系統函式 os.urandom() 從作業系統提供的源生成隨機數。

警告

此模組的偽隨機生成器不應用於安全目的。對於安全或加密用途,請參閱 secrets 模組。

參見

M. Matsumoto 和 T. Nishimura, “Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom number generator”, ACM Transactions on Modeling and Computer Simulation Vol. 8, No. 1, January pp.3–30 1998。

Complementary-Multiply-with-Carry recipe,用於生成具有長週期和相對簡單更新操作的相容替代隨機數生成器。

備註

全域性隨機數生成器和 Random 例項都是執行緒安全的。然而,在自由執行緒構建中,對全域性生成器或同一 Random 例項的併發呼叫可能會遇到爭用和效能不佳。請考慮為每個執行緒使用獨立的 Random 例項。

簿記函式

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_weights 將引發 TypeError

weightscum_weights 可以使用與 float 值(由 random() 返回)互動操作的任何數字型別(包括整數、浮點數和分數,但不包括十進位制數)。權重被假定為非負且有限。如果所有權重都為零,則引發 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 <= ba <= N <= b,當 b < ab <= N <= a

端點值 b 是否包含在範圍內取決於表示式 a + (b-a) * random() 中的浮點舍入。

random.triangular(low, high, mode)

返回一個隨機浮點數 N,使得 low <= N <= high,並且在這些邊界之間具有指定的 modelowhigh 邊界預設為零和一。mode 引數預設為邊界之間的中點,給出對稱分佈。

random.betavariate(alpha, beta)

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)

伽馬分佈。(*不是*伽馬函式!)形狀和尺度引數 alphabeta 必須為正值。(呼叫約定各不相同,有些來源將“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 中修改: *mu* 和 *sigma* 現在具有預設引數。

random.lognormvariate(mu, sigma)

對數正態分佈。如果您取此分佈的自然對數,您將得到一個均值為 mu 且標準差為 sigma 的正態分佈。mu 可以有任何值,而 sigma 必須大於零。

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

正態分佈。mu 是均值,sigma 是標準差。

版本 3.11 中修改: *mu* 和 *sigma* 現在具有預設引數。

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() 行為。

randbytes(n)

在子類中覆蓋此方法以自定義 Random 例項的 randbytes() 行為。

class random.SystemRandom([seed])

使用 os.urandom() 函式從作業系統提供的源生成隨機數的類。並非所有系統都可用。不依賴於軟體狀態,序列不可重現。因此,seed() 方法無效並被忽略。如果呼叫 getstate()setstate() 方法,則會引發 NotImplementedError

關於可重現性的說明

有時能夠重現偽隨機數生成器給出的序列是很有用的。透過重用種子值,只要沒有多個執行緒執行,相同的序列就應該可以在不同執行之間重現。

隨機模組的大多數演算法和種子函式在 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)])

參見

Statistics for Hackers,由 Jake Vanderplas 製作的影片教程,介紹僅使用一些基本概念進行統計分析,包括模擬、取樣、洗牌和交叉驗證。

Economics Simulation,由 Peter Norvig 製作的市場模擬,展示了有效使用此模組提供的許多工具和分佈(高斯、均勻、樣本、貝塔分佈、選擇、三角分佈和 randrange)。

A Concrete Introduction to Probability (using 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)。)

參見

Generating Pseudo-random Floating-Point Values,Allen B. Downey 的一篇論文,描述了生成比 random() 通常生成的浮點數更精細的方法。

命令列用法

在 3.13 版本加入。

random 模組可以從命令列執行。

python -m random [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]

接受以下選項

-h, --help

顯示幫助資訊並退出。

-c CHOICE [CHOICE ...]
--choice CHOICE [CHOICE ...]

使用 choice() 列印一個隨機選擇。

-i <N>
--integer <N>

使用 randint() 列印一個介於 1 和 N(包括 N)之間的隨機整數。

-f <N>
--float <N>

使用 uniform() 列印一個介於 0 和 N(包括 N)之間的隨機浮點數。

如果沒有給出選項,輸出取決於輸入

命令列示例

以下是 random 命令列介面的一些示例

$ # Choose one at random
$ python -m random egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
Lobster Thermidor aux crevettes with a Mornay sauce

$ # Random integer
$ python -m random 6
6

$ # Random floating-point number
$ python -m random 1.8
1.7080016272295635

$ # With explicit arguments
$ python  -m random --choice egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
egg

$ python -m random --integer 6
3

$ python -m random --float 1.8
1.5666339105010318

$ python -m random --integer 6
5

$ python -m random --float 6
3.1942323316565915