欢迎光临
我们一直在努力

24.Python random 模块完全指南

文章目录

    • 第一部分:random 模块是什么?
      • 1.1 为什么需要随机数?
      • 1.2 伪随机数:计算机的随机数是"真随机"吗?
    • 第二部分:生成随机数
      • 2.1 random()——最基础:生成 [0.0, 1.0) 的浮点数
      • 2.2 uniform(a, b)——指定范围的浮点数
      • 2.3 randint(a, b)——随机整数(含两端)
      • 2.4 randrange(start, stop, step)——更灵活的随机整数
      • 2.5 getrandbits(k)——生成 k 位随机二进制整数
    • 第三部分:序列相关操作
      • 3.1 choice(seq)——从序列中随机选一个
      • 3.2 choices(population, weights, k)——有权重的随机抽取(可重复)
      • 3.3 sample(population, k)——不重复抽取(无放回抽样)
      • 3.4 shuffle(seq)——原地打乱序列
      • 3.5 choice vs choices vs sample vs shuffle 对比总结
    • 第四部分:随机种子(seed)
      • 4.1 什么是种子?
      • 4.2 种子的实际用途
      • 4.3 保存和恢复随机状态
    • 第五部分:概率分布
      • 5.1 为什么需要不同的概率分布?
      • 5.2 gauss / normalvariate——正态分布
      • 5.3 expovariate(lambd)——指数分布
      • 5.4 triangular(low, high, mode)——三角分布
      • 5.5 betavariate(alpha, beta)——Beta 分布
      • 5.6 其他分布(一览)
    • 第六部分:实战案例
      • 6.1 案例一:密码生成器
      • 6.2 案例二:模拟抽奖系统
      • 6.3 案例三:蒙特卡洛估算 π
      • 6.4 案例四:随机游走(股价模拟)
      • 6.5 案例五:简易文字游戏——猜数字
      • 6.6 案例六:随机名字/词语生成器
    • 第七部分:安全随机数
      • 7.1 普通 random 的安全性问题
      • 7.2 secrets 模块——密码学安全随机数
      • 7.3 random vs secrets 场景对比
    • 第八部分:常见陷阱与注意事项
      • 8.1 陷阱1:shuffle 不返回新列表
      • 8.2 陷阱2:randint 和 randrange 的端点问题
      • 8.3 陷阱3:多线程下的随机数不安全
      • 8.4 陷阱4:种子相同但调用顺序不同
      • 8.5 陷阱5:用 random 生成安全令牌(危险!)
    • 第九部分:Random 类——创建独立的随机生成器
      • 9.1 为什么要用 Random 类?
      • 9.2 实际应用:游戏中多个独立随机流
    • 第十部分:完整速查表
      • 10.1 函数分类速查
      • 10.2 选择哪个函数?决策树
    • 总结

24.Python random 模块完全指南

本文档面向零基础新手,目标是让你真正理解:

  • random 模块是什么,能做什么
  • 生成随机整数、浮点数的各种方法
  • 对列表进行随机抽取、打乱顺序
  • 随机种子(seed)是什么,为什么要用它
  • 各种概率分布(正态分布、指数分布等)
  • 如何用随机数解决实际问题(抽奖、密码、模拟、游戏)
  • 安全随机数 vs 普通随机数的区别

配有大量可运行示例,全部从最基础讲起。


第一部分:random 模块是什么?

1.1 为什么需要随机数?

生活中随机无处不在:

  • 抽奖时随机抽取获奖者
  • 游戏里随机生成地图、掉落装备
  • 密码学中生成随机密钥
  • 科学模拟中用随机采样来估算结果
  • 机器学习中随机划分训练集和测试集

Python 内置的 random 模块就是为了满足这些需求而生的。

import random # 只需要这一行就能使用所有功能

# 快速体验
print(random.random()) # 0.0 到 1.0 之间的随机浮点数
print(random.randint(1, 100)) # 1 到 100 之间的随机整数
print(random.choice(["石头", "剪刀", "布"])) # 随机选一个


1.2 伪随机数:计算机的随机数是"真随机"吗?

重要概念: 计算机产生的随机数其实是"伪随机数(Pseudo-random numbers)"。

真随机:
来自真实物理现象(如放射性衰变、热噪声)
完全不可预测,不能重现

伪随机:
用数学算法产生的"看起来随机"的数字序列
从同一个"种子"出发,每次产生的序列完全相同
这正是我们在科学实验和调试中需要的——可重现!

import random

# 设置种子后,每次运行结果完全相同
random.seed(42)
print(random.random()) # 0.6394267984578837(永远是这个)
print(random.random()) # 0.025010755222666936(永远是这个)
print(random.random()) # 0.27502931836911926(永远是这个)

# 不同种子,产生不同序列
random.seed(100)
print(random.random()) # 完全不同的数字

Python random 模块的底层算法是 Mersenne Twister(梅森旋转算法), 周期长达 2^19937-1,对于日常使用完全够用。


第二部分:生成随机数

2.1 random()——最基础:生成 [0.0, 1.0) 的浮点数

import random

# random() 返回 0.0(含)到 1.0(不含)之间的浮点数
for i in range(5):
print(f"第{i+1}次:{random.random():.6f}")

# 输出示例:
# 第1次:0.374540
# 第2次:0.950714
# 第3次:0.731994
# 第4次:0.598658
# 第5次:0.156019

用 random() 生成任意范围的浮点数:

