真正意义上的随机数(或者随机事件)在某次产生过程中是按照实验过程中表现的分布概率随机产生的,其结果是不可预测的,是不可见的。而计算机中的随机函数是按照一定算法模拟产生的,其结果是确定的,是可见的。我们可以这样认为这个可预见的结果其出现的概率是100%。所以用计算机随机函数所产生的“随机数”并不随机,是伪随机数。
计算机的伪随机数是由随机种子根据一定的计算方法计算出来的数值。所以,只要计算方法一定,随机种子一定,那么产生的随机数就是固定的。
只要用户或第三方不设置随机种子,那么在默认情况下随机种子来自系统时钟。Python 的这个库在底层使用通用的算法,经过长久的考验,可靠性没得说,但绝对不能用于密码相关的功能。如果要用于密码相关的功能,请使用 secret 模块,secrets 模块可用于生成高加密强度的随机数,适应管理密码、账户验证、安全凭据和相关机密数据管理的需要。
random
random.seed()
初始化随机数生成器。如果 a 被省略或为 None
,则使用当前系统时间。 如果操作系统提供随机源,则使用它们而不是系统时间。如果 a 是 int 类型,则直接使用。随机种子最直接的感受是:只要计算方法一定,随机种子一定,那么产生的随机数就是固定的(这也是它不能用于密码相关功能的原因之一),例如:
>>> random.seed(10)
>>> random.randint(0, 10)
9
>>> random.randint(0, 10)
0
>>> random.randint(0, 10)
6
>>> random.seed(10)
>>> random.randint(0, 10)
9
>>> random.randint(0, 10)
0
>>> random.randint(0, 10)
6
>>> random.seed()
>>> random.randint(0, 10)
2
>>> random.randint(0, 10)
10
>>> random.randint(0, 10)
8
可以很明显地观察到,在设置了相同的随机种子以后,使用 random.randint(0, 10) 生成的随机数序列和之前是一样的。
random.getstate()
返回捕获生成器当前内部状态的对象。 这个对象可以传递给 setstate() 来恢复状态。
random.setstate(state)
传入一个先前利用getstate方法获得的状态对象,使得生成器恢复到这个状态。
random.getrandbits(k)
返回一个不大于 k 位的 Python 整数(十进制),比如 k=10,则结果在0~2^10之间的整数(转换成二进制的字符串以后,长度不超过 k)。
>>> random.getrandbits(5)
18
直接看整数的结果可能看不出来位长度方面的差异,用 bin 转换成二进制的时候就直观多了:
>>> bin(random.getrandbits(5))[2:]
'1101'
>>> bin(random.getrandbits(5))[2:]
'1111'
>>> bin(random.getrandbits(5))[2:]
'10010'
random.randrange(stop)
从 range(stop) 返回一个随机选择的元素,这相当于 choice(range(stop))
>>> random.randrange(10)
3
>>> random.randrange(10)
7
>>> random.randrange(10)
4
random.randrange(start, stop[, step])
从 range(start, stop, step)
返回一个随机选择的元素。 这相当于 choice(range(start, stop, step))
>>> random.randrange(5, 10, 2)
9
>>> random.randrange(5, 10, 2)
5
>>> random.randrange(5, 10, 2)
5
random.randint(a, b)
返回随机整数 N 满足 a <= N <= b
。相当于 randrange(a, b+1)
。
>>> random.randint(5, 10)
5
>>> random.randint(5, 10)
6
>>> random.randint(5, 10)
10
random.choice(seq)
从非空序列 seq 返回一个随机元素。 如果 seq 为空,则引发 IndexError。
>>> random.choice([])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python3.6/random.py", line 260, in choice
raise IndexError('Cannot choose from an empty sequence') from None
IndexError: Cannot choose from an empty sequence
>>> random.choice([1, 0])
0
>>> random.choice([1, 0])
1
random.choices(population, weights=None, *, cum_weights=None, k=1)
从 population 中选择替换,返回大小为 k 的元素列表。 如果 population 为空,则引发 IndexError。同时可以设置 population 的权重 weights。
>>>
>>> random.choices([1, 2, 3, 4, 5], [0, 1, 2, 3, 4], k=2)
[4, 2]
>>> random.choices([1, 2, 3, 4, 5], [0, 1, 2, 3, 4], k=2)
[4, 5]
random.shuffle(x)
将序列 x 随机打乱位置(洗牌)。
>>> x = [1, 2, 3, 4, 5]
>>> random.shuffle(x)
>>> x
[3, 2, 1, 5, 4]
random.sample(population, k)
返回从总体序列或集合中选择的唯一元素的 k 长度列表。 用于无重复的随机抽样。
>>> random.sample([1, 2, 3, 4, 5], 3)
[5, 4, 3]
>>> random.sample([1, 2, 3, 4, 5], 4)
[4, 1, 5, 2]
>>> random.sample([1, 2, 3, 4, 5], 5)
[4, 5, 1, 2, 3]
random.random()
返回 [0.0, 1.0) 范围内的下一个随机浮点数。
>>> random.random()
0.5386670484231432
random.uniform(a, b)
返回一个随机浮点数 N ,当 a <= b
时 a <= N <= b
,当 b < a
时 b <= N <= a
。
取决于等式 a + (b-a) * random()
中的浮点舍入,终点 b
可以包括或不包括在该范围内。
>>> random.uniform(5, 4)
4.5101042412090635
>>> random.uniform(5, 4)
4.834203559467793
>>> random.uniform(5, 4)
4.558653182546412
>>> random.uniform(4, 5)
4.9686781985609425
>>> random.uniform(4, 5)
4.415173693217833
>>> random.uniform(4, 5)
4.035317829787831
常见的字符串常量
>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.digits
'0123456789'
>>> string.hexdigits
'0123456789abcdefABCDEF'
>>> string.octdigits
'01234567'
>>> string.printable
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
>>> string.whitespace
' \t\n\r\x0b\x0c'
secrets
secrets.choice(sequence)
返回一个从非空序列中随机选取的元素。使用方式和 random.choice() 类似。
import string
import secrets
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(8))
print(password) # Xaa43wQ0
secrets.randbelow(n)
返回 [0, n) 范围内的随机整数。使用方式和 random.randrange(stop) 类似。
import secrets
password = "".join([str(secrets.randbelow(9)) for i in range(8)])
print(password) # 40228257
secrets.randbits(k)
返回 k 个随机比特位的整数。 与 random.getrandbits(k) 的使用类似。
import secrets
random_8_bit = secrets.randbits(8)
print(bin(random_8_bit)[2:].zfill(8)) # 11110010
secrets.compare_digest(a, b)
用于安全地比较两个加密哈希值是否相同,即使攻击者可能通过修改内存中的数据来干扰比较结果。该函数通过比较两个哈希值的二进制内容来确保安全性,适用于密码学场景。
直接比较字符串或二进制数据可能暴露比较过程的时间差异,从而被攻击者利用,secrets.compare_digest(a, b) 则是通过固定时间比较,避免攻击者通过响应时间差异猜测正确值,从而避免时序攻击。
import time
import secrets
def unsafe_compare(a, b):
return a == b # 普通比较,时间可能随匹配长度变化
def safe_compare(a, b):
return secrets.compare_digest(a, b) # # 固定时间比较
prefix = secrets.token_hex(10000000)
correct = prefix + "secret12356789"
user_input = prefix + "secret124567890"
start = time.time()
print(unsafe_compare(correct, user_input))
end = time.time()
print("Unsafe compare time:", end - start)
start = time.time()
print(safe_compare(correct, user_input))
end = time.time()
print("Safe compare time:", end - start)
# False
# Unsafe compare time: 0.0
# False
# Safe compare time: 0.011017084121704102
token生成
import secrets
print(secrets.token_bytes(16))
print(secrets.token_hex())
print(secrets.token_urlsafe(nbytes=32))
# b'\xf5\xb9&\xcb\t+%\xf5Lp\x8f\x8a\xbb\x0f\xaf\x83'
# 5ae2a01430d39ea8656be4b9a536bafc6c37205bdc3216207f2152eee6576597
# ynSF3wysM9aTL0w_sOkio5F8GViAebGTC8wbo_nijEg