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 分布返回浮点数。
其中 alpha 和 beta 都应大于零。
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.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 之间的比较方式,我们在进行以下比较时,会有不同的返回时间:
"password" == "a"
,在比较第一位时立刻返回False
"password" == "pa"
,在比较第三位时立刻返回False
通过测定返回的时间差,攻击者可以按位分别匹配比较的位数、而非将所有位数整体匹配。
简单来讲,对于每个字符有 n 种可能性,字符串长度为 m 的情况。如果 k 个字符的比较足以测量返回的时间差,这会使得攻击所需遍历的可能性数目从最高约n**m
种变为约(n**k)*(m/k)
种。
可以使用以下函数解决则会个问题:
secrets.compare_digest(a, b)
编辑输入两个字符串或两个 bytes。如果两个字符串(或 bytes)相等,则返回 True
。反之,返回 False
。
该函数不考虑高效性,无论 a 和 b 之间相匹配的位数有多少,它都会对每一位都进行匹配计算。这使得上面所述的时间差不存在。