Python生成随机数的3种方法:random、randint、choice与secrets安全边界

编程狮 2026-09-18 16:36:25 浏览数 (27)
反馈

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

生成随机数3种方法附边界

本文基于 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)shufflesamplechoices。不要因为它们都在同一模块就认为语义一致:

函数 行为 是否原地修改 是否允许重复
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

不要为了让安全测试“稳定”而给生成器固定种子。安全场景关注长度、字符集合和不可预测性,而不是某个固定输出。

SystemRandomsecrets 都使用操作系统提供的随机源。选择 API 时先问“攻击者能否因预测结果获利”,若答案是能,就不要用 random

secrets 还提供 token_bytestoken_hextoken_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 生成随机数的三种方法

总结

三种常见 Python 随机数方法分别解决不同任务:

  • random() 生成 [0,1) 小数;
  • randint() 生成包含两端的整数;
  • choice() 从非空序列抽一个元素。

固定种子让测试可复现,却不提升安全性。

下一次写随机逻辑时,先确定输出类型与区间,再判断是否需要安全随机,最后补上空序列、端点和重复抽样测试。只要涉及密码、验证码或令牌,直接选择 secrets,不要让普通模拟 API 承担安全职责。

延伸学习

  1. 需要系统练习语法,可跟着 Python3 入门课程 完成基础训练;
  2. 只想临时生成测试值,可使用 随机数生成工具,但不要把网页结果用于安全令牌;
  3. 想看安全字符串的业务案例,可读 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:只有无安全和利益风险的演示适合。真实抽奖涉及公平、审计与防预测,需要安全随机源和完整规则记录。

0 人点赞