Skip to content

02-random随机数

Python 3.11+


为什么需要 random 模块?

问题场景

你需要开发一个抽奖系统,从参与者列表中随机选出 3 名获奖者:

python
# ❌ 自己实现随机逻辑:分布不均匀,无法复现
index = hash(str(time.time())) % len(participants)

# ✅ 用 random 模块:均匀分布,可用 seed 复现
import random
random.seed(42)                              # 固定种子,测试可复现
winners = random.sample(participants, k=3)  # 不重复随机抽取

# 安全令牌场景(用 secrets 而非 random)
import secrets
token = secrets.token_urlsafe(16)           # 密码学安全的随机字符串

random 模块适合模拟、测试、游戏等非安全场景;涉及密码和令牌时改用 secrets


概念铺垫

random 模块的核心是 Mersenne Twister(梅森旋转) 算法——一个基于矩阵线性递归的伪随机数生成器(PRNG),周期长达 2^19937-1。

┌──────────────────────────────────────────────────────────────┐
│           Mersenne Twister 算法原理                           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   状态数组 state[624]  每个元素 32 bit                        │
│                                                              │
│   生成过程:                                                  │
│   1. 初始化:seed → 生成 624 个状态字                         │
│   2. 每次调用:从 state 取一个字,进行"回火"变换               │
│   3. 用完 624 个字后,执行"扭转"生成下一批 624 个字             │
│                                                              │
│   回火变换(tempering):确保输出均匀分布                      │
│   y = state[i]                                               │
│   y ^= (y >> 11)                                             │
│   y ^= (y << 7) & 0x9D2C5680                                 │
│   y ^= (y << 15) & 0xEFC60000                                │
│   y ^= (y >> 18)                                             │
│                                                              │
│   重要特性:                                                  │
│   · 确定性:相同 seed → 相同序列                              │
│   · 周期长:~4.3×10^6001 次调用后才循环                       │
│   · 非密码学安全:观察 624 个连续输出可恢复完整状态            │
│                                                              │
└──────────────────────────────────────────────────────────────┘

L1 理解层:会用

第一部分:基本随机数生成

1.1 随机整数

实际场景

在游戏开发、模拟测试、数据采样等场景中,经常需要生成随机整数。比如掷骰子、生成测试数据、随机抽样等。

问题:randint()randrange() 有什么区别?如何生成指定范围内的随机整数?

python
import random

# 随机整数 [a, b]
rand_int: int = random.randint(1, 10)    # 1 到 10 之间的整数

# 随机整数 [a, b),步长为 step
rand_range_step: int = random.randrange(1, 10, 2)  # 1, 3, 5, 7, 9 中的一个

# 随机整数 [a, b)
rand_range: int = random.randrange(10)     # 0 到 9 之间的整数

1.2 随机浮点数

实际场景

在科学计算、概率模拟、蒙特卡洛方法中,需要生成随机浮点数。比如模拟物理过程、生成随机概率等。

问题:random()uniform() 有什么区别?如何生成指定范围的随机浮点数?

python
import random

# 随机浮点数 [0, 1)
rand_float: float = random.random()          # 0.0 到 1.0 之间

# 随机浮点数 [a, b]
rand_uniform: float = random.uniform(1.0, 10.0)  # 1.0 到 10.0 之间

第二部分:序列操作

2.1 随机选择

实际场景

在抽奖系统、随机推荐、A/B 测试等场景中,需要从列表中随机选择元素。比如抽奖程序、随机展示广告、随机选择测试用例等。

问题:choice()choices()sample() 有什么区别?

python
import random

items: list[str] = ['苹果', '香蕉', '橙子', '葡萄', '西瓜']

# 随机选择一个元素
one_item: str = random.choice(items)     # 随机一个水果

# 随机选择多个元素(可重复)
multi_items: list[str] = random.choices(items, k=3)  # 随机 3 个,可能重复

# 随机选择多个元素(不重复)
unique_items: list[str] = random.sample(items, k=3)   # 随机 3 个,不重复

2.2 打乱顺序

实际场景

在洗牌游戏、随机排序、数据增强等场景中,需要随机打乱序列顺序。比如扑克牌游戏、随机展示列表项等。

问题:如何原地打乱列表顺序?打乱后可以恢复吗?

python
import random

items: list[int] = [1, 2, 3, 4, 5]

# 原地打乱
random.shuffle(items)
print(items)  # [3, 1, 5, 2, 4](随机顺序)

第三部分:随机分布

3.1 均匀分布

实际场景

