跳转到内容

User:Xyy23330121/Python/随机数

来自维基学院


许多重要功能需要用随机数来实现。本页面将讲解几种生成随机数的方法。其中 random 模块的随机数不适合用作加密目的,而 secret 模块的随机数适合用作加密。

random模块:均匀分布

[编辑 | 编辑源代码]

随机字节

[编辑 | 编辑源代码]

random.randbytes(n)

[编辑 | 编辑源代码]

生成 n 个随机字节。

随机整数

[编辑 | 编辑源代码]

random.randrange(stop)

[编辑 | 编辑源代码]

random.randrange(start, stop[, step])

随机在 range(start, stop, step) 中选择一个元素,并返回。

由于参数解释方式的原因,在只有一个参数时,不应该使用关键字参数。比如 randrange(start = 10) 会被解释为 randrange(0, 10, 1)

random.randint(a, b)

[编辑 | 编辑源代码]

相当于 random.randrange(a, b+1)

random.getrandbits(k)

[编辑 | 编辑源代码]

返回具有 k 个随机比特位的非负整数。

随机浮点数

[编辑 | 编辑源代码]

random.random()

[编辑 | 编辑源代码]

返回一个随机的浮点数 x,满足 0 <= x < 1

random.uniform(a, b)

[编辑 | 编辑源代码]

返回在 a 和 b 之间的一个随机的浮点数。

其结果等同于 a + (b-a)*random.random(),是否包含 b 取决于浮点舍入误差。

从序列中随机选取对象

[编辑 | 编辑源代码]

random.choice(seq)

[编辑 | 编辑源代码]

从非空序列 seq 中,随机选取一个元素,并返回选取的元素。如果 seq 为空序列,则报错 IndexError。

random.choices(population, weights=None, *, cum_weights=None, k=1)

[编辑 | 编辑源代码]

从非空序列 population 中,有重复地选取元素 k 次,并返回选取得到的列表。如果 population 为空序列,则报错 IndexError。

weights 为权重序列。它表示 population 中每个元素的权重。该序列的长度应该与 population 相等。各个权重值应当为非负且有限的数值。且应当使用能与浮点数互相运算的类型(比如整数、浮点数、fractions.Fraction,不包括 decimal.Decimal)。

cum_weights 为累计权重序列。它以累积的方式表示元素权重,比如:

cum_weights 为[1,2,4] 与 weights 为 [1,1,2] 的效果是相同的。

在函数内部,权重序列会被转化为累计权重序列再进行计算。

cum_weights 不应和 weights 同时设置。如果同时设置 weights 和 cum_weights,则报错 TypeError。如果两者都没有被设置,则默认每个元素的权重相等。

即便使用相同的种子,不设置权重,该函数返回的序列,与 random.choice 函数反复 k 次得到的序列是不同的。这是因为 random.choice 默认使用整数运算算法来规避舍入误差,而该函数使用浮点运算。

random.sample(population, k, *, counts=None)

[编辑 | 编辑源代码]

从非空序列 population 中,无重复地选取元素 k 次,并返回选取得到的列表。如果 population 为空序列,则报错 IndexError。

counts 指定 population 中,元素出现的次数。它应当是长度等同于 population 长度的、全是整数的序列。

比如:random.sample([0,1,1,1],k=2) 等同于 random.sample([0,1],k=2,counts=[1,3])

若不指定 counts,相当于设 counts 为全 1 序列。如果 k > sum(counts),则报错 ValueError。

对于从一系列整数中选取样本的情况,使用 range 函数生成的 range 对象作为 population 会大量节省空间并提高计算速度。

随机打乱序列

[编辑 | 编辑源代码]

random.shuffle(x)

[编辑 | 编辑源代码]

将可变序列 x 打乱。类似 list.sort 方法,该函数会直接修改 x 的内容。

由于排列数可以随着 x 长度增加而快速增加——并超出随机数生成器的周期。因此,对于过长序列而言,其大多数排列永远不会被 random.shuffle 函数所产生。

若使用默认的 Mersenne Twister 随机数生成器,过长意味着序列长度大于 2080。

