Python 生成随机数主要依赖标准库中的 random 模块和 secrets 模块,科学计算场景还会使用 NumPy 提供的随机数工具。三者定位不同:random 模块适合模拟、游戏、抽样等普通需求;secrets 模块面向密码、令牌等安全敏感场景;NumPy 则擅长批量生成数组形式的随机数。理解它们之间的差异,是避免把不安全的随机数用于安全场景的关键。

一、random模块:基础随机数生成
random 模块基于梅森旋转算法实现,是一个伪随机数生成器。它生成的数字序列由初始种子决定,但普通使用时种子来自系统时间或操作系统提供的熵,因此每次运行通常会得到不同结果。处理一般随机需求时,random 模块的接口足够直观。
最常用的函数包括 random()、randint()、uniform()、randrange()、choice()、shuffle()、sample()。random() 返回 0 到 1 之间的浮点数,randint(a, b) 返回包含两端点的整数,uniform(a, b) 返回指定区间的浮点数。选择元素时,choice() 从序列中随机取一个元素,sample() 可以无放回地抽取多个元素,shuffle() 则原地打乱列表。下面这段代码展示了这些基础用法。
import random # 0 到 1 之间的浮点数 print(random.random()) # 1 到 10 之间的整数,包含两端 print(random.randint(1, 10)) # 1.5 到 3.5 之间的浮点数 print(random.uniform(1.5, 3.5)) # 从 0 到 100、步长为 5 的序列中随机选择 print(random.randrange(0, 101, 5)) items = ['apple', 'banana', 'cherry', 'date'] print(random.choice(items)) # 随机选择一个元素 random.shuffle(items) # 原地打乱列表 print(items) print(random.sample(items, 2)) # 随机抽取 2 个不重复元素
需要特别注意的是,randint(a, b) 包含右端点,这与 Python 切片习惯不同;如果在循环中重复调用 random.choice,要清楚它是有放回抽样,可能出现重复元素。若需要无重复抽样,直接使用 random.sample 更合适。
二、随机种子与结果可重复
伪随机数生成器的核心特点是:只要种子相同,生成的随机数序列就完全一致。random.seed() 可以显式设置种子,这在调试、测试和复现实验时非常有用。相同种子会保证同一进程内、相同调用顺序下得到相同结果。
下面示例两次使用 seed(42),可以看到输出序列相同。如果去掉 seed,每次运行结果通常不同。
import random random.seed(42) print(random.randint(1, 100)) print(random.random()) random.seed(42) print(random.randint(1, 100)) print(random.random())
如果没有显式调用 seed,Python 会在导入 random 模块时从系统熵源获取种子。这个熵源在大多数操作系统中来自 os.urandom,质量较好。需要注意的是,不要为了提高可复现性而把 random.seed 当作安全机制,固定种子意味着攻击者可以预测后续随机数。对于多线程程序,random 模块的全局函数共享内部状态,多个线程同时调用可能导致结果不可控,必要时可为每个线程创建独立的 random.Random() 实例。
三、secrets模块:密码学安全的随机数
secrets 模块是 Python 标准库中用于生成密码学安全随机数的模块,底层基于 os.urandom,而 os.urandom 又使用操作系统提供的加密安全随机源。它生成的随机数不可预测,适合生成密码重置令牌、会话 ID、验证码和加密盐值。
secrets 的常用函数与 random 类似,但更精简:secrets.randbelow(n) 返回 0 到 n 之间的整数,secrets.choice(seq) 从序列中安全选择元素,secrets.token_hex(nbytes) 生成十六进制令牌,secrets.token_urlsafe(nbytes) 生成适合放在 URL 中的安全字符串。下面是实际示例。
import secrets # 生成 16 字节的十六进制令牌 token = secrets.token_hex(16) print(token) # 生成适合 URL 的安全字符串 url_safe = secrets.token_urlsafe(24) print(url_safe) # 从列表中安全选择元素 print(secrets.choice(['red', 'green', 'blue'])) # 生成 0 到 99 之间的安全随机整数 print(secrets.randbelow(100))
不要把 random 模块用于安全令牌。即使设置了随机种子,random 的输出仍可预测。安全的替代方案是 secrets.token_urlsafe 或使用标准库 uuid 模块的 uuid4,但 uuid4 底层也依赖 os.urandom。对于验证码、短令牌等场景,secrets.randbelow 和 secrets.choice 就足够,关键是不要为了简短而牺牲随机空间,建议令牌长度不低于 16 字节。
四、NumPy中的随机数生成
如果需要在数组或矩阵中批量生成随机数,NumPy 提供的随机数工具比 Python 标准库更高效。新版本推荐使用 np.random.default_rng() 创建 Generator 对象,而不是直接调用旧版全局函数 np.random.rand 等。Generator 使用更现代的 PCG64 算法,性能更好,接口也更清晰。
import numpy as np rng = np.random.default_rng(12345) # 生成形状为 (3,) 的随机浮点数组 print(rng.random(3)) # 生成 0 到 9 之间的 5 个随机整数 print(rng.integers(0, 10, size=5)) # 从给定列表中随机选择,生成形状为 (4,) 的数组 print(rng.choice(['a', 'b', 'c'], size=4))
Generator 支持 random、integers、choice、normal、permutation 等方法,可以一次生成指定形状的数组。与标准库 random 一样,default_rng 接受整数种子时结果可复现。需要注意,NumPy 随机数生成器与 Python random 模块互不影响,两者维护各自的内部状态。如果项目同时使用标准库和 NumPy,不要假设它们共享随机源。
五、常见误区与选择建议
第一个常见误区是认为 random.randint 足够安全。实际上它只是伪随机,攻击者可以在获得足够多输出后预测后续值。任何涉及密码、令牌、加密密钥的场景都必须改用 secrets 或操作系统级随机源。
第二个误区是滥用 random.sample 处理大数据。random.sample 对序列长度和抽样数量较敏感,如果从超大列表中抽取大量样本,内存和时间开销可能较高。此时可考虑使用 random.randrange 配合去重,或在 NumPy 中使用 generator.choice(..., replace=False)。
选择建议如下:
- 普通模拟、游戏、随机排序:使用
random模块。 - 需要结果可复现:设置
random.seed或np.random.default_rng(seed)。 - 密码、令牌、验证码:使用
secrets模块。 - 批量数值计算:使用
numpy.random.Generator。
理解不同随机数工具的安全边界和性能特点,能帮助开发者在正确场景下做出选择,避免安全隐患,也能提升程序执行效率。