在蒙特卡洛模拟、随机采样中,经常需要均匀分布的随机数。

python
import random

# 均匀分布 [a, b]
values: list[float] = [random.uniform(0, 1) for _ in range(5)]
print(values)  # [0.23, 0.87, 0.45, 0.12, 0.67]

3.2 正态分布

实际场景

在统计模拟、自然现象模拟、金融建模中,正态分布(高斯分布)是最常见的分布。比如模拟身高分布、股票价格波动等。

问题:如何生成符合正态分布的随机数?gauss()normalvariate() 有什么区别?

python
import random

# 正态分布(高斯分布)
# mu: 均值, sigma: 标准差
values: list[float] = [random.gauss(0, 1) for _ in range(5)]
print(values)  # 接近标准正态分布

# 另一种写法(行为相同)
values_alt: list[float] = [random.normalvariate(0, 1) for _ in range(5)]

3.3 其他分布

实际场景

在特定领域的模拟中,需要其他类型的概率分布。比如指数分布用于模拟等待时间,Beta 分布用于贝叶斯统计等。

python
import random

# 指数分布
exp_value: float = random.expovariate(1.0)

# 三角分布
tri_value: float = random.triangular(0, 1, 0.5)  # low, high, mode

# Beta 分布
beta_value: float = random.betavariate(2, 5)

# Gamma 分布
gamma_value: float = random.gammavariate(1, 1)

第四部分:随机种子与可复现性

4.1 设置随机种子

实际场景

在测试、调试、科学实验中,需要保证随机数的可复现性。设置相同的种子可以保证每次运行生成相同的随机序列。

问题:为什么要设置随机种子?什么时候需要设置?

python
import random

# 设置随机种子,确保可复现
random.seed(42)
val1: float = random.random()  # 0.6394267984578837

random.seed(42)
val2: float = random.random()  # 0.6394267984578837(相同结果)

第五部分:实际应用示例

5.1 生成随机密码

实际场景

在用户注册、密码重置、系统初始化等场景中,需要生成随机密码或密钥。

python
import random
import string

def generate_password(length: int = 12) -> str:
    """生成随机密码"""
    chars: str = string.ascii_letters + string.digits + '!@#$%^&*'
    return ''.join(random.choices(chars, k=length))

password: str = generate_password(16)
print(password)  # xK9#mP2@nL5$qR8!

5.2 抽奖程序

实际场景

在活动抽奖、随机选人等场景中,需要从参与者中随机选择获奖者。

python
import random

def lottery(participants: list[str], winners_count: int) -> list[str]:
    """抽奖程序"""
    winners: list[str] = random.sample(participants, winners_count)
    return winners

participants: list[str] = ['张三', '李四', '王五', '赵六', '钱七', '孙八']
winners: list[str] = lottery(participants, 2)
print(f"中奖者: {winners}")

5.3 随机颜色生成

实际场景

在数据可视化、图形设计、游戏开发中,需要生成随机颜色。

python
import random

def random_color() -> str:
    """生成随机十六进制颜色"""
    return f'#{random.randint(0, 0xFFFFFF):06x}'

colors: list[str] = [random_color() for _ in range(5)]
for color in colors:
    print(color)  # #a3f2c1, #8b4d2e, ...

5.4 模拟掷骰子

实际场景

在游戏开发、概率教学、统计模拟中,需要模拟骰子投掷。

python
import random

def roll_dice(count: int = 1) -> list[int]:
    """掷骰子"""
    return [random.randint(1, 6) for _ in range(count)]

result1: list[int] = roll_dice(2)  # [3, 5]
result2: list[int] = roll_dice(3)  # [1, 6, 2]

5.5 模拟抛硬币

实际场景

在概率教学、统计模拟中,需要模拟抛硬币实验。

python
import random

def flip_coin() -> str:
    """抛硬币"""
    return random.choice(['正面', '反面'])

results: list[str] = [flip_coin() for _ in range(10)]
print(results)  # ['正面', '反面', '正面', ...]

第六部分:安全随机数

6.1 使用 secrets 模块

实际场景

在生成安全令牌、API 密钥、会话 ID 等安全敏感场景中,不应使用 random 模块,而应使用 secrets 模块,因为它生成的是加密安全的随机数。

问题:为什么安全敏感场景要使用 secrets 而不是 random

python
import secrets

# 安全随机整数
secure_int: int = secrets.randbelow(1000000)

# 安全随机字节
key: bytes = secrets.token_bytes(16)

# 安全随机 URL 安全字符串
token_url: str = secrets.token_urlsafe(16)

