使用者: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 分布返回浮點數。
其中 alpha 和 beta 都應大於零。
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.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 之間的比較方式,我們在進行以下比較時,會有不同的返回時間:
"password" == "a"
,在比較第一位時立刻返回False
"password" == "pa"
,在比較第三位時立刻返回False
通過測定返回的時間差,攻擊者可以按位分別匹配比較的位數、而非將所有位數整體匹配。
簡單來講,對於每個字符有 n 種可能性,字符串長度為 m 的情況。如果 k 個字符的比較足以測量返回的時間差,這會使得攻擊所需遍歷的可能性數目從最高約n**m
種變為約(n**k)*(m/k)
種。
可以使用以下函數解決則會個問題:
secrets.compare_digest(a, b)
編輯輸入兩個字符串或兩個 bytes。如果兩個字符串(或 bytes)相等,則返回 True
。反之,返回 False
。
該函數不考慮高效性,無論 a 和 b 之間相匹配的位數有多少,它都會對每一位都進行匹配計算。這使得上面所述的時間差不存在。