# 生成 [a, b) 范围内的浮点数,公式:a + (b-a) * random()
a, b = 10, 20
value = a + (b a) * random.random()
print(f"{a}{b} 之间:{value:.4f}")

# 生成 0 到 100 的百分比
percent = random.random() * 100
print(f"随机百分比:{percent:.2f}%")


2.2 uniform(a, b)——指定范围的浮点数

uniform(a, b) 是 a + (b-a) * random() 的简写版,更直观:

import random

# 生成 5.0 到 10.0 之间的浮点数
price = random.uniform(5.0, 10.0)
print(f"随机价格:¥{price:.2f}") # 如:¥7.83

# 注意:a 可以大于 b
print(random.uniform(10, 1)) # 也可以,范围是 [1, 10]

# 实际应用:模拟测量误差
true_value = 100.0
measured = true_value + random.uniform(0.5, 0.5)
print(f"真实值:{true_value},测量值:{measured:.4f}")


2.3 randint(a, b)——随机整数(含两端)

randint(a, b) 返回 a 到 b(含 a 和 b)之间的随机整数:

import random

# 模拟掷骰子(1到6)
dice = random.randint(1, 6)
print(f"掷出了:{dice} 点")

# 验证两端都会出现
results = [random.randint(1, 6) for _ in range(10000)]
for face in range(1, 7):
count = results.count(face)
print(f" {face}点:{count}次({count/100:.1f}%)")
# 每个面出现概率约 16.7%

容易混淆的地方:

# randint(a, b):包含 b!
print(random.randint(1, 10)) # 可能输出 1 到 10,10 也会出现

# 和 range(1, 10) 不同!range(1, 10) 不包含 10
# 等价于:random.randrange(1, 11)


2.4 randrange(start, stop, step)——更灵活的随机整数

randrange 和 range() 参数一模一样,但随机返回其中一个值:

import random

# randrange(stop):0 到 stop-1
print(random.randrange(10)) # 0 到 9

# randrange(start, stop):start 到 stop-1(不含 stop)
print(random.randrange(1, 10)) # 1 到 9(不含 10!)

# randrange(start, stop, step):按步长选
# 等价于 random.choice(range(0, 20, 2))
even = random.randrange(0, 20, 2)
print(f"随机偶数(0-18):{even}") # 0,2,4,…,18 中的一个

odd = random.randrange(1, 20, 2)
print(f"随机奇数(1-19):{odd}") # 1,3,5,…,19 中的一个

# 实际应用:随机选择特定倍数
multiple_of_5 = random.randrange(5, 101, 5)
print(f"5的倍数(5-100):{multiple_of_5}")

randint vs randrange 对比:

# randint(1, 10):包含 10
# randrange(1, 10):不包含 10,即 1-9
# randrange(1, 11):等价于 randint(1, 10)

# 记忆技巧:randrange 和 range 一样,不含右端点


2.5 getrandbits(k)——生成 k 位随机二进制整数

import random

# 生成指定位数的随机整数
bit4 = random.getrandbits(4) # 0 到 15(4位二进制)
bit8 = random.getrandbits(8) # 0 到 255(8位)
bit32 = random.getrandbits(32) # 0 到 2^32-1

print(f"4位:{bit4}(二进制:{bin(bit4)})")
print(f"8位:{bit8}(二进制:{bin(bit8)})")
print(f"32位:{bit32}")

# 实际应用:生成随机的 ID
token = random.getrandbits(128)
print(f"128位随机ID:{token}")
print(f"十六进制:{hex(token)}")


第三部分:序列相关操作

3.1 choice(seq)——从序列中随机选一个

choice 从非空序列(列表、元组、字符串)中随机选一个元素:

import random

# 从列表选
fruits = ["苹果", "香蕉", "橙子", "葡萄", "西瓜"]
print(f"今天吃:{random.choice(fruits)}")

# 从元组选
directions = ("上", "下", "左", "右")
move = random.choice(directions)
print(f"向{move}走")

# 从字符串选(选一个字符)
chars = "ABCDEFG"
char = random.choice(chars)
print(f"随机字母:{char}")

# 实用:模拟石头剪刀布
def computer_move():
return random.choice(["石头", "剪刀", "布"])

for round_ in range(3):
print(f"第{round_+1}局:电脑出了{computer_move()}")

注意:序列不能为空,否则报错:

try:
random.choice([]) # IndexError: Cannot choose from an empty sequence
except IndexError as e:
print(f"错误:{e}")


3.2 choices(population, weights, k)——有权重的随机抽取(可重复)

choices 支持权重,且允许重复抽取(放回抽样):

import random

# 基础用法:从列表中抽 k 个(可重复)
colors = ["红", "绿", "蓝"]
result = random.choices(colors, k=5)
print(f"抽到:{result}") # ['红', '蓝', '红', '红', '绿']

# 设置权重(红色概率更高)
# weights 和列表一一对应,不需要相加等于1
weighted = random.choices(
population=["红", "绿", "蓝"],
weights=[5, 3, 2], # 红:绿:蓝 = 5:3:2
k=100
)
print(f"红色出现:{weighted.count('红')}次(期望约50次)")
print(f"绿色出现:{weighted.count('绿')}次(期望约30次)")
print(f"蓝色出现:{weighted.count('蓝')}次(期望约20次)")

