Python 生成随机数要先分场景:普通模拟用 random.random(),指定整数范围用 randint(),从候选项抽取用 choice();密码、验证码和令牌不能用 random 模块,应改用 secrets。

本文基于 Python 3,用同一个固定种子演示三种 Python 随机数方法,并给出本机实际输出。你会看到两端是否包含、怎样复现实验、空序列为什么报错,以及伪随机和安全随机的边界。固定种子只用于测试,不应出现在安全凭据生成流程;同一输出也不能证明分布公平。
一、先看结论:三种随机数方法怎么选
| 需求 | 推荐方法 | 返回值 | 区间/行为 | 注意 |
|---|---|---|---|---|
| 生成 0 到 1 之间小数 | random.random() |
浮点数 | [0.0, 1.0),不含 1.0 |
适合概率、模拟、测试 |
| 生成指定整数范围 | random.randint(a, b) |
整数 | 包含 a 和 b |
不要把右端点当成 range 排除 |
| 从候选项中抽一个 | random.choice(seq) |
序列中的一个元素 | 要求非空序列 | 空序列抛 IndexError |
| 密码、验证码、令牌 | secrets |
安全随机值 | 操作系统随机源 | 不要固定种子 |
| 可复现实验 | random.Random(seed) |
独立生成器 | 相同种子和调用顺序可复现 | 只用于测试 |
一句话:普通模拟用 random,安全凭据用 secrets;先确定输出类型和区间,再选 API。
二、方法一:random 生成 0 到 1 之间的小数
random.random() 返回大于等于 0.0、小于 1.0 的浮点数,也就是半开区间 [0.0, 1.0)。它适合概率判断、模拟和测试数据。
import random
# 创建独立的随机生成器实例,避免影响模块级全局随机状态
rng = random.Random(20260917)
# 生成 [0.0, 1.0) 之间的小数
value = rng.random()
# 保留 6 位小数输出
print(f"{value:.6f}")
本文在 Python 3 环境实际执行,输出为:
0.616661
不同 Python 版本和实现可能得到不同结果,但固定种子后,在同一环境、同一调用顺序下可复现。
单独创建 Random 实例能避免和模块级全局状态互相影响。种子相同、Python 实现与调用顺序相同时,序列可复现,便于测试失败回放。它不是“更随机”,恰恰是为了稳定重现。
random 模块还提供 uniform(a, b)、shuffle、sample 和 choices。不要因为它们都在同一模块就认为语义一致:
| 函数 | 行为 | 是否原地修改 | 是否允许重复 |
|---|---|---|---|
shuffle(seq) |
原地打乱列表 | 是 | 不适用 |
sample(pop, k) |
返回不重复样本 | 否 | 否 |
choices(pop, k) |
有放回抽取 | 否 | 是 |
uniform(a, b) |
返回区间内浮点数 | 否 | 不适用 |
测试时应断言范围、长度和约束,不要断言一个未固定种子的具体数值。
如果你对模块导入、函数和对象还不熟,可以先补 Python3基础教程,再继续看随机状态。
三、方法二:randint 生成包含两端的整数
randint(a, b) 会返回 a <= N <= b 的整数。初学者最常犯的错误,是把它当成不包含右端点的 range。
import random
# 固定种子,方便复现实验
rng = random.Random(20260917)
# 先消耗一次随机状态
print(rng.random())
# 生成 1 到 6 之间的整数,包含 1 和 6
print(rng.randint(1, 6))
由于先消耗了一次随机状态,本机第二行实际得到:
6
如果删除前一次 random() 调用,固定种子下的结果也会变化。这说明固定种子要和完整调用顺序一起记录。
若需要不包含上限的整数,可用 randrange(stop) 或 randrange(start, stop, step):
import random
rng = random.Random(20260917)
# 从 0、2、4、6、8 中选择,不包含 stop=10
print(rng.randrange(0, 10, 2))
Python 3.12 起,randrange(10.0) 这类自动把非整数转换为整数的写法会抛 TypeError,参数应明确传整数。
randint 用法适合骰子、题号和闭区间业务值,但不适合用 int(random() * n) 自己模拟。标准库的范围函数处理大整数与分布细节更稳,也让端点语义一眼可见。
常用签名和边界记不住时,可查 Python速查手册,不要靠猜测决定右端点。
| 写法 | 是否包含左端点 | 是否包含右端点 | 示例结果 |
|---|---|---|---|
randint(1, 6) |
是 | 是 | 1、2、3、4、5、6 |
randrange(1, 6) |
是 | 否 | 1、2、3、4、5 |
range(1, 6) |
是 | 否 | 1、2、3、4、5 |
randrange(0, 10, 2) |
是 | 否 | 0、2、4、6、8 |
四、方法三:choice 从序列中抽一个元素
抽题、抽颜色或随机选择配置时,choice(sequence) 比“先生成索引再取值”更直接。
import random
# 固定种子,保持与本文测试序列一致
rng = random.Random(20260917)
# 前两次调用保留,用于复现完整测试顺序
rng.random()
rng.randint(1, 6)
topics = ["Python", "SQL", "JavaScript"]
# 从列表中随机选择一个元素
print(rng.choice(topics))
本机实际输出为:
SQL
前两次调用保留在示例中,是为了与本文完整测试序列一致。若只复制 choice 部分,得到其他选项也完全正常。
空序列没有可选元素。本文实际执行 rng.choice([]),得到:
IndexError: Cannot choose from an empty sequence
业务代码应先验证候选集合,并决定空集合时返回默认值、跳过任务还是抛业务异常。不要捕获所有 Exception 后悄悄返回空字符串,这会把数据缺失伪装成正常结果。
choice 空序列是输入约束错误,不是随机失败。候选由数据库查询产生时,应把查询条件、候选数量和去重规则写进日志;候选带权重时使用 choices(population, weights=...),并验证权重数量一致、非负且总和不是 0。
| 函数 | 抽取方式 | 是否可重复 | 空序列/越界行为 |
|---|---|---|---|
choice(seq) |
抽一个 | 不适用 | 空序列抛 IndexError |
choices(pop, k) |
抽 k 个,有放回 | 可重复 | 总体为空抛 IndexError |
sample(pop, k) |
抽 k 个,无放回 | 不重复 | k > len(pop) 抛 ValueError |
shuffle(seq) |
原地打乱 | 不适用 | 修改原列表,返回 None |
五、普通随机和安全随机必须分开
Python random官方文档 明确说明,该模块使用确定性伪随机生成器,不适合加密用途。
抽奖演示、游戏、模拟和可复现测试可以使用 random;重置密码链接、会话令牌、验证码等应使用 secrets。
import secrets
# 生成 6 位数字验证码
code = "".join(str(secrets.randbelow(10)) for _ in range(6))
# 生成 URL 安全令牌
token = secrets.token_urlsafe(24)
# 验证码长度为 6,令牌长度大于 20
print(len(code), len(token) > 20)
预期结果(安全随机每次不同)是:
6 True
不要为了让安全测试“稳定”而给生成器固定种子。安全场景关注长度、字符集合和不可预测性,而不是某个固定输出。
SystemRandom 和 secrets 都使用操作系统提供的随机源。选择 API 时先问“攻击者能否因预测结果获利”,若答案是能,就不要用 random。
secrets 还提供 token_bytes、token_hex 和 token_urlsafe。令牌长度应按系统风险与存储格式确定,比较敏感令牌时使用 secrets.compare_digest 减少时序泄漏,而不是普通字符串等号承担全部安全设计。
| 对比项 | random |
secrets |
|---|---|---|
| 随机源 | 伪随机生成器 | 操作系统安全随机源 |
| 是否适合加密 | 不适合 | 适合 |
| 是否可固定种子 | 可以,用于测试 | 不应固定种子 |
| 典型场景 | 模拟、游戏、抽奖演示 | 密码、验证码、令牌 |
| 关注重点 | 可复现、分布 | 不可预测、长度、字符集 |
六、三个边界决定结果是否可信
第一,记录区间。random() 不含 1.0,randint(a,b) 包含 b,randrange 类似 range 不含 stop。
第二,区分有放回和无放回:choices 可重复抽取,sample 在同一次抽样中不重复,样本数超过总体会报错。
第三,不要遍历大量结果后凭肉眼判断“均匀”。需要验证分布时,统计频数并设置足够样本,同时接受合理波动。固定种子适合回归测试,不适合证明生成器具有真正随机性。
| 边界 | 正确理解 | 常见错误 |
|---|---|---|
random() 区间 |
[0.0, 1.0) |
以为会返回 1.0 |
randint(a, b) |
包含两端 | 当成 range 不含 b |
randrange(start, stop) |
不包含 stop | 与 randint 混用 |
choices |
有放回,可重复 | 以为结果不重复 |
sample |
无放回,不重复 | k 超过总体长度 |
| 固定种子 | 测试复现 | 用于安全凭据 |
如果出现每次启动结果都一样,检查是否反复设置同一个 seed;如果多线程性能下降,可为线程创建独立 Random 实例;如果候选集合来自用户输入,先处理空集合和重复项。Python 随机数避坑的核心,是让场景、范围和随机源保持一致。
七、常见异常与排错清单
| 现象 | 常见原因 | 修复方式 |
|---|---|---|
IndexError: Cannot choose from an empty sequence |
choice 传入空序列 |
先判断候选是否非空 |
ValueError: Sample larger than population |
sample 样本数超过总体 |
检查 k <= len(population) |
| 每次启动结果相同 | 反复设置同一个种子 | 安全场景不要固定种子 |
| 固定种子后结果仍变化 | 调用顺序或生成器实例不同 | 固定完整调用流程 |
| 集合不能索引 | choice 需要序列 |
先转成列表,并确认顺序 |
| 抽奖结果可预测 | 使用了普通 random |
改用 secrets 并记录规则 |
shuffle 返回 None |
原地修改列表 | 不要赋值回原变量 |
八、测试用例建议
| 测试输入 | 验证目标 |
|---|---|
random() 多次调用 |
值都在 [0.0, 1.0) |
randint(1, 6) 多次调用 |
值都在 1 到 6,包含两端 |
randrange(1, 6) 多次调用 |
值都在 1 到 5,不含 6 |
choice([]) |
抛 IndexError |
sample(pop, len(pop) + 1) |
抛 ValueError |
choices(pop, k) |
结果长度正确,允许重复 |
| 固定种子完整流程 | 输出可复现 |
secrets 生成令牌 |
长度正确,不固定种子 |

