標準庫之 random 模組

【1758872】的博客發表於2024-09-26

一、介紹random模組

1.1、random模組簡介

random模組是Python標準庫中用於生成偽隨機數的模組,偽隨機數是透過演算法生成的數列,在一定範圍內表現出隨機性,雖然這些數列在一定程度上是可預測的,但對於大多數應用來說已經足夠。

二、random模組的基本功能

2.1、整數用函式

2.1.1、random.randrange()

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

返回從 range(start, stop, step) 隨機選擇的一個元素。

支援設定步長,可以生成按固定間隔的隨機數,這對於需要特定模式或間隔的隨機數生成非常有用。注意:不包括右邊界

# 返回 0 <= N < 100 範圍內的隨機整數
print(random.randrange(100))
# 返回 100 <= N < 200 範圍內的隨機整數
print(random.randrange(100, 200))
# 返回 100 <= N < 200 範圍內的隨機整數,步長為5
print(random.randrange(100, 200, 5))

2.1.2、random.randint()

random.randint(a, b)

返回隨機整數 N 滿足 a <= N <= b。相當於 randrange(a, b+1)。

不支援設定步長,只能生成指定範圍內的任意整數。注意:包括左邊界和右邊界

# 返回 100 <= N <= 200 範圍內的隨機整數
print(random.randint(100, 200))

2.1.3、random.getrandbits()

random.getrandbits(k)

返回隨機整數 N 滿足 0 <= N <= 2^k-1,例如,getrandbits(16)將生成一個0到65535之間的隨機整數。這個函式特別適用於需要生成大範圍隨機數的情況。

# 返回 0 <= N <= 65535(2的16次方再減去1) 範圍內的隨機整數
print(random.getrandbits(16))

2.2、序列用函式

2.2.1、random.choice()

random.choice(seq)

從非空序列 seq 返回一個隨機元素。 如果 seq 為空,則引發 IndexError。

a = ['alice', 'bob', 'helen', 'jack', 'sue']
b = ('alice', 'bob', 'helen', 'jack', 'sue')
c = {'alice', 'bob', 'helen', 'jack', 'sue'}
d = {'alice': 16, 'bob': 18, 'helen': 19, 'jack': 20, 'sue': 22}
e = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
# 從列表中隨機選取一個元素
print(random.choice(a))
# 從元組中隨機選取一個元素
print(random.choice(b))
# 從字串中隨機選取一個元素
print(random.choice(e))
# ———————————————————————————————————————————
# 不支援從集合中隨機選取,會報錯
# print(random.choice(c))
# 不支援從字典中隨機選取,會報錯,但可以使用 random.choice(list(d.items())) 的方法來實現
# print(random.choice(d))

2.2.2、random.choices()

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

  • population:必需引數,指定要進行選擇的序列(可以是列表、元組、字串等)。
  • weights:可選引數,指定每個元素的權重(機率)。如果不指定,則預設每個元素的權重相等。
  • cum_weights:可選引數,指定累計權重。如果指定了cum_weights,則必需省略weights引數。
  • k:可選引數,指定要選擇的元素個數。預設為1,即只選擇一個元素。

從序列中有放回地隨機抽取k個元素,並返回對應的陣列,weights引數代表每個元素被選到的相對權重,而cum_weights代表的是累積權重,它們的長度需和待抽取序列population長度一致。若無設定權重,則每個元素被抽到的權重是一樣的。

注意:權重不能為負值,否則會報錯。

import random
fruits = ['apple', 'banana', 'orange', 'grape', 'watermelon']
weights = [0.1, 0.2, 0.3, 0.2, 0.2]
cum_weights = [0.1, 0.4, 0.7, 0.9, 1.0]
# 從列表中隨機選擇一個元素
chosen_fruit = random.choices(fruits)
# 選擇多個元素
chosen_fruits = random.choices(fruits, k=3)
# 按照機率從列表中隨機選擇一個元素
chosen_fruit = random.choices(fruits, weights=weights)
# 利用cum_weights引數選擇元素
chosen_fruit = random.choices(fruits, cum_weights=cum_weights)

選擇多個元素並計算選擇的次數

import random
fruits = ['apple', 'banana', 'orange', 'grape', 'watermelon']
chosen_fruits = random.choices(fruits, k=1000)
fruit_counts = {}
for fruit in chosen_fruits:
    if fruit in fruit_counts:
        fruit_counts[fruit] += 1
    else:
        fruit_counts[fruit] = 1
print(fruit_counts)

2.2.3、random.shuffle()

random.shuffle(x)

就地將序列 x 隨機打亂位置。

import random
fruits = ['apple', 'banana', 'orange', 'grape', 'watermelon']
random.shuffle(fruits)  # 注意:改變的是原列表
print(fruits)  # ['orange', 'watermelon', 'grape', 'banana', 'apple']

2.2.4、random.sample()

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

從序列中無放回地隨機抽取k個元素。用於無重複的隨機抽樣。