random模块:其它分布

[编辑 | 编辑源代码]

整数:二项式分布

[编辑 | 编辑源代码]

random.binomialvariate(n=1, p=0.5)

[编辑 | 编辑源代码]

返回 n 次独立实验,在每次实验成功率为 p 时,成功的次数。

浮点数分布

[编辑 | 编辑源代码]

random.triangular(low, high, mode)

[编辑 | 编辑源代码]

三角分布返回浮点数。

默认 (low, high, mode)(0, 1, 0.5)

random.batavariate(alpha, beta)

[编辑 | 编辑源代码]

beta 分布返回浮点数。

其中 alphabeta 都应大于零。

random.expovariate(lambd=1.0)

[编辑 | 编辑源代码]

指数分布返回浮点数。

其中 lambd 应是非零的。

random.gammavariate(alpha, beta)

[编辑 | 编辑源代码]

gamma 分布返回浮点数。

random.gauss(mu = 0.0, sigma=1.0)

[编辑 | 编辑源代码]

正态分布(也即高斯分布)返回浮点数。

如果两个线程同时调用此函数,它们可能返回相同的值。为避免这个问题,可以:

  • 让每个线程使用不同的随机数生成器实例
  • 为每一次调用加锁
  • 改用速度较慢,但是线程安全的 random.normalvariate 函数。

random.normalvariate(mu = 0.0, sigma=1.0)

[编辑 | 编辑源代码]

正态分布(也即高斯分布)返回浮点数。

random.lognormvariate(mu, sigma)

[编辑 | 编辑源代码]

对数正态分布返回浮点数。

random.vonmisesvariate(mu, kappa)

[编辑 | 编辑源代码]

冯·米塞斯分布返回浮点数。

random.paretovariate(alpha)

[编辑 | 编辑源代码]

帕累托分布返回浮点数。

random.weibullvariate(alpha, beta)

[编辑 | 编辑源代码]

威布尔分布返回浮点数。

random 模块:随机数生成器

[编辑 | 编辑源代码]

以上所述的 random 模块的函数都基于 random 模块提供的随机数生成器。

默认随机数生成器

[编辑 | 编辑源代码]

random 模块的几乎所有函数都依赖于默认随机数生成器 random.Random 的一个核心方法 random.Random().random()。默认随机数生成器使用高效的 梅森旋转算法 作为核心随机数生成器。但是,只要知道种子,梅森旋转算法的结果就是完全确定的。因此 random 模块的随机数不适合用作加密目的。

操作当前随机数生成器

[编辑 | 编辑源代码]

以下几个操作会更改当前使用的默认随机数生成器。

random.seed(a=None, version=2)

[编辑 | 编辑源代码]

初始化随机数生成器。a 为使用的种子,而 version 代表处理种子的方法。

如果 a 为 None,则使用操作系统提供的随机数,如果操作系统不提供随机数,则使用当前系统时间。

如果要指定 a,a 可以为整数、字符串、bytes 或 bytearray。其中整数不会被处理,并将直接作为种子使用。而字符串、bytes 和 bytearray 都会被处理:

  • 如果 version 为 2,则会将字符串、bytes 或 bytearray 对象转换为整数,并使用整数的所有位数作为种子。
  • 如果 version 为 1,相对于 version 为 2 的时候,如果用字符串做种子,可能的种子范围较小。不建议设置 version 为 1,应仅在需要从旧版本的 Python 再现随机序列时使用它。

random.getstate() 和 random.setstate(state)

[编辑 | 编辑源代码]

random.getstate() 会返回表示随机数生成器内部状态的元组。该元组可以传入 random.setstate(state) 函数来恢复状态。

随机数生成器对象

[编辑 | 编辑源代码]

random.Random([seed])

[编辑 | 编辑源代码]

random 模块使用的默认随机数生成器。

核心方法
[编辑 | 编辑源代码]

random.Random 的以下几个实例方法和核心随机数生成器有关。

seed(a = None, version=2)
[编辑 | 编辑源代码]

