結論
# randint(a,b)の代わり
secretsRandint = lambda a,b: (secrets.randbelow((b-a)+1))+a
# choices(l, k=n)の代わり
secretsChoices = lambda table, k: [secrets.choice(table) for _ in range(k)]
背景
Pythonで乱数を生成する際にはrandom.randintが使えます。しかし、この関数は簡単な規則で乱数を生成されています。このため、パスワードの精製などにはsecretsモジュールの仕様が推奨されます。
ただし、secretsモジュールの関数はrandomと少し異なります。同じように使えるようにします。
randint(a,b)
予備知識: []は閉区間で以上・以下です。()は開区間で超過・未満です。
- random.randint(a,b) は [a,b]を返します。random.randint(4,7) なら 4,5,6,7が出ます。
- secrets.randbelow(n) は [0,n)を返します。secrets.randbelow(4) は 0,1,2,3が出ます。
ここでrandomのように(a,b)の区間を返したい場合、
- secrets.randbelow( (b-a)+1) ) + a
とします。
例: secretsで4,5,6,7を返したい場合、
- secrets.randbelow( (7-4)+1) ) + 4 と代入し
- secrets.randbelow( 4 ) + 4 となる。
- これは、(0,1,2,3)の一様に出現するランダムな集合に+4なので、結果(4,5,6,7)となる
実装例
import secrets
secretsRandint = lambda a,b: (secrets.randbelow((b-a)+1))+a
import collections
# Counter({1: 2020, 2: 2011, 5: 2008, 3: 1992, 4: 1969}) のように一様にランダムに選択されている
print(collections.Counter([secretsRandint(1,5) for _ in range(10000)]))
choices
# ['1', '7', '2', '5', '3', '2', '3', '7', '6', '3']
import string, random
random.choices(string.digits, k=10)
となる便利な関数です。なぜかsecretsにはchoiceという1文字をランダムに取り出す関数しかありません。n回呼べばよいです。
実装例
secretsChoices = lambda table, k: [secrets.choice(table) for _ in range(k)]
# ['6', '1', '6', '0', '9', '7', '3', '1', '8', '7']
print(secretsChoices(string.digits, 10))