import random
fruits = ['apple', 'banana', 'orange', 'grape', 'watermelon']
# 返回一個新的隨機打亂序列, 注意:原列表是沒有改變的
print(random.sample(fruits,len(fruits)))  # ['orange', 'watermelon', 'apple', 'banana', 'grape']
print(fruits)  # ['apple', 'banana', 'orange', 'grape', 'watermelon']

重複的元素可以一個個地直接列出,或使用可選的僅限關鍵字形參 counts 來指定。 例如,sample(['red', 'blue'], counts=[4, 2], k=5) 等價於 sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5)。

要從一系列整數中選擇樣本,請使用 range() 物件作為引數。 對於從大量人群中取樣,這種方法特別快速且節省空間:sample(range(10000000), k=60) 。

2.3、離散分佈

2.3.1、random.binomialvariate()

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

二項式分佈。 返回 n 次獨立試驗在每次試驗的成功率為 p 時的成功次數

2.4、實值分佈

2.4.1、random.random()

random.random()

返回 0.0 <= X < 1.0 範圍內的隨機浮點數

2.4.2、random.uniform(a, b)

random.uniform(a, b)

返回一個隨機浮點數 N ,a <= N <= b。

import random
print(random.uniform(60, 100))

2.4.2.1、保留小數點後n位的方法

2.4.2.1.1、使用round()函式
num = 3.14159
# 這種方式是進行的四捨五入
rounded_num = round(num, 2)  # 輸出: 3.14
2.4.2.1.2、使用format()函式
num = 3.14159
# 這種方式是進行的四捨五入,注意:得到的是字串形式的浮點數
formatted_num = "{:.2f}".format(num)  # 輸出: '3.14'
2.4.2.1.3、使用decimal模組進行精確控制
from decimal import Decimal, ROUND_HALF_UP
 
num = Decimal('3.14159')
context = Decimal(1) / Decimal(10) ** 2
# 這種方式是進行的四捨五入
rounded_num = num.quantize(context, rounding=ROUND_HALF_UP)  # 輸出: Decimal('3.14')
2.4.2.1.4、使用f-string(Python 3.6+)
num = 3.14159
formatted_num = f"{num:.2f}"  # 輸出: '3.14'

三、與random相關的模組

3.1、secrets模組

secrets 模組用於生成高度加密的隨機數,適於管理密碼、賬戶驗證、安全憑據及機密資料。

3.1.1、secrets.choice()

secrets.choice(seq)

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

import secrets
# 假設我們有一個元素列表
fruits = ['apple', 'banana', 'orange', 'grape', 'watermelon']
# 使用 secrets.choice 來安全地選擇列表中的一個隨機元素
secure_random_element = secrets.choice(fruits)
print(secure_random_element)

3.1.2、secrets.randbelow()

secrets.randbelow(exclusive_upper_bound)

返回 [0, exclusive_upper_bound) 範圍內的隨機整數。

import secrets
# 生成一個安全的隨機整數,範圍在 0 <= N < 10之間
secure_int = secrets.randbelow(10)
print(f"安全隨機整數:{secure_int}")

3.1.3、secrets.randbits()

secrets.randbits(k)

返回一個具有k個隨機位的非負整數。如 secrets.randbits(3),表示取值為 0 <= N < 2^3,也就是 [0, 8) 之間的隨機整數。

3.1.4、secrets.token_bytes()

secrets.token_bytes([nbytes=None])

如果指定了 nbytes, secrets.token_bytes(nbytes) 會返回一個包含 nbytes 個隨機位元組的 bytes 物件。 如果未提供 nbytes 引數,它會返回一個合適用於安全令牌的預設長度的隨機位元組序列(通常為 32 個位元組)。

import secrets
# 生成預設長度(通常為32個位元組)的隨機位元組序列
random_token = secrets.token_bytes()
print(len(random_token))   # 輸出:32
print(random_token) # 輸出隨機位元組序列:b'<\xa1\xc2\xb9k\xf4\x9e$\xd9\x03\xd5H\xb8\x1e\xe2d\xe0\x82x\xe9)\x11\xe9\x04l\xda\x95\xe3\xf0C\x88\xba'
# 生成指定長度的隨機位元組序列
specified_length_token = secrets.token_bytes(16)
print(specified_length_token) # 輸出隨機位元組序列:b'\xa2C\x98H\xb2\xca5\n\xb69h{\xfa\xb3n\xc5'

3.1.5、secrets.token_hex()

secrets.token_hex([nbytes=None])

如果提供了 nbytes 引數,則會生成長度為 nbytes*2 的十六進位制字串(每個位元組轉換為兩個十六進位制字元)。 如果未提供 nbytes 引數,則會生成一個適用於安全令牌的預設長度的十六進位制字串(通常是 32 個字元)。