# 安全随机十六进制字符串
token_hex: str = secrets.token_hex(16)

L2 实践层:用好

推荐做法

做法原因示例
安全场景用 secrets 而非 randomrandom 基于伪随机算法,可被预测secrets.token_urlsafe(16)
测试时固定 random.seed()保证测试可复现,排查问题方便random.seed(42)
不重复抽样用 sample(),可重复用 choices()二者语义不同,混用导致逻辑错误random.sample(lst, k=3)
带权重抽样用 choices(weights=...)模拟概率不等的抽奖、A/B 测试random.choices(lst, weights=[60,30,10])
生产代码避免在模块顶层调用 random.seed()会影响整个程序的随机状态测试中局部设置

实际应用示例

python
import random
import string
import secrets

# 带权重的抽奖(一等奖 10%,二等奖 30%,三等奖 60%)
prizes = ["一等奖", "二等奖", "三等奖"]
result = random.choices(prizes, weights=[10, 30, 60], k=1)[0]

# 生成安全 API token
api_token: str = secrets.token_urlsafe(32)

# 可复现的随机测试数据
rng = random.Random(42)        # 使用独立的 Random 实例,不影响全局状态
test_data = [rng.randint(1, 100) for _ in range(10)]

反模式:不要这样做

python
# ❌ 密码/令牌用 random
import random
password = ''.join(random.choices('abcdef0123456789', k=32))  # 不安全!

# ✅ 用 secrets
import secrets
token = secrets.token_hex(16)

# ❌ 混淆 sample 和 choices
import random
items = [1, 2, 3]
# sample 要求 k <= len(items)
random.sample(items, k=5)   # ValueError,元素不够

# ✅ 可重复时用 choices
random.choices(items, k=5)  # 允许重复,[2, 1, 3, 2, 1]

# ❌ 在模块顶层设置 seed(影响全局随机状态)
import random
random.seed(42)              # 会让所有依赖 random 的代码都变成伪随机

常见陷阱

陷阱现象解决方案
random.sample(lst, k>len(lst))ValueError确认 k <= len(lst),或改用 choices
未设种子的测试每次运行结果不同,难以复现 bug测试前 random.seed(固定值)
random 生成密码伪随机,可被预测改用 secrets 模块
shuffle 修改原列表原始数据丢失copy = lst[:],再 shuffle(copy)
全局 seed 污染一处设了 seed,影响其他模块随机行为random.Random(seed) 创建独立实例

适用场景

场景推荐模块说明
游戏、模拟、测试数据random性能好,可复现
统计分布模拟random内置正态、指数等分布
密码、令牌、会话 IDsecrets密码学安全
大规模数值模拟numpy.random批量生成,速度快 100 倍

L3 专家层:深入

Python 如何实现

random 模块在 CPython 中由两部分组成:纯 Python 的 random.py(算法逻辑)和 C 扩展的 _random 模块(Mersenne Twister 核心,位于 Modules/_randommodule.c)。

┌──────────────────────────────────────────────────────────────┐
│            random 模块实现架构                                │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   random.py (Python)                _random.c (C)            │
│   ─────────────────                ─────────────             │
│   random.seed(42)          →        init_genrand(seed)       │
│   random.random()          →        genrand_res53()          │
│   random.randint(a,b)      →        randint_impl()           │
│   random.shuffle(seq)      →        Fisher-Yates (Python)    │
│                                                              │
│   C 实现的核心优势:                                          │
│   · 状态数组在 C 内存中,无 Python 对象开销                    │
│   · genrand_res53() 在 C 层合并两个 32 位随机数               │
│       生成 53 位精度的 double(对应 IEEE 754 尾数)            │
│                                                              │
└──────────────────────────────────────────────────────────────┘
python
# 验证:查看 random 的内部状态
import random

r = random.Random()
state = r.getstate()
print(f"状态类型: {type(state)}")
print(f"状态长度: {len(state)}")  # 元组 (版本, 内部元组, gauss_next)
# MT 状态: 624 个 32 位整数
mt_state = state[1]
print(f"MT 数组长度: {len(mt_state)}")  # 625 (624 + index)

# 种子如何影响状态:seed(0) vs seed(1)
r0 = random.Random(0)
r1 = random.Random(1)
s0 = r0.getstate()[1]
s1 = r1.getstate()[1]
# 相邻种子产生完全不同的状态序列
print(f"seed(0) 第一个状态字: {s0[0]}")  # 完全不同的值
print(f"seed(1) 第一个状态字: {s1[0]}")  # 完全不同的值
python
# 验证:Mersenne Twister 安全性弱点
# 观察 624 个连续的 32 位输出可恢复状态
import random

