跳至內容

使用者:Xyy23330121/Python/隨機數

來自維基學院


許多重要功能需要用隨機數來實現。本頁面將講解幾種生成隨機數的方法。其中 random 模塊的隨機數不適合用作加密目的,而 secret 模塊的隨機數適合用作加密。

random模塊:均勻分布

[編輯 | 編輯原始碼]

隨機字節

[編輯 | 編輯原始碼]

random.randbytes(n)

[編輯 | 編輯原始碼]

生成 n 個隨機字節。

隨機整數

[編輯 | 編輯原始碼]

random.randrange(stop)

[編輯 | 編輯原始碼]

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

隨機在 range(start, stop, step) 中選擇一個元素,並返回。

由於參數解釋方式的原因,在只有一個參數時,不應該使用關鍵字參數。比如 randrange(start = 10) 會被解釋為 randrange(0, 10, 1)

random.randint(a, b)

[編輯 | 編輯原始碼]

相當於 random.randrange(a, b+1)

random.getrandbits(k)

[編輯 | 編輯原始碼]

返回具有 k 個隨機比特位的非負整數。

隨機浮點數

[編輯 | 編輯原始碼]

random.random()

[編輯 | 編輯原始碼]

返回一個隨機的浮點數 x,滿足 0 <= x < 1

random.uniform(a, b)

[編輯 | 編輯原始碼]

返回在 a 和 b 之間的一個隨機的浮點數。

其結果等同於 a + (b-a)*random.random(),是否包含 b 取決於浮點捨入誤差。

從序列中隨機選取對象

[編輯 | 編輯原始碼]

random.choice(seq)

[編輯 | 編輯原始碼]

從非空序列 seq 中,隨機選取一個元素,並返回選取的元素。如果 seq 為空序列,則報錯 IndexError。

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

[編輯 | 編輯原始碼]

從非空序列 population 中,有重複地選取元素 k 次,並返回選取得到的列表。如果 population 為空序列,則報錯 IndexError。

weights 為權重序列。它表示 population 中每個元素的權重。該序列的長度應該與 population 相等。各個權重值應當為非負且有限的數值。且應當使用能與浮點數互相運算的類型(比如整數、浮點數、fractions.Fraction,不包括 decimal.Decimal)。

cum_weights 為累計權重序列。它以累積的方式表示元素權重,比如:

cum_weights 為[1,2,4] 與 weights 為 [1,1,2] 的效果是相同的。

在函數內部,權重序列會被轉化為累計權重序列再進行計算。

cum_weights 不應和 weights 同時設置。如果同時設置 weights 和 cum_weights,則報錯 TypeError。如果兩者都沒有被設置,則默認每個元素的權重相等。

即便使用相同的種子,不設置權重,該函數返回的序列,與 random.choice 函數反覆 k 次得到的序列是不同的。這是因為 random.choice 默認使用整數運算算法來規避捨入誤差,而該函數使用浮點運算。

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

[編輯 | 編輯原始碼]

從非空序列 population 中,無重複地選取元素 k 次,並返回選取得到的列表。如果 population 為空序列,則報錯 IndexError。

counts 指定 population 中,元素出現的次數。它應當是長度等同於 population 長度的、全是整數的序列。

比如:random.sample([0,1,1,1],k=2) 等同於 random.sample([0,1],k=2,counts=[1,3])

若不指定 counts,相當於設 counts 為全 1 序列。如果 k > sum(counts),則報錯 ValueError。

對於從一系列整數中選取樣本的情況,使用 range 函數生成的 range 對象作為 population 會大量節省空間並提高計算速度。

隨機打亂序列

[編輯 | 編輯原始碼]

random.shuffle(x)

[編輯 | 編輯原始碼]

將可變序列 x 打亂。類似 list.sort 方法,該函數會直接修改 x 的內容。

由於排列數可以隨着 x 長度增加而快速增加——並超出隨機數生成器的周期。因此,對於過長序列而言,其大多數排列永遠不會被 random.shuffle 函數所產生。

若使用默認的 Mersenne Twister 隨機數生成器,過長意味着序列長度大於 2080。