import secrets
# 生成預設長度(通常為32個字元)的隨機十六進位制字串
random_hex_token = secrets.token_hex()
print(random_hex_token)  # 53063bd5d38f7f032e146d769567254748dcbc34de37f716043a21d4b9ef0575
# 生成指定長度的隨機十六進位制字串
specified_length_hex_token = secrets.token_hex(16)
print(specified_length_hex_token)  # bcce543dee60747639895e2fcffcfaf8

3.1.6、secrets.token_urlsafe()

secrets.token_urlsafe([nbytes=None])

返回安全的 URL 隨機文字字串,包含 nbytes 個隨機位元組。文字用 Base64 編碼,平均來說,每個位元組對應 1.3 個結果字元。 未提供 nbytes 或為 None 時,它會生成一個適合於安全令牌的預設長度隨機 URL 安全字串。 這個函式生成的返回值是一個只包含 URL 安全字元(字母、數字、下劃線和短橫線)的字串。

import secrets
# 生成預設長度的隨機 URL 安全字串
random_urlsafe_token = secrets.token_urlsafe()
print(len(random_urlsafe_token))   # 輸出:43
print(random_urlsafe_token)        # 輸出:t6GrND8P32pL763R36VQIaB70jf8r_uruvSa0wgQYrY
# 生成指定長度的隨機 URL 安全字串
specified_length_urlsafe_token = secrets.token_urlsafe(16)
print(specified_length_urlsafe_token)  # 輸出:dUA2mPJbbWtB6rLj8hoC1g

3.1.7、secrets.compare_digest()

secrets.compare_digest(a, b)

用於比較兩個字串 a 和 b,並且在字串匹配時具有防止時間側通道攻擊的特性。在密碼學和安全相關的場景中,比較兩個敏感字串時,使用 secrets.compare_digest 要優於簡單的 == 運算子。 該函式返回一個布林值,如果 a 和 b 匹配,返回 True,否則返回 False。這種比較在比較時間上更加均勻,不易受到時間側通道攻擊的影響。在比較敏感資料時,尤其是在密碼驗證或令牌比對時使用這個函式,可以提高系統的安全性。

生成系統密碼示例

import string
import random
import secrets
 
def generate_password(min_length, max_repeat, max_class_repeat, *char_credits):
    characters = [string.digits, string.ascii_uppercase, '!@#$%^&*(){}<>,?`~+-=[]', string.ascii_lowercase]
    all_characters = ''.join(characters)
 
    while True:
        # 生成每個字元類別的字元
        password = [secrets.choice(char) for char, count in zip(characters, char_credits) for _ in range(count)]
        # 新增字元以滿足最小密碼長度要求
        remaining_len = min_length - sum(char_credits)
        password += [secrets.choice(all_characters) for _ in range(remaining_len)]
        random.shuffle(password)
        if is_valid_password(min_length, password, characters, max_repeat, max_class_repeat):
            return ''.join(password)
 
 
def is_valid_password(min_length, password, characters, max_repeat, max_class_repeat):
    # 判斷是否以=開頭
    if ''.join(password).startswith("="):
        return False
    
    # 檢查字元和類別是否連續重複超過了規定次數
    repeat_count = max(password.count(char) for char in set(password))
    if repeat_count > min_length // 4:
        return False
 
    for char_class in characters:
        char_class_positions = [idx for idx, char in enumerate(password) if char in char_class]
        for idx in char_class_positions:
            if idx < len(password) - max_class_repeat:
                if all(password[idx + i] in char_class for i in range(0, max_class_repeat + 1)):
                    return False
    # 判定同一個字元的索引
    char_repeat = [char for char in set(password) if password.count(char) > max_repeat]
    char_repeat_positions = {}
    for idx, char in enumerate(password):
        if char in char_repeat:
            if char not in char_repeat_positions:
                char_repeat_positions[char] = [idx]
            else:
                char_repeat_positions[char].append(idx)
 
    # 判定相同字元是否連續
    for _, index in char_repeat_positions.items():
        for idx in index:
            if idx < len(password) - max_repeat:
                if all(password[idx + i] == password[idx] for i in range(0, max_repeat + 1)):
                    return False
    return True
 
 
if __name__ == '__main__':
    # 設定密碼生成的引數
    min_length = 16
    char_credits = (3, 3, 3, 3)  # 數字、大寫字母、特殊字元、小寫字母
    # 同一個字元不能連續重複的數量
    max_repeat = 2
    # 同一類字元不能連續重複的數量
    max_class_repeat = 3
 
    # 生成和列印密碼
    for _ in range(100):
        generated_password = generate_password(min_length, max_repeat, max_class_repeat, *char_credits)
        print(generated_password)

生成密碼重置URL示例

import secrets
import string
 
# 生成安全令牌,用於密碼恢復應用程式
security_token = ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
# 構建臨時密保URL
temp_url = f"https://example.com/password-recovery?token={security_token}"
print("生成的臨時密保URL:", temp_url)  # 輸出:生成的臨時密保URL: https://example.com/password-recovery?token=90HOEUVFroaJIO5D

相關文章