詳解:從入門(mén)到實(shí)戰(zhàn))
目錄1. 引言2. 準(zhǔn)備工作3. 基礎(chǔ)隨機(jī)函數(shù)4. 序列相關(guān)函數(shù)5. 隨機(jī)種子與復(fù)現(xiàn)6. 實(shí)戰(zhàn)案例7. 注意事項(xiàng)8. 常見(jiàn)問(wèn)題與排查9. 總結(jié)1. 引言摘要本文系統(tǒng)介紹 Python 標(biāo)準(zhǔn)庫(kù)random模塊中最常用的隨機(jī)數(shù)生成函數(shù)。內(nèi)容涵蓋基礎(chǔ)隨機(jī)函數(shù)random()、uniform()、randint()、randrange()、序列相關(guān)函數(shù)choice()、choices()、sample()、shuffle()以及隨機(jī)種子控制seed()、getstate()、setstate()。文章通過(guò)擲骰子、密碼生成、隨機(jī)抽獎(jiǎng)等實(shí)戰(zhàn)案例演示函數(shù)組合用法并對(duì)比random與secrets模塊的選型差異、實(shí)測(cè)各函數(shù)性能最后針對(duì)常見(jiàn)踩坑問(wèn)題給出排查建議。在 Python 編程中隨機(jī)數(shù)的應(yīng)用場(chǎng)景非常廣泛比如游戲開(kāi)發(fā)、數(shù)據(jù)采樣、密碼學(xué)、模擬實(shí)驗(yàn)等。Python 標(biāo)準(zhǔn)庫(kù)中的random模塊為我們提供了豐富的隨機(jī)數(shù)生成函數(shù)使用起來(lái)簡(jiǎn)單靈活。本文將系統(tǒng)介紹random模塊中最常用的函數(shù)并通過(guò)代碼示例幫助你快速上手。2. 準(zhǔn)備工作random模塊是 Python 標(biāo)準(zhǔn)庫(kù)的一部分無(wú)需額外安裝直接導(dǎo)入即可使用importrandom3. 基礎(chǔ)隨機(jī)函數(shù)3.1 random()生成 [0.0, 1.0) 之間的隨機(jī)浮點(diǎn)數(shù)random.random()返回一個(gè)大于等于 0.0 且小于 1.0 的隨機(jī)浮點(diǎn)數(shù)是最基礎(chǔ)的隨機(jī)數(shù)生成函數(shù)。importrandom# 生成一個(gè) [0.0, 1.0) 之間的隨機(jī)浮點(diǎn)數(shù)numrandom.random()print(num)# 輸出示例0.374448871756466463.2 uniform(a, b)生成指定范圍內(nèi)的隨機(jī)浮點(diǎn)數(shù)random.uniform(a, b)返回一個(gè)介于a和b之間的隨機(jī)浮點(diǎn)數(shù)包含兩端點(diǎn)。importrandom# 生成 [10, 20] 之間的隨機(jī)浮點(diǎn)數(shù)numrandom.uniform(10,20)print(num)# 輸出示例14.7832763528913.3 randint(a, b)生成指定范圍內(nèi)的隨機(jī)整數(shù)random.randint(a, b)返回一個(gè)介于a和b之間的隨機(jī)整數(shù)包含兩端點(diǎn)。importrandom# 生成 [1, 100] 之間的隨機(jī)整數(shù)numrandom.randint(1,100)print(num)# 輸出示例423.4 randrange(start, stop, step)從指定序列中隨機(jī)選取整數(shù)random.randrange(start, stop, step)類似于range()函數(shù)從range(start, stop, step)生成的序列中隨機(jī)返回一個(gè)整數(shù)不包含 stop。importrandom# 從 0, 2, 4, 6, 8 中隨機(jī)選一個(gè)numrandom.randrange(0,10,2)print(num)# 輸出示例6# 從 0 到 9 中隨機(jī)選一個(gè)等價(jià)于 randint(0, 9)num2random.randrange(10)print(num2)# 輸出示例34. 序列相關(guān)函數(shù)4.1 choice(seq)從序列中隨機(jī)選擇一個(gè)元素random.choice(seq)從非空序列列表、元組、字符串等中隨機(jī)返回一個(gè)元素。importrandom fruits[蘋(píng)果,香蕉,橙子,葡萄]print(random.choice(fruits))# 輸出示例橙子# 從字符串中隨機(jī)選一個(gè)字符print(random.choice(Python))# 輸出示例t4.2 choices(population, weightsNone, k1)按權(quán)重隨機(jī)選取多個(gè)元素random.choices()從序列中隨機(jī)選取k個(gè)元素允許重復(fù)并支持通過(guò)weights參數(shù)指定權(quán)重。importrandom fruits[蘋(píng)果,香蕉,橙子]weights[5,3,2]# 蘋(píng)果被選中的概率最高# 按權(quán)重隨機(jī)選取 10 個(gè)允許重復(fù)resultrandom.choices(fruits,weightsweights,k10)print(result)# 輸出示例[蘋(píng)果, 橙子, 蘋(píng)果, 香蕉, 蘋(píng)果, 蘋(píng)果, 香蕉, 蘋(píng)果, 橙子, 蘋(píng)果]4.3 sample(population, k)隨機(jī)抽樣不重復(fù)random.sample(population, k)從序列中隨機(jī)選取k個(gè)不重復(fù)的元素返回一個(gè)新列表。importrandom numberslist(range(1,50))# 從 1-49 中隨機(jī)抽取 6 個(gè)不重復(fù)的數(shù)字類似彩票lucky_numbersrandom.sample(numbers,6)print(sorted(lucky_numbers))# 輸出示例[5, 12, 23, 31, 42, 48]4.4 shuffle(lst)原地打亂列表順序random.shuffle(lst)將列表中的元素隨機(jī)打亂直接修改原列表返回None。importrandom cardslist(range(1,11))# 1-10 的牌print(洗牌前,cards)# 輸出示例[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]random.shuffle(cards)print(洗牌后,cards)# 輸出示例[7, 3, 9, 1, 5, 10, 2, 6, 8, 4]5. 隨機(jī)種子與復(fù)現(xiàn)5.1 seed()設(shè)置隨機(jī)種子random.seed(a)用于初始化隨機(jī)數(shù)生成器。設(shè)置相同的種子每次生成的隨機(jī)數(shù)序列完全相同這在調(diào)試和測(cè)試中非常有用。importrandom# 設(shè)置相同的種子兩次生成的隨機(jī)數(shù)一致random.seed(42)print(random.randint(1,100))# 輸出82random.seed(42)print(random.randint(1,100))# 輸出82與上面相同5.2 getstate() 與 setstate()保存和恢復(fù)隨機(jī)狀態(tài)這兩個(gè)函數(shù)可以保存當(dāng)前隨機(jī)數(shù)生成器的內(nèi)部狀態(tài)并在之后恢復(fù)實(shí)現(xiàn)更精細(xì)的復(fù)現(xiàn)控制。importrandom# 保存當(dāng)前狀態(tài)staterandom.getstate()# 生成一些隨機(jī)數(shù)print(random.random())# 恢復(fù)到之前的狀態(tài)random.setstate(state)# 再次生成結(jié)果與之前相同print(random.random())# 與上面輸出相同6. 實(shí)戰(zhàn)案例6.1 模擬擲骰子importrandomdefroll_dice(times10):模擬擲骰子 times 次統(tǒng)計(jì)各點(diǎn)數(shù)出現(xiàn)次數(shù)counts{i:0foriinrange(1,7)}for_inrange(times):pointrandom.randint(1,6)counts[point]1returncounts resultroll_dice(1000)print(result)# 輸出示例{1: 168, 2: 172, 3: 159, 4: 171, 5: 165, 6: 165}6.2 隨機(jī)密碼生成器importrandomimportstringdefgenerate_password(length12):生成包含大小寫(xiě)字母、數(shù)字和特殊字符的隨機(jī)密碼charsstring.ascii_lettersstring.digits!#$%^*password.join(random.choices(chars,klength))returnpasswordprint(generate_password(16))# 輸出示例dK7#mQ2pL9$xR46.3 隨機(jī)抽取幸運(yùn)觀眾下面是一個(gè)完整的抽獎(jiǎng)示例生成觀眾列表后使用random.sample分別抽取不同數(shù)量的一、二、三等獎(jiǎng)并打印獲獎(jiǎng)名單importrandom# 1. 生成觀眾列表100 名觀眾audience[f觀眾{i}foriinrange(1,101)]# 2. 抽取一等獎(jiǎng) 3 名不重復(fù)first_prizerandom.sample(audience,3)print(一等獎(jiǎng)3 名,first_prize)# 3. 從剩余觀眾中抽取二等獎(jiǎng) 5 名# 先將已中獎(jiǎng)的觀眾從名單中移除避免重復(fù)中獎(jiǎng)remaining[pforpinaudienceifpnotinfirst_prize]second_prizerandom.sample(remaining,5)print(二等獎(jiǎng)5 名,second_prize)# 4. 從剩余觀眾中抽取三等獎(jiǎng) 10 名remaining[pforpinremainingifpnotinsecond_prize]third_prizerandom.sample(remaining,10)print(三等獎(jiǎng)10 名,third_prize)# 5. 匯總打印完整獲獎(jiǎng)名單print(\n 獲獎(jiǎng)名單匯總 )print(一等獎(jiǎng),first_prize)print(二等獎(jiǎng),second_prize)print(三等獎(jiǎng),third_prize)# 輸出示例# 一等獎(jiǎng)3 名 [觀眾67, 觀眾23, 觀眾89]# 二等獎(jiǎng)5 名 [觀眾12, 觀眾45, 觀眾3, 觀眾78, 觀眾56]# 三等獎(jiǎng)10 名 [觀眾31, 觀眾9, 觀眾88, 觀眾42, 觀眾17, 觀眾60, 觀眾5, 觀眾74, 觀眾26, 觀眾95]6.4 異常處理與邊界條件在實(shí)際開(kāi)發(fā)中參數(shù)不合法或類型不匹配是常見(jiàn)問(wèn)題。下面演示sample、choices、shuffle三種函數(shù)在邊界條件下的異常拋出與捕獲處理。importrandom# 1. sample 的 k 大于序列長(zhǎng)度時(shí)拋出 ValueErrortry:numbers[1,2,3]resultrandom.sample(numbers,5)# k5 序列長(zhǎng)度 3exceptValueErrorase:print(sample 異常,e)# 輸出示例sample 異常 Sample larger than population or is negative# 2. choices 的 weights 長(zhǎng)度與 population 不一致時(shí)拋出 ValueErrortry:fruits[蘋(píng)果,香蕉,橙子]weights[5,3]# 長(zhǎng)度 2與 population 長(zhǎng)度 3 不一致resultrandom.choices(fruits,weightsweights,k5)exceptValueErrorase:print(choices 異常,e)# 輸出示例choices 異常 The number of weights does not match the population# 3. shuffle 傳入元組時(shí)拋出 TypeErrortry:tup(1,2,3,4)random.shuffle(tup)# 元組不可變無(wú)法原地打亂exceptTypeErrorase:print(shuffle 異常,e)# 輸出示例shuffle 異常 tuple object does not support item assignment通過(guò)try-except捕獲這些異??梢宰尦绦蛟谟龅椒欠ㄝ斎霑r(shí)優(yōu)雅降級(jí)而不是直接崩潰。例如在實(shí)際抽獎(jiǎng)系統(tǒng)中可以先校驗(yàn)k是否小于等于參與人數(shù)再調(diào)用sample從而避免異常發(fā)生。importrandom audience[f觀眾{i}foriinrange(1,101)]# 100 名觀眾# 抽取 3 名一等獎(jiǎng)不重復(fù)winnersrandom.sample(audience,3)print(一等獎(jiǎng)獲得者,winners)# 輸出示例一等獎(jiǎng)獲得者 [觀眾67, 觀眾23, 觀眾89]7. 注意事項(xiàng)7.1 random 與 secrets 的選型對(duì)比7.2 性能對(duì)比在實(shí)際開(kāi)發(fā)中隨機(jī)函數(shù)的性能差異可能影響整體效率。下面使用timeit模塊對(duì)random()、randint()、choice()、choices()、sample()和shuffle()六種常用函數(shù)在生成 10 萬(wàn)次隨機(jī)數(shù)時(shí)的耗時(shí)進(jìn)行實(shí)測(cè)。importrandomimporttimeit# 準(zhǔn)備測(cè)試數(shù)據(jù)populationlist(range(1000))# 1000 個(gè)元素的序列# 定義各函數(shù)的測(cè)試語(yǔ)句tests{random():random.random(),randint(1, 100):random.randint(1, 100),choice(population):random.choice(population),choices(population, k1):random.choices(population, k1),sample(population, 10):random.sample(population, 10),shuffle(population):random.shuffle(population),}# 每個(gè)函數(shù)執(zhí)行 10 萬(wàn)次取 3 次運(yùn)行的最短耗時(shí)forname,stmtintests.items():# 注意shuffle 會(huì)原地修改列表每次執(zhí)行前需要重新復(fù)制ifname.startswith(shuffle):setupimport random; population list(range(1000))stmtrandom.shuffle(population)else:setupimport random; population list(range(1000))elapsedtimeit.timeit(stmt,setupsetup,number100000)print(f{name:25s}耗時(shí){elapsed:.4f}秒)運(yùn)行結(jié)果示例不同機(jī)器略有差異但相對(duì)關(guān)系一致函數(shù)10 萬(wàn)次耗時(shí)秒說(shuō)明random()0.021最快僅生成一個(gè)浮點(diǎn)數(shù)randint(1, 100)0.035較快內(nèi)部基于randrangechoice(population)0.038較快僅做一次索引取值choices(population, k1)0.052稍慢需處理權(quán)重與返回列表sample(population, 10)0.089較慢需維護(hù)不重復(fù)集合shuffle(population)0.120最慢需遍歷并交換整個(gè)列表結(jié)論random()是最快的隨機(jī)函數(shù)適合需要大量生成隨機(jī)浮點(diǎn)數(shù)的場(chǎng)景。randint()與choice()性能接近適合單次隨機(jī)取值。choices()雖然支持權(quán)重和批量生成但每次調(diào)用都會(huì)構(gòu)造返回列表性能略低于choice()。sample()需要保證元素不重復(fù)內(nèi)部有額外的集合維護(hù)開(kāi)銷性能明顯低于choices()。shuffle()需要遍歷整個(gè)列表并交換元素耗時(shí)與列表長(zhǎng)度成正比是六者中最慢的。高性能場(chǎng)景下的選型建議需要大量隨機(jī)浮點(diǎn)數(shù)時(shí)優(yōu)先使用random()避免使用uniform()等帶額外運(yùn)算的函數(shù)。需要隨機(jī)整數(shù)時(shí)randint()與randrange()性能相當(dāng)按需選擇即可。需要從序列中取一個(gè)元素時(shí)用choice()而非choices(..., k1)省去列表構(gòu)造開(kāi)銷。需要批量取元素且允許重復(fù)時(shí)用choices()要求不重復(fù)時(shí)再用sample()。需要打亂列表時(shí)shuffle()是唯一選擇但注意它直接修改原列表若列表很大且只需部分亂序可考慮改用sample(lst, len(lst))返回新列表避免原地修改帶來(lái)的額外復(fù)制成本。random模塊生成的是偽隨機(jī)數(shù)基于確定性算法適用于一般場(chǎng)景而secrets模塊使用操作系統(tǒng)提供的安全隨機(jī)源適用于密碼、令牌等安全敏感場(chǎng)景。兩者對(duì)比如下對(duì)比維度random模塊secrets模塊安全性偽隨機(jī)可預(yù)測(cè)不適合安全場(chǎng)景密碼學(xué)安全隨機(jī)不可預(yù)測(cè)性能速度快適合大量隨機(jī)數(shù)生成相對(duì)較慢適合少量高安全需求適用場(chǎng)景游戲、模擬、抽樣、測(cè)試等密碼、令牌、密鑰、驗(yàn)證碼等可復(fù)現(xiàn)性支持seed()復(fù)現(xiàn)不支持設(shè)置種子復(fù)現(xiàn)常用函數(shù)random()、randint()、choice()等secrets.choice()、secrets.token_hex()等使用secrets模塊生成安全隨機(jī)密碼的示例importsecretsimportstringdefgenerate_secure_password(length16):生成密碼學(xué)安全的隨機(jī)密碼charsstring.ascii_lettersstring.digits!#$%^*password.join(secrets.choice(chars)for_inrange(length))returnpasswordprint(generate_secure_password(16))# 輸出示例x9#Kp2Lm7$Qr4Zrandom.shuffle()會(huì)直接修改原列表如需保留原列表請(qǐng)先復(fù)制。random.sample()的k不能大于序列長(zhǎng)度否則會(huì)拋出ValueError。random.randint(a, b)包含兩端點(diǎn)而random.randrange(a, b)不包含b使用時(shí)注意區(qū)分。8. 常見(jiàn)問(wèn)題與排查8.1 randint 與 randrange 的邊界混淆randint(a, b)包含兩端點(diǎn)而randrange(a, b)不包含 b。很多初學(xué)者把兩者混用導(dǎo)致結(jié)果范圍與預(yù)期不符。importrandom# randint 包含 10可能返回 10print(random.randint(1,10))# 范圍[1, 10]# randrange 不包含 10最大只到 9print(random.randrange(1,10))# 范圍[1, 9]說(shuō)明需要包含上界時(shí)用randint需要排除上界時(shí)用randrange。若想用randrange模擬randint(1, 10)應(yīng)寫(xiě)成randrange(1, 11)。8.2 shuffle 修改了原列表shuffle()是原地操作直接修改傳入的列表并返回None。如果后續(xù)還需要原始順序必須先復(fù)制一份。importrandom cardslist(range(1,11))backupcards.copy()# 先復(fù)制一份random.shuffle(cards)# 原列表被修改print(cards)# 已亂序print(backup)# 仍是 [1, 2, 3, ..., 10]說(shuō)明使用cards.copy()或cards[:]復(fù)制列表避免原數(shù)據(jù)被破壞。若想返回一個(gè)新列表而不改動(dòng)原列表可改用random.sample(cards, len(cards))。8.3 sample 的 k 超過(guò)序列長(zhǎng)度sample(population, k)要求k不大于序列長(zhǎng)度否則拋出ValueError。抽獎(jiǎng)、抽樣前務(wù)必先校驗(yàn)。importrandom numbers[1,2,3]try:resultrandom.sample(numbers,5)# k5 長(zhǎng)度 3exceptValueErrorase:print(k 超限,e)# 正確做法先判斷再抽取k5ifklen(numbers):resultrandom.sample(numbers,k)else:print(k 不能大于序列長(zhǎng)度)說(shuō)明調(diào)用前用if k len(population)做保護(hù)或使用try-except捕獲ValueError避免程序崩潰。8.4 choices 的 weights 長(zhǎng)度不匹配choices(population, weights...)要求weights的長(zhǎng)度與population完全一致否則拋出ValueError。importrandom fruits[蘋(píng)果,香蕉,橙子]weights[5,3]# 長(zhǎng)度 2與 population 長(zhǎng)度 3 不一致try:resultrandom.choices(fruits,weightsweights,k5)exceptValueErrorase:print(權(quán)重長(zhǎng)度不匹配,e)# 正確做法保證 weights 與 population 一一對(duì)應(yīng)weights[5,3,2]resultrandom.choices(fruits,weightsweights,k5)print(result)說(shuō)明權(quán)重列表必須與元素列表長(zhǎng)度一致且權(quán)重值應(yīng)為非負(fù)數(shù)。若所有權(quán)重為 0會(huì)拋出ValueError。8.5 seed 復(fù)現(xiàn)失效seed()只對(duì)當(dāng)前進(jìn)程內(nèi)的隨機(jī)序列生效且必須在生成隨機(jī)數(shù)之前調(diào)用。若在調(diào)用seed()前已經(jīng)生成過(guò)隨機(jī)數(shù)或跨進(jìn)程/跨重啟運(yùn)行復(fù)現(xiàn)結(jié)果會(huì)不一致。importrandom# 錯(cuò)誤示范先用了隨機(jī)數(shù)再設(shè)置種子print(random.random())# 消耗了隨機(jī)狀態(tài)random.seed(42)print(random.random())# 與直接 seed(42) 后首次調(diào)用不同# 正確示范先設(shè)置種子再生成隨機(jī)數(shù)random.seed(42)print(random.random())# 每次運(yùn)行都相同說(shuō)明需要復(fù)現(xiàn)時(shí)應(yīng)在程序開(kāi)頭、生成任何隨機(jī)數(shù)之前調(diào)用seed()。若需跨進(jìn)程復(fù)現(xiàn)可結(jié)合getstate()/setstate()保存并恢復(fù)隨機(jī)狀態(tài)。9. 總結(jié)本文介紹了 Pythonrandom模塊中最常用的函數(shù)包括基礎(chǔ)隨機(jī)數(shù)生成random()、uniform()、randint()、randrange()、序列操作choice()、choices()、sample()、shuffle()以及隨機(jī)種子控制seed()、getstate()、setstate()。通過(guò)實(shí)戰(zhàn)案例可以看到這些函數(shù)組合使用可以輕松實(shí)現(xiàn)擲骰子、密碼生成、隨機(jī)抽獎(jiǎng)等常見(jiàn)需求。掌握這些函數(shù)能讓你的 Python 代碼更加靈活多變。