random模塊:其它分布

[編輯 | 編輯原始碼]

整數:二項式分布

[編輯 | 編輯原始碼]

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

[編輯 | 編輯原始碼]

返回 n 次獨立實驗,在每次實驗成功率為 p 時,成功的次數。

浮點數分布

[編輯 | 編輯原始碼]

random.triangular(low, high, mode)

[編輯 | 編輯原始碼]

三角分布返回浮點數。

默認 (low, high, mode)(0, 1, 0.5)

random.batavariate(alpha, beta)

[編輯 | 編輯原始碼]

beta 分布返回浮點數。

其中 alphabeta 都應大於零。

random.expovariate(lambd=1.0)

[編輯 | 編輯原始碼]

指數分布返回浮點數。

其中 lambd 應是非零的。

random.gammavariate(alpha, beta)

[編輯 | 編輯原始碼]

gamma 分布返回浮點數。

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

[編輯 | 編輯原始碼]

正態分布(也即高斯分布)返回浮點數。

如果兩個線程同時調用此函數,它們可能返回相同的值。為避免這個問題,可以:

  • 讓每個線程使用不同的隨機數生成器實例
  • 為每一次調用加鎖
  • 改用速度較慢,但是線程安全的 random.normalvariate 函數。

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

[編輯 | 編輯原始碼]

正態分布(也即高斯分布)返回浮點數。

random.lognormvariate(mu, sigma)

[編輯 | 編輯原始碼]

對數正態分布返回浮點數。

random.vonmisesvariate(mu, kappa)

[編輯 | 編輯原始碼]

馮·米塞斯分布返回浮點數。

random.paretovariate(alpha)

[編輯 | 編輯原始碼]

帕累托分布返回浮點數。

random.weibullvariate(alpha, beta)

[編輯 | 編輯原始碼]

威布爾分布返回浮點數。

random 模塊:隨機數生成器

[編輯 | 編輯原始碼]

以上所述的 random 模塊的函數都基於 random 模塊提供的隨機數生成器。

默認隨機數生成器

[編輯 | 編輯原始碼]

random 模塊的幾乎所有函數都依賴於默認隨機數生成器 random.Random 的一個核心方法 random.Random().random()。默認隨機數生成器使用高效的 梅森旋轉算法 作為核心隨機數生成器。但是,只要知道種子,梅森旋轉算法的結果就是完全確定的。因此 random 模塊的隨機數不適合用作加密目的。

操作當前隨機數生成器

[編輯 | 編輯原始碼]

以下幾個操作會更改當前使用的默認隨機數生成器。

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

[編輯 | 編輯原始碼]

初始化隨機數生成器。a 為使用的種子,而 version 代表處理種子的方法。

如果 a 為 None,則使用操作系統提供的隨機數,如果操作系統不提供隨機數,則使用當前系統時間。

如果要指定 a,a 可以為整數、字符串、bytes 或 bytearray。其中整數不會被處理,並將直接作為種子使用。而字符串、bytes 和 bytearray 都會被處理:

  • 如果 version 為 2,則會將字符串、bytes 或 bytearray 對象轉換為整數,並使用整數的所有位數作為種子。
  • 如果 version 為 1,相對於 version 為 2 的時候,如果用字符串做種子,可能的種子範圍較小。不建議設置 version 為 1,應僅在需要從舊版本的 Python 再現隨機序列時使用它。

random.getstate() 和 random.setstate(state)

[編輯 | 編輯原始碼]

random.getstate() 會返回表示隨機數生成器內部狀態的元組。該元組可以傳入 random.setstate(state) 函數來恢復狀態。

隨機數生成器對象

[編輯 | 編輯原始碼]

random.Random([seed])

[編輯 | 編輯原始碼]

random 模塊使用的默認隨機數生成器。

核心方法
[編輯 | 編輯原始碼]

random.Random 的以下幾個實例方法和核心隨機數生成器有關。

seed(a = None, version=2)
[編輯 | 編輯原始碼]

random.seed 函數調用的方法。

getstate()
[編輯 | 編輯原始碼]