random.seed 函数调用的方法。

getstate()
[编辑 | 编辑源代码]

random.getstate 函数调用的方法。

setstate(state)
[编辑 | 编辑源代码]

random.setstate 函数调用的方法。

random.random 函数调用的方法。

getrandbits(k)
[编辑 | 编辑源代码]

random.getrandbits 函数调用的方法。

延申方法
[编辑 | 编辑源代码]

这里所述“延申方法”是基于“核心方法”的、random 模块的函数使用的方法。特别的,每个函数使用的方法都和函数同名。即:

  • 在随机数生成器内部状态相同的情况下,random.choice(seq) 的结果等同于 random.Random().choice(seq) 的结果。

由于前面章节的函数已经充分介绍过了。这里不再具体列出所有方法。

random.SystemRandom([seed])

[编辑 | 编辑源代码]

使用 os.urandom() 函数的类。该类型并非适用于所有系统、也不依赖于软件状态。其生成的随机数序列无法重现。

该类型提供的方法和上面的方法相同。其中,seed 方法没有任何作用。

自定义随机数生成器

[编辑 | 编辑源代码]

要自定义不同于默认梅森旋转算法的随机数生成器,应该自定义 random.Random 的子类,并修改其核心方法。

在使用自定义的生成器时,直接用继承自 random.Random 的“延申方法”即可。只要核心方法正确且恰当,延申方法的结果也应当符合预期。

secret 模块:安全随机数

[编辑 | 编辑源代码]

secret 模块用于生成高度加密的、安全的随机数。适用于管理密码、账户验证、安全凭据等机密数据。

随机数生成器

[编辑 | 编辑源代码]

class secrets.SystemRandom

[编辑 | 编辑源代码]

类似 random.SystemRandom,这是使用 os.urandom() 函数的类。

生成随机数

[编辑 | 编辑源代码]

secrets.choice(sequence)

[编辑 | 编辑源代码]

从非空序列 sequence 中随机选取一个元素,并返回选取的元素。

secrets.randbelow(n)

[编辑 | 编辑源代码]

返回一个随机整数。范围为

secrets.randbits(k)

[编辑 | 编辑源代码]

返回具有 k 个随机比特位的非负整数。

生成 Token

[编辑 | 编辑源代码]

secrets.token_bytes([nbytes=None])

[编辑 | 编辑源代码]

返回含 nbytes 个字节的随机 bytes 对象。如果 nbytes 为 None,则使用合理的默认值。

随着计算机能力的提升,保证安全所需的、随机字节数也会提升。因此,nbytes 的默认值在版本更新时会随时改变。

secrets.token_hex([nbytes=None])

[编辑 | 编辑源代码]

类似 secrets.token_bytes 函数,但返回的是对应的十六进制字符串。

secrets.token_urlsafe([nbytes=None])

[编辑 | 编辑源代码]

类似 secrets.token_bytes 函数,但返回的是对应的、Base64 编码的字符串。平均每个字节对应 1.3 个字符。

定时攻击

[编辑 | 编辑源代码]

对于 Python 默认的字符串之间和 bytes 之间的比较方式,我们在进行以下比较时,会有不同的返回时间:

  1. "password" == "a",在比较第一位时立刻返回 False
  2. "password" == "pa",在比较第三位时立刻返回 False

通过测定返回的时间差,攻击者可以按位分别匹配比较的位数、而非将所有位数整体匹配。

简单来讲,对于每个字符有 n 种可能性,字符串长度为 m 的情况。如果 k 个字符的比较足以测量返回的时间差,这会使得攻击所需遍历的可能性数目从最高约n**m种变为约(n**k)*(m/k)种。

可以使用以下函数解决则会个问题:

secrets.compare_digest(a, b)

[编辑 | 编辑源代码]

输入两个字符串或两个 bytes。如果两个字符串(或 bytes)相等,则返回 True。反之,返回 False

该函数不考虑高效性,无论 a 和 b 之间相匹配的位数有多少,它都会对每一位都进行匹配计算。这使得上面所述的时间差不存在。