# cum_weights:累积权重(效果相同,只是另一种写法)
# weights=[5,3,2] 等价于 cum_weights=[5,8,10]
result2 = random.choices(
["一等奖", "二等奖", "三等奖", "谢谢参与"],
cum_weights=[1, 5, 15, 100], # 累积:1%, 5%, 15%, 100%
k=1
)[0]
print(f"抽奖结果:{result2}")

模拟一个加权骰子:

import random
from collections import Counter

# 作弊骰子:6点概率是其他面的3倍
biased_dice = random.choices(
[1, 2, 3, 4, 5, 6],
weights=[1, 1, 1, 1, 1, 3], # 6的权重是3,其他是1
k=10000
)
counts = Counter(biased_dice)
print("作弊骰子统计:")
for face in range(1, 7):
pct = counts[face] / 100
bar = "█" * (counts[face] // 100)
print(f" {face}点:{counts[face]:5d}次({pct:.1f}%){bar}")


3.3 sample(population, k)——不重复抽取(无放回抽样)

sample 从序列中随机选 k 个不重复的元素:

import random

# 从 1-100 中不重复地选 6 个数(模拟彩票)
lottery = random.sample(range(1, 101), 6)
lottery.sort()
print(f"本期开奖号码:{lottery}")

# 和 choices 的区别:sample 不重复!
pool = [1, 2, 3, 4, 5]
print(f"sample(不重复):{random.sample(pool, 3)}")
print(f"choices(可重复):{random.choices(pool, k=3)}")

# 从字符串中取样(不重复字符)
word = "PYTHON"
letters = random.sample(word, 4)
print(f"从{word}随机选4个字母:{''.join(letters)}")

# k 不能超过序列长度!
try:
random.sample([1, 2, 3], 5) # ValueError
except ValueError as e:
print(f"错误:{e}")

实际应用——随机分组:

import random

students = ["张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十"]

# 随机打乱顺序后分组
shuffled = random.sample(students, len(students)) # 等同于洗牌

group_size = 2
groups = [shuffled[i:i+group_size] for i in range(0, len(shuffled), group_size)]

print("随机分组结果:")
for i, group in enumerate(groups, 1):
print(f" 第{i}组:{'、'.join(group)}")


3.4 shuffle(seq)——原地打乱序列

shuffle 直接修改原列表,没有返回值:

import random

# 基础用法
cards = list(range(1, 11)) # [1, 2, 3, …, 10]
print(f"洗牌前:{cards}")
random.shuffle(cards)
print(f"洗牌后:{cards}")

# 注意:shuffle 修改原列表,返回 None!
deck = ["A", "2", "3", "4", "5"]
result = random.shuffle(deck)
print(f"deck:{deck}") # 已被打乱
print(f"result:{result}") # None!!!

# 如果需要保留原列表,先复制
original = [1, 2, 3, 4, 5]
shuffled = original.copy()
random.shuffle(shuffled)
print(f"原列表:{original}") # 不变
print(f"打乱后:{shuffled}") # 打乱

# 实际应用:发牌
def create_deck():
suits = ["♠", "♥", "♦", "♣"]
values = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
return [f"{v}{s}" for s in suits for v in values]

deck = create_deck()
random.shuffle(deck)

players = 4
hands = 5 # 每人5张

print("发牌结果:")
for i in range(players):
hand = deck[i*hands : (i+1)*hands]
print(f" 玩家{i+1}{' '.join(hand)}")


3.5 choice vs choices vs sample vs shuffle 对比总结

┌──────────┬──────────────────────────────┬──────────┬──────────────────────────┐
│ 方法 │ 用途 │ 重复 │ 返回值 │
├──────────┼──────────────────────────────┼──────────┼──────────────────────────┤
│ choice │ 选 1 个 │ – │ 单个元素 │
│ choices │ 选 k 个(可加权) │ 允许重复 │ 新列表 │
│ sample │ 选 k 个(不重复) │ 不重复 │ 新列表 │
│ shuffle │ 打乱整个列表 │ – │ None(原地修改!) │
└──────────┴──────────────────────────────┴──────────┴──────────────────────────┘


第四部分:随机种子(seed)

4.1 什么是种子?

随机数生成器就像一台机器:
– 种子(seed)= 机器的初始状态
– 相同的种子 → 相同的初始状态 → 产生完全相同的随机序列
– 不设种子 → Python 用当前时间戳作为种子 → 每次运行结果不同

import random

# ===== 不设种子:每次运行结果不同 =====
print("不设种子:")
print(random.randint(1, 100)) # 每次运行都不同
print(random.randint(1, 100))

print()

# ===== 设置种子:每次运行结果完全相同 =====
print("种子=42:")
random.seed(42)
print(random.randint(1, 100)) # 永远是 52
print(random.randint(1, 100)) # 永远是 93
print(random.randint(1, 100)) # 永远是 15

print()

# 再次设置相同种子,序列重置
random.seed(42)
print("重置种子=42:")
print(random.randint(1, 100)) # 还是 52(和前面一样)
print(random.randint(1, 100)) # 还是 93


4.2 种子的实际用途

import random

# 用途1:让实验结果可重现(科学研究、机器学习调试)
def train_model(seed=None):
if seed is not None:
random.seed(seed)
data = list(range(100))
random.shuffle(data)
train = data[:80]
test = data[80:]
print(f"训练集前5个:{train[:5]}")
print(f"测试集前5个:{test[:5]}")

print("第1次运行(seed=2024):")
train_model(seed=2024)
print()
print("第2次运行(seed=2024):")
train_model(seed=2024) # 完全相同!
print()
print("第3次运行(不设种子):")
train_model() # 每次不同

# 用途2:游戏地图生成——相同种子生成相同地图
def generate_map(seed, size=5):
random.seed(seed)
terrain = []
types = ["🌲", "🏔", "🌊", "🌾", "🏜"]
for _ in range(size):
row = [random.choice(types) for _ in range(size)]
terrain.append(row)
return terrain

print("\\n地图(种子=100):")
for row in generate_map(100):
print(" ".join(row))

print("\\n地图(种子=100,再次生成):")
for row in generate_map(100):
print(" ".join(row)) # 完全相同的地图!


4.3 保存和恢复随机状态

import random

random.seed(0)

# 产生几个随机数
for _ in range(3):
random.random()

# 保存当前状态
state = random.getstate()

# 继续产生随机数
r1 = random.random()
r2 = random.random()
r3 = random.random()
print(f"原始序列:{r1:.4f} {r2:.4f} {r3:.4f}")

# 恢复状态
random.setstate(state)

# 再次产生,完全相同
r1b = random.random()
r2b = random.random()
r3b = random.random()
print(f"恢复后: {r1b:.4f} {r2b:.4f} {r3b:.4f}")
print(f"完全相同:{r1==r1b and r2==r2b and r3==r3b}")


第五部分:概率分布

5.1 为什么需要不同的概率分布?

uniform(均匀分布):每个值等概率出现
→ 掷骰子、彩票、简单随机选择

normalvariate(正态分布):中间多、两端少的"钟形"分布
→ 人的身高体重、测量误差、成绩分布、股价波动

expovariate(指数分布):等待时间分布
→ 顾客到店的间隔时间、设备故障间隔

其他分布用于特定科学/工程计算场景


5.2 gauss / normalvariate——正态分布

正态分布有两个参数:

  • mu(μ):均值(分布的中心)
  • sigma(σ):标准差(分布的宽度,越大越分散)

import random

# gauss(mu, sigma):正态分布(速度稍快)
# normalvariate(mu, sigma):正态分布(线程安全)
# 对普通用途,二者效果相同

# 模拟一班学生成绩(均值70,标准差15)
scores = [random.gauss(70, 15) for _ in range(1000)]

# 统计分布
buckets = {"不及格(<60)": 0, "及格(60-69)": 0,
"良好(70-79)": 0, "优秀(80-89)": 0, "满分(90+)": 0}

for s in scores:
if s < 60: buckets["不及格(<60)"] += 1
elif s < 70: buckets["及格(60-69)"] += 1
elif s < 80: buckets["良好(70-79)"] += 1
elif s < 90: buckets["优秀(80-89)"] += 1
else: buckets["满分(90+)"] += 1

print("成绩分布(1000人):")
for label, count in buckets.items():
bar = "█" * (count // 10)
print(f" {label:15s}{count:4d}{bar}")

输出(示例):

成绩分布(1000人):
不及格(<60) : 154人 ███████████████
及格(60-69) : 237人 ███████████████████████
良好(70-79) : 251人 █████████████████████████
优秀(80-89) : 228人 ██████████████████████
满分(90+) : 130人 █████████████


5.3 expovariate(lambd)——指数分布

指数分布描述"独立随机事件之间的等待时间":

  • lambd(λ):单位时间内平均发生次数(速率)
  • 平均等待时间 = 1 / λ

import random

# 模拟咖啡店:平均每分钟来3个顾客
# lambd = 3 → 平均间隔 = 1/3 分钟 ≈ 20秒

arrivals = [random.expovariate(3) for _ in range(100)]

avg_interval = sum(arrivals) / len(arrivals)
print(f"100次到店间隔:平均 {avg_interval*60:.1f} 秒(期望约20秒)")

# 模拟一小时内到店时间轴
time = 0.0
count = 0
while time < 60: # 60分钟
interval = random.expovariate(3)
time += interval
if time < 60:
count += 1

print(f"1小时内到店顾客:{count}人(期望约180人)")


5.4 triangular(low, high, mode)——三角分布

三角分布指定最小值、最大值和最可能的值(众数):

import random

# 项目完工时间估算(最乐观3天,最悲观10天,最可能5天)
estimates = [random.triangular(3, 10, 5) for _ in range(10000)]

avg = sum(estimates) / len(estimates)
over_7 = sum(1 for x in estimates if x > 7) / len(estimates)

print(f"平均完工时间:{avg:.2f} 天")
print(f"超过7天的概率:{over_7:.1%}")


5.5 betavariate(alpha, beta)——Beta 分布

Beta 分布生成 (0, 1) 之间的值,常用于模拟概率、比例:

import random

# alpha=beta=1:退化为均匀分布
# alpha>beta:偏向高值(商品好评率高)
# alpha<beta:偏向低值(商品好评率低)

# 模拟两款商品的好评率
product_a = [random.betavariate(8, 2) for _ in range(1000)] # 通常是高好评
product_b = [random.betavariate(2, 5) for _ in range(1000)] # 通常是低好评

avg_a = sum(product_a) / len(product_a)
avg_b = sum(product_b) / len(product_b)

print(f"商品A 平均好评率:{avg_a:.1%}(期望:{8/(8+2):.1%})")
print(f"商品B 平均好评率:{avg_b:.1%}(期望:{2/(2+5):.1%})")


5.6 其他分布(一览)

import random

# vonmisesvariate(mu, kappa):圆形分布,用于方向/角度
angle = random.vonmisesvariate(0, 2) # 弧度值
print(f"随机角度:{angle:.4f} 弧度(≈{angle*180/3.14:.1f}°)")

# paretovariate(alpha):帕累托分布(80/20法则)
# 用于财富分配、城市规模等幂律分布模拟
wealth = random.paretovariate(1.5)
print(f"帕累托分布:{wealth:.4f}")

# weibullvariate(alpha, beta):威布尔分布
# 用于设备寿命、可靠性工程
lifetime = random.weibullvariate(10, 1.5) # 平均寿命约10年
print(f"设备寿命估计:{lifetime:.2f} 年")

# gammavariate(alpha, beta):伽马分布
g = random.gammavariate(2, 1)
print(f"伽马分布:{g:.4f}")

# lognormvariate(mu, sigma):对数正态分布(用于股价等)
stock_return = random.lognormvariate(0.01, 0.1)
print(f"股票收益乘数:{stock_return:.4f}")


第六部分:实战案例

6.1 案例一:密码生成器

import random
import string

def generate_password(length=12, use_upper=True, use_digits=True, use_symbols=True):
"""
生成指定规则的随机密码

参数:
length – 密码长度(默认12位)
use_upper – 是否包含大写字母
use_digits – 是否包含数字
use_symbols – 是否包含特殊符号
"""
# 构建字符池
chars = string.ascii_lowercase # 小写字母 a-z

if use_upper:
chars += string.ascii_uppercase # 大写字母 A-Z
if use_digits:
chars += string.digits # 数字 0-9
if use_symbols:
chars += "!@#$%^&*" # 常用特殊符号

# 确保密码至少包含每种类型的一个字符
must_have = [random.choice(string.ascii_lowercase)]
if use_upper:
must_have.append(random.choice(string.ascii_uppercase))
if use_digits:
must_have.append(random.choice(string.digits))
if use_symbols:
must_have.append(random.choice("!@#$%^&*"))

# 用 sample 填充剩余位置(不重复,但这里 choices 更合适)
remaining = random.choices(chars, k=length len(must_have))

# 合并并打乱顺序
password_list = must_have + remaining
random.shuffle(password_list)

return "".join(password_list)

# 生成5个密码
print("生成的密码:")
for i in range(5):
pwd = generate_password(16)
print(f" {i+1}. {pwd}")

# 不同配置
print("\\n纯数字PIN码(6位):")
print(" " + "".join(random.choices(string.digits, k=6)))

print("\\n只含字母(8位):")
print(" " + generate_password(8, use_digits=False, use_symbols=False))


6.2 案例二:模拟抽奖系统

import random

class LotterySystem:
"""可配置的抽奖系统"""

def __init__(self, prizes):
"""
prizes: 字典,格式为 {"奖品名称": 权重, …}
权重越大,中奖概率越高
"""

self.prizes = list(prizes.keys())
self.weights = list(prizes.values())

def draw_once(self):
"""抽取一次"""
return random.choices(self.prizes, weights=self.weights, k=1)[0]

def draw_multiple(self, n):
"""抽取 n 次(可重复)"""
return random.choices(self.prizes, weights=self.weights, k=n)

def simulate(self, n_draws=10000):
"""模拟 n 次抽奖,统计概率"""
results = self.draw_multiple(n_draws)
total = self.weights
total_w = sum(total)

print(f"模拟{n_draws}次抽奖结果:")
for prize in self.prizes:
count = results.count(prize)
actual = count / n_draws
expected = self.weights[self.prizes.index(prize)] / total_w
print(f" {prize:10s}:实际{actual:.2%} 期望{expected:.2%} "
f"(差距:{abs(actualexpected)*100:.2f}%)")

# 创建抽奖系统
lottery = LotterySystem({
"一等奖(iPhone)": 1,
"二等奖(AirPods)": 5,
"三等奖(购物券)": 14,
"参与奖(贴纸)": 80,
})

print("=== 单次抽奖 ===")
print(f"恭喜您获得:{lottery.draw_once()}")

print("\\n=== 十连抽 ===")
results = lottery.draw_multiple(10)
for r in results:
print(f" {r}")

print("\\n=== 概率模拟 ===")
lottery.simulate(100000)


6.3 案例三:蒙特卡洛估算 π

蒙特卡洛方法:用大量随机数来估算数学结果。

原理:
在边长为2的正方形内(面积4),有一个半径为1的圆(面积π)
随机撒点,落在圆内的概率 = π/4
因此:π ≈ 4 × (落在圆内的点数 / 总点数)

import random

def estimate_pi(n_samples=1_000_000):
"""用蒙特卡洛方法估算 π"""
inside = 0

for _ in range(n_samples):
x = random.uniform(1, 1)
y = random.uniform(1, 1)
# 判断点是否在半径为1的圆内
if x**2 + y**2 <= 1:
inside += 1

return 4 * inside / n_samples

# 测试不同样本量
print("蒙特卡洛估算 π:")
for n in [100, 1_000, 10_000, 100_000, 1_000_000]:
pi_est = estimate_pi(n)
error = abs(pi_est 3.14159265) / 3.14159265 * 100
print(f" {n:>10,} 个样本:π ≈ {pi_est:.6f} 误差:{error:.4f}%")

输出(示例):

蒙特卡洛估算 π:
100 个样本:π ≈ 3.120000 误差:0.6915%
1,000 个样本:π ≈ 3.148000 误差:0.2025%
10,000 个样本:π ≈ 3.143200 误差:0.0466%
100,000 个样本:π ≈ 3.141880 误差:0.0196%
1,000,000 个样本:π ≈ 3.141652 误差:0.0123%


6.4 案例四:随机游走(股价模拟)

import random

def simulate_stock(initial_price=100.0, days=252, mu=0.0003, sigma=0.02, seed=None):
"""
模拟股票价格的随机游走

参数:
initial_price – 初始价格
days – 交易天数(252 = 一年)
mu – 每日平均收益率
sigma – 每日收益率标准差(波动率)
seed – 随机种子
"""
if seed is not None:
random.seed(seed)

prices = [initial_price]
for _ in range(days 1):
daily_return = random.gauss(mu, sigma)
new_price = prices[1] * (1 + daily_return)
prices.append(max(0.01, new_price)) # 价格不能为负

final = prices[1]
total_ret = (final initial_price) / initial_price * 100
max_p = max(prices)
min_p = min(prices)

return prices, final, total_ret, max_p, min_p

# 模拟一只股票一年的走势
prices, final, total_ret, high, low = simulate_stock(seed=2024)

print(f"初始价格:¥100.00")
print(f"最终价格:¥{final:.2f}")
print(f"全年收益:{total_ret:+.2f}%")
print(f"最高价格:¥{high:.2f}")
print(f"最低价格:¥{low:.2f}")

# 简单绘制走势(用字符画)
print("\\n价格走势(每周一个数据点):")
weekly = prices[::5] # 每5天取一个点
max_w = max(weekly)
min_w = min(weekly)
for i, p in enumerate(weekly):
bar_len = int((p min_w) / (max_w min_w) * 30)
bar = "█" * bar_len
print(f" 第{i*5+1:3d}天:¥{p:7.2f} {bar}")


6.5 案例五:简易文字游戏——猜数字

import random

def guessing_game():
"""猜数字游戏"""
secret = random.randint(1, 100)
max_try = 7
attempt = 0

print("=" * 40)
print(" 欢迎来到猜数字游戏!")
print(f" 我想了一个 1~100 的数字")
print(f" 你有 {max_try} 次机会,加油!")
print("=" * 40)

while attempt < max_try:
attempt += 1
remaining = max_try attempt

try:
guess = int(input(f"\\n第{attempt}次猜测(还剩{remaining}次):"))
except ValueError:
print(" 请输入整数!")
attempt -= 1
continue

if guess < 1 or guess > 100:
print(" 超出范围!请输入 1~100 的数字")
attempt -= 1
continue

if guess == secret:
print(f"\\n🎉 恭喜!你猜对了!答案就是 {secret}!")
print(f" 用了 {attempt} 次猜中!")
return
elif guess < secret:
hint = "太小了!"
if secret guess > 20:
hint += "(差很远呢)"
print(f" ↑ {hint}")
else:
hint = "太大了!"
if guess secret > 20:
hint += "(差很远呢)"
print(f" ↓ {hint}")

print(f"\\n💔 游戏结束!答案是 {secret},下次加油!")

# 运行游戏
# guessing_game() # 取消注释即可运行


6.6 案例六:随机名字/词语生成器

import random

# 中文随机姓名生成
SURNAMES = ["赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈",
"张", "刘", "杨", "黄", "林", "朱", "何", "罗", "宋", "唐"]

NAMES_MALE = ["伟", "芳", "娜", "秀英", "敏", "静", "丽", "强", "磊",
"军", "洋", "勇", "艳", "杰", "娟", "涛", "明", "超", "秀兰"]
NAMES_FEMALE = ["雪", "梅", "霞", "云", "燕", "莉", "琴", "慧", "颖", "雯",
"婷", "美", "珍", "兰", "凤", "英", "文", "春", "华", "玲"]

def random_chinese_name(gender="random"):
if gender == "random":
gender = random.choice(["male", "female"])
surname = random.choice(SURNAMES)
pool = NAMES_MALE if gender == "male" else NAMES_FEMALE
# 随机1或2个字的名
if random.random() < 0.4:
given = random.choice(pool)
else:
given = random.choice(pool) + random.choice(pool)
return surname + given

# 生成一份虚假的名单
print("随机生成10个名字:")
for i in range(10):
name = random_chinese_name()
age = random.randint(18, 60)
city = random.choice(["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉"])
print(f" {i+1:2d}. {name}{age}岁,来自{city}")


第七部分:安全随机数

7.1 普通 random 的安全性问题

random 模块使用 Mersenne Twister 算法:
✅ 速度快,统计特性好
❌ 不适合密码学用途!

原因:
Mersenne Twister 不是"密码学安全"的随机数生成器
攻击者只要观察到足够多的输出值(624个32位数),
就可以完整推算出内部状态,预测未来的所有随机数!

因此,如果你用 random 生成密码、令牌、验证码等,
存在被攻击者预测的安全风险。


7.2 secrets 模块——密码学安全随机数

Python 3.6+ 内置了 secrets 模块,专门用于安全场景:

import secrets
import string

# secrets.randbelow(n):安全的 0 到 n-1 随机整数
print(secrets.randbelow(100))

# secrets.randbits(k):安全的 k 位随机整数
print(secrets.randbits(32))

# secrets.choice(seq):安全版 choice
print(secrets.choice(["A", "B", "C", "D"]))

# secrets.token_bytes(n):n 字节随机二进制数据
token = secrets.token_bytes(16)
print(f"随机字节:{token.hex()}")

# secrets.token_hex(n):n 字节的十六进制字符串(常用于 API Key)
api_key = secrets.token_hex(32)
print(f"API Key:{api_key}")

# secrets.token_urlsafe(n):URL 安全的随机字符串
session_id = secrets.token_urlsafe(32)
print(f"Session ID:{session_id}")

def secure_password(length=16):
"""使用 secrets 生成真正安全的密码"""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
while True:
password = "".join(secrets.choice(alphabet) for _ in range(length))
# 确保包含各类字符
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
has_symbol = any(c in "!@#$%^&*" for c in password)
if all([has_upper, has_lower, has_digit, has_symbol]):
return password

print("\\n安全密码(用 secrets 生成):")
for i in range(3):
print(f" {secure_password()}")


7.3 random vs secrets 场景对比

使用 random 的场景(速度优先,无安全要求):
✅ 游戏开发(地图生成、战斗结算)
✅ 模拟实验(蒙特卡洛、数值模拟)
✅ 机器学习(数据打乱、随机采样)
✅ 测试数据生成
✅ 抽奖、游戏概率

使用 secrets 的场景(安全性优先):
✅ 用户密码生成
✅ API 密钥生成
✅ Session Token(登录令牌)
✅ 邮件验证码、短信验证码
✅ CSRF Token
✅ 任何"如果被预测会造成损失"的场景


第八部分:常见陷阱与注意事项

8.1 陷阱1:shuffle 不返回新列表

import random

data = [1, 2, 3, 4, 5]

# ❌ 错误写法(很多新手犯的错)
shuffled = random.shuffle(data)
print(shuffled) # None!!!原因:shuffle 原地修改,返回 None

# ✅ 正确写法1:先复制,再 shuffle
data2 = data.copy()
random.shuffle(data2)
print(data2) # 打乱后的列表

# ✅ 正确写法2:用 sample 代替 shuffle,它返回新列表
data3 = random.sample(data, len(data))
print(data3) # 打乱后的新列表,原 data 不变

# ✅ 正确写法3:sorted + key=lambda: random.random()
data4 = sorted(data, key=lambda x: random.random())
print(data4)


8.2 陷阱2:randint 和 randrange 的端点问题

import random

# randint(a, b):含 b(闭区间 [a, b])
values_int = set(random.randint(1, 5) for _ in range(10000))
print(f"randint(1,5) 可能出现的值:{sorted(values_int)}") # {1, 2, 3, 4, 5}

# randrange(a, b):不含 b(半开区间 [a, b))
values_range = set(random.randrange(1, 5) for _ in range(10000))
print(f"randrange(1,5) 可能出现的值:{sorted(values_range)}") # {1, 2, 3, 4}

# 容易犯错的场景
# 想从 0-9 中取一个数:
print(random.randrange(10)) # ✅ 0 到 9
print(random.randint(0, 9)) # ✅ 0 到 9
print(random.randrange(0, 10)) # ✅ 0 到 9
# print(random.randint(0, 10)) # ⚠️ 0 到 10!含 10!


8.3 陷阱3:多线程下的随机数不安全

import random
import threading

# ❌ 问题:多个线程共享同一个随机数生成器,可能导致结果不一致
# 解决方案1:每个线程创建自己的 Random 实例

def worker(thread_id):
# ✅ 每个线程用独立的 Random 对象
rng = random.Random() # 独立实例,自动获取不同种子
for _ in range(3):
print(f"线程{thread_id}{rng.randint(1, 100)}")

threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()


8.4 陷阱4:种子相同但调用顺序不同

import random

# 种子固定后,结果依赖于调用顺序
random.seed(42)
a = random.randint(1, 10)
b = random.random()
c = random.choice(["x", "y", "z"])
print(f"顺序1:a={a}, b={b:.4f}, c={c}")

random.seed(42)
# 改变调用顺序!
c2 = random.choice(["x", "y", "z"]) # 先调用这个
a2 = random.randint(1, 10)
b2 = random.random()
print(f"顺序2:a={a2}, b={b2:.4f}, c={c2}")
# 结果完全不同!因为消耗随机数的顺序变了


8.5 陷阱5:用 random 生成安全令牌(危险!)

import random
import secrets

# ❌ 不安全:用 random 生成验证码
def bad_verification_code():
return "".join([str(random.randint(0, 9)) for _ in range(6)])

# ✅ 安全:用 secrets 生成验证码
def good_verification_code():
return "".join([str(secrets.randbelow(10)) for _ in range(6)])

print(f"不安全的验证码:{bad_verification_code()}")
print(f"安全的验证码: {good_verification_code()}")


第九部分:Random 类——创建独立的随机生成器

9.1 为什么要用 Random 类?

默认情况下,import random 后使用的是一个"全局实例"。
如果你需要:
– 多个独立的随机流(互不影响)
– 不同模块使用不同的种子
– 不影响全局随机状态

就应该使用 random.Random 类创建独立实例。

import random

# 创建独立的随机生成器实例
rng1 = random.Random(seed=100) # 种子100
rng2 = random.Random(seed=200) # 种子200

# 两个生成器完全独立
print("生成器1:", [rng1.randint(1, 10) for _ in range(5)])
print("生成器2:", [rng2.randint(1, 10) for _ in range(5)])

# 重置生成器1,生成器2不受影响
rng1.seed(100)
print("生成器1重置后:", [rng1.randint(1, 10) for _ in range(5)])
print("生成器2继续:", [rng2.randint(1, 10) for _ in range(5)])

# 全局随机状态没有被影响
print("全局 random:", random.randint(1, 10)) # 不受 rng1/rng2 影响


9.2 实际应用:游戏中多个独立随机流

import random

class GameEngine:
"""使用独立随机流的游戏引擎"""

def __init__(self, seed=None):
base_seed = seed or random.randint(0, 2**32)

# 不同系统使用不同种子,互相不干扰
self.map_rng = random.Random(base_seed ^ 0x1234) # 地图生成
self.loot_rng = random.Random(base_seed ^ 0x5678) # 掉落计算
self.enemy_rng = random.Random(base_seed ^ 0x9ABC) # 敌人 AI
self.effect_rng = random.Random(base_seed ^ 0xDEF0) # 视觉效果

def generate_map_tile(self):
tiles = ["草地", "森林", "山地", "河流", "沙漠"]
return self.map_rng.choice(tiles)

def roll_loot(self):
if self.loot_rng.random() < 0.1: # 10% 掉落稀有物品
return self.loot_rng.choice(["传奇武器", "稀有盔甲", "神秘药水"])
return self.loot_rng.choice(["金币", "草药", "普通装备"])

def enemy_action(self):
return self.enemy_rng.choice(["攻击", "防御", "逃跑", "使用技能"])

# 使用固定种子,确保游戏可重现
game = GameEngine(seed=42)

print("游戏地图(前6格):")
for _ in range(6):
print(f" {game.generate_map_tile()}", end=" ")
print()

print("\\n战斗掉落(5场):")
for i in range(5):
print(f" 第{i+1}场:{game.roll_loot()}")

print("\\n敌人行动(5次):")
for i in range(5):
print(f" 回合{i+1}{game.enemy_action()}")


第十部分:完整速查表

10.1 函数分类速查

📌 生成随机数
random.random() → [0.0, 1.0) 的浮点数
random.uniform(a, b) → [a, b] 的浮点数
random.randint(a, b) → [a, b] 的整数(含两端)
random.randrange(stop) → [0, stop) 的整数
random.randrange(a, b) → [a, b) 的整数(不含 b)
random.randrange(a, b, s) → range(a,b,s) 中的整数
random.getrandbits(k) → k 位随机整数

📌 序列操作
random.choice(seq) → 随机选 1 个
random.choices(seq, k=n) → 随机选 n 个(可重复,可加权)
random.sample(seq, k) → 随机选 k 个(不重复)
random.shuffle(lst) → 原地打乱(返回 None!)

📌 随机种子
random.seed(n) → 设置种子
random.getstate() → 获取当前状态
random.setstate(state) → 恢复状态

📌 概率分布
random.gauss(mu, sigma) → 正态分布
random.normalvariate(mu, sigma) → 正态分布(线程安全版)
random.uniform(a, b) → 均匀分布
random.expovariate(lambd) → 指数分布
random.triangular(low,high,mode)→ 三角分布
random.betavariate(alpha, beta) → Beta 分布
random.lognormvariate(mu,sigma) → 对数正态分布

📌 安全随机(secrets 模块)
secrets.randbelow(n) → 安全随机整数 [0, n)
secrets.randbits(k) → 安全随机 k 位整数
secrets.choice(seq) → 安全随机选一个
secrets.token_hex(n) → n 字节的十六进制字符串
secrets.token_urlsafe(n) → URL 安全的随机字符串
secrets.token_bytes(n) → n 字节随机二进制数据


10.2 选择哪个函数?决策树

你想要的是…

生成随机数?
├─ 浮点数 0~1 → random()
├─ 浮点数 a~b → uniform(a, b)
├─ 整数(含两端) → randint(a, b)
└─ 整数(类似range)→ randrange(a, b, step)

从序列中选?
├─ 只选1个 → choice(seq)
├─ 选多个可重复 → choices(seq, k=n)
│ └─ 有概率权重 → choices(seq, weights=[…], k=n)
├─ 选多个不重复 → sample(seq, k)
└─ 打乱整个序列 → shuffle(seq)(原地!)

需要可重现结果? → seed(n) 设置种子
需要安全性? → 用 secrets 模块
需要独立随机流? → Random() 创建实例
需要概率分布? → gauss / expovariate / triangular 等


总结

random 模块是 Python 中最常用的内置模块之一。学完本章,你应该掌握:

  • 基础函数:random()、uniform()、randint()、randrange() 的区别和使用场景
  • 序列操作:choice(选1个)、choices(有放回)、sample(无放回)、shuffle(原地打乱)
  • 随机种子:为什么要用 seed(),什么时候用
  • 概率分布:正态分布、指数分布、三角分布等应用场景
  • 安全随机:普通 random 不能用于密码学,应使用 secrets 模块
  • 独立实例:用 random.Random() 创建独立的生成器,避免干扰全局状态
  • 常见陷阱:shuffle 返回 None、randint 含端点、多线程安全等
  • 24.Python random 模块完全指南

    赞(0)
    未经允许不得转载:171主机测评 » 24.Python random 模块完全指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址