random.getstate 函數調用的方法。

setstate(state)
[編輯 | 編輯原始碼]

random.setstate 函數調用的方法。

random.random 函數調用的方法。

getrandbits(k)
[編輯 | 編輯原始碼]

random.getrandbits 函數調用的方法。

延申方法
[編輯 | 編輯原始碼]

這裡所述「延申方法」是基於「核心方法」的、random 模塊的函數使用的方法。特別的,每個函數使用的方法都和函數同名。即:

  • 在隨機數生成器內部狀態相同的情況下,random.choice(seq) 的結果等同於 random.Random().choice(seq) 的結果。

由於前面章節的函數已經充分介紹過了。這裡不再具體列出所有方法。

random.SystemRandom([seed])

[編輯 | 編輯原始碼]

使用 os.urandom() 函數的類。該類型並非適用於所有系統、也不依賴於軟件狀態。其生成的隨機數序列無法重現。

該類型提供的方法和上面的方法相同。其中,seed 方法沒有任何作用。

自定義隨機數生成器

[編輯 | 編輯原始碼]

要自定義不同於默認梅森旋轉算法的隨機數生成器,應該自定義 random.Random 的子類,並修改其核心方法。

在使用自定義的生成器時,直接用繼承自 random.Random 的「延申方法」即可。只要核心方法正確且恰當,延申方法的結果也應當符合預期。

secret 模塊:安全隨機數

[編輯 | 編輯原始碼]

secret 模塊用於生成高度加密的、安全的隨機數。適用於管理密碼、賬戶驗證、安全憑據等機密數據。

隨機數生成器

[編輯 | 編輯原始碼]

class secrets.SystemRandom

[編輯 | 編輯原始碼]

類似 random.SystemRandom,這是使用 os.urandom() 函數的類。

生成隨機數

[編輯 | 編輯原始碼]

secrets.choice(sequence)

[編輯 | 編輯原始碼]

從非空序列 sequence 中隨機選取一個元素,並返回選取的元素。

secrets.randbelow(n)

[編輯 | 編輯原始碼]

返回一個隨機整數。範圍為

secrets.randbits(k)

[編輯 | 編輯原始碼]

返回具有 k 個隨機比特位的非負整數。

生成 Token

[編輯 | 編輯原始碼]

secrets.token_bytes([nbytes=None])

[編輯 | 編輯原始碼]

返回含 nbytes 個字節的隨機 bytes 對象。如果 nbytes 為 None,則使用合理的默認值。

隨着計算機能力的提升,保證安全所需的、隨機字節數也會提升。因此,nbytes 的默認值在版本更新時會隨時改變。

secrets.token_hex([nbytes=None])

[編輯 | 編輯原始碼]

類似 secrets.token_bytes 函數,但返回的是對應的十六進制字符串。

secrets.token_urlsafe([nbytes=None])

[編輯 | 編輯原始碼]

類似 secrets.token_bytes 函數,但返回的是對應的、Base64 編碼的字符串。平均每個字節對應 1.3 個字符。

定時攻擊

[編輯 | 編輯原始碼]

對於 Python 默認的字符串之間和 bytes 之間的比較方式,我們在進行以下比較時,會有不同的返回時間:

  1. "password" == "a",在比較第一位時立刻返回 False
  2. "password" == "pa",在比較第三位時立刻返回 False

通過測定返回的時間差,攻擊者可以按位分別匹配比較的位數、而非將所有位數整體匹配。

簡單來講,對於每個字符有 n 種可能性,字符串長度為 m 的情況。如果 k 個字符的比較足以測量返回的時間差,這會使得攻擊所需遍歷的可能性數目從最高約n**m種變為約(n**k)*(m/k)種。

可以使用以下函數解決則會個問題:

secrets.compare_digest(a, b)

[編輯 | 編輯原始碼]

輸入兩個字符串或兩個 bytes。如果兩個字符串(或 bytes)相等,則返回 True。反之,返回 False

該函數不考慮高效性,無論 a 和 b 之間相匹配的位數有多少,它都會對每一位都進行匹配計算。這使得上面所述的時間差不存在。