总结
三种常见 Python 随机数方法分别解决不同任务:
random()生成[0,1)小数;randint()生成包含两端的整数;choice()从非空序列抽一个元素。
固定种子让测试可复现,却不提升安全性。
下一次写随机逻辑时,先确定输出类型与区间,再判断是否需要安全随机,最后补上空序列、端点和重复抽样测试。只要涉及密码、验证码或令牌,直接选择 secrets,不要让普通模拟 API 承担安全职责。
延伸学习
- 需要系统练习语法,可跟着 Python3 入门课程 完成基础训练;
- 只想临时生成测试值,可使用 随机数生成工具,但不要把网页结果用于安全令牌;
- 想看安全字符串的业务案例,可读 Python随机密码示例,并把其中做法与
secrets边界对照。
常见问题
Q:randint(1,6) 可能得到 6 吗?
A:可以。randint 包含左右端点。需要 1 到 5 时应写 randint(1,5),或按 range 语义使用 randrange(1,6)。
Q:设置 seed 后结果为什么还是变了?
A:随机序列还取决于调用顺序和生成器实例。新增一次随机调用,后续所有值都会前移;测试应固定完整流程。
Q:choice 可以传集合 set 吗?
A:不建议,choice 需要支持整数索引的序列。先把数据整理成列表,并明确集合转列表后的顺序是否影响复现。
Q:抽奖程序能用 random 吗?
A:只有无安全和利益风险的演示适合。真实抽奖涉及公平、审计与防预测,需要安全随机源和完整规则记录。

免费 AI IDE