# random.random() 底层调用 genrand_res53():
#   用 2 个连续的 32 位 MT 输出构造 53 位浮点数
# 所以 624 次 random.random() 消耗 1248 个 MT 状态字

# 非安全场景的正确做法:
# random 用于模拟测试,secrets 用于安全场景
import secrets, os

# secrets 模块使用 os.urandom()(底层 /dev/urandom 或 CryptGenRandom)
print(f"secrets 熵源: {os.urandom(16).hex()}")

# 验证 secrets 不使用 MT 算法
s = secrets.randbits(256)
print(f"secrets 256位随机数: {s:#x}")

性能考量

操作复杂度说明
random.random()O(1)单次 MT 回火操作,极快
random.randint(a, b)O(1)先调用 random(),再映射到 [a,b]
random.choice(seq)O(1)一次 random() + 索引
random.sample(seq, k)O(k + k*(N-k)/N)蓄水池抽样算法
random.shuffle(seq)O(n)Fisher-Yates 算法,n 次交换
random.seed(n)O(625)初始化 624 字状态数组
random.Random(n) (新实例)O(625)独立状态,无全局污染
python
# 验证性能:独立 Random 实例 vs 全局 random
import random, timeit

# 全局 random 模块(内置 C 实现)
t1 = timeit.timeit('random.random()', 'import random', number=1000000)
# 独立 Random 实例(相同性能)
t2 = timeit.timeit('r.random()', 'import random; r = random.Random(42)', number=1000000)
print(f"全局 random.random(): {t1:.4f}s / 1M calls")
print(f"实例 r.random():       {t2:.4f}s / 1M calls")

知识关联

┌──────────────────────────────────────────────────────────────┐
│            random 模块知识关联图                              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌──────────┐     ┌──────────┐     ┌───────────┐          │
│   │  random  │────→│ Mersenne │────→│ 种子的    │          │
│   │  模块    │     │ Twister  │     │ 确定性    │          │
│   └──────────┘     └──────────┘     └───────────┘          │
│        │                │                 │                  │
│        ↓                ↓                 ↓                  │
│   ┌──────────┐     ┌──────────┐     ┌───────────┐          │
│   │ secrets  │     │ os.urandom│     │ 密码学    │          │
│   │  安全随机 │     │ /dev/     │     │ 安全      │          │
│   │          │     │  urandom  │     │ CSPRNG   │          │
│   └──────────┘     └──────────┘     └───────────┘          │
│                                                              │
│   ┌──────────┐     ┌──────────┐     ┌───────────┐          │
│   │ numpy    │     │ PCG64 /  │     │ 向量化    │          │
│   │ random   │────→│ Philox   │────→│ 批量生成  │          │
│   └──────────┘     └──────────┘     └───────────┘          │
│                                                              │
│   选择决策树:                                                │
│   游戏/模拟/测试  → random(MT19937)                         │
│   密码/令牌       → secrets(os.urandom)                    │
│   统计推断/科学   → numpy.random(PCG64 更优)                │
│   可复现性要求   → random.seed() + 独立 Random 实例           │
│                                                              │
│   Mersenne Twister 局限性:                                   │
│   · 不能用于密码学(状态可被逆向)                             │
│   · 初始化慢(624 字),不适合频繁创建实例                     │
│   · 现代替代:PCG、Xoroshiro 系列(numpy 1.17+ 默认)         │
│                                                              │
└──────────────────────────────────────────────────────────────┘

本章小结

┌──────────────────────────────────────────────────────────────┐
│                  random 模块 知识要点                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   随机整数:randint(a,b)  randrange(a,b,step)                │
│   随机浮点:random()  uniform(a,b)                           │
│                                                              │
│   序列操作:                                                 │
│   choice(seq)         随机选 1 个                            │
│   choices(seq,k=N)    可重复选 N 个(支持 weights)          │
│   sample(seq,k=N)     不重复选 N 个                          │
│   shuffle(seq)        原地打乱                               │
│                                                              │
│   分布:gauss(mu,sigma)  normalvariate  uniform  expovariate │
│   种子:seed(n) 固定状态,测试可复现                         │
│                                                              │
│   安全场景必须用 secrets:                                   │
│   token_urlsafe()  token_hex()  token_bytes()  randbelow()   │
│                                                              │
│   L3 要点:Mersenne Twister (MT19937) → 624 字状态 → 可预测  │
│                                                              │
└──────────────────────────────────────────────────────────────┘