Python中的
random
模块包含两个接口(类)的
伪随机数生成器(PRNGs)。您可以将其视为生成随机数的两种方式。
A note on the module secrets
.
The module secrets
does not implement any type of PRNG but provides helper functions(which is awesome because we don't have to write them ourselves) based on SystemRandom and os.urandom(which SystemRandom is based upon). The comments are mine:
from random import SystemRandom
_sysrand = SystemRandom()
randbits = _sysrand.getrandbits
choice = _sysrand.choice
def randbelow(exclusive_upper_bound):
...
return _sysrand._randbelow(exclusive_upper_bound)
def token_bytes(nbytes=None):
...
return os.urandom(nbytes)
def token_hex(nbytes=None):
...
return binascii.hexlify(token_bytes(nbytes)).decode('ascii')
def token_urlsafe(nbytes=None):
...
tok = token_bytes(nbytes)
return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')
Random.random()是如何工作的?
random.random()在'random.py'模块中的第749行(对我而言)进行定义。
_inst = Random()
...
random = _inst.random
类random.Random()
本身并没有定义random()
方法,而是继承了_random.Random()
(它定义了一个名为random()
的方法),这是一个名为Random()
的类,位于模块_random
中。
_random
(它是一个内置模块)的C
源代码可以在这里找到(实际上称为_randommodule.c
。请参见下面的说明)。
(历史上,如果一个模块叫做spam,包含其实现的C文件就被称为spammodule.c;如果模块名非常长,比如spammify,则模块名可以只是spammify.c。)
_random.Random.random()
(或random.random()
)方法在_randommodule.c
文件中定义为_random_Random_random_impl()
。
static PyObject *
_random_Random_random_impl(RandomObject *self)
{
uint32_t a=genrand_int32(self)>>5, b=genrand_int32(self)>>6;
return PyFloat_FromDouble((a*67108864.0+b)*(1.0/9007199254740992.0));
}
genrand_int32()
是由Mersenne Twister PRNG实现定义的函数,它返回一个4字节的数字。
SystemRandom().random()如何工作?
(我知道你没有要求SystemRandom(),但当我写这篇文章时还没有意识到)
我制作了这张图片作为我的回答概述(然而,我鼓励你阅读全部内容)
![Overview of SystemRandom's random() method](https://istack.dev59.com/iRCVV.webp)
SystemRandom().random()
定义在模块random.py
中。
...
def random(self):
"""Get the next random number in the range [0.0, 1.0)."""
return (int.from_bytes(_urandom(7), 'big') >> 3) * RECIP_BPF**strong text**
该函数使用模块
os.py
中定义的另一个函数
urandom()
。
from os import urandom as _urandom
os.py
模块本身并没有定义urandom()
函数,但是从一个内置模块中导入它。如果你使用的是POSIX OS,那么os.py
将会导入posix
内置模块;如果你使用的是Windows NT OS,那么os.py
将会导入nt
内置模块。这些模块包含了urandom()
的定义。
if 'posix' in _names:
name = 'posix'
linesep = '\n'
from posix import *
或者
elif 'nt' in _names:
name = 'nt'
linesep = '\r\n'
from nt import *
"posix"和"nt"是内置模块,因此它们没有"__file__"属性。
深入
源代码:
POSIX
"urandom()"在"posixmodule.c"中定义为
os_urandom_impl(),它调用
_PyOS_URandom()。
static PyObject *
os_urandom_impl(PyObject *module, Py_ssize_t size)
{
...
bytes = PyBytes_FromStringAndSize(NULL, size);
...
result = _PyOS_URandom(PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
...
return bytes
}
_PyOS_URandom()
定义在 bootstrap_hash.c
文件中,然后调用 pyurandom()。
int
_PyOS_URandom(void *buffer, Py_ssize_t size)
{
return pyurandom(buffer, size, 1, 1);
}
static int
pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
{
...
return dev_urandom(buffer, size, raise);
...
}
dev_urandom
定义在 bootstrap_hash.c
文件中,然后使用 /dev/urandom
目录获取随机字节。
static int
dev_urandom(char *buffer, Py_ssize_t size, int raise)
{
...
fd = _Py_open("/dev/urandom", O_RDONLY);
...
do {
n = _Py_read(fd, buffer, (size_t)size);
...
} while (0 < size);
...
}
Windows NT
这看起来有点奇怪(我也这么认为),但是posixmodule.c
文件也用于NT系统,以下是文件开头的一段引用(注释)
该文件还用于Windows NT / MS-Win。 在这种情况下,该模块实际上将自己称为“nt”,而不是“posix”,并且一些函数未实现或以不同方式实现。源代码假定对于Windows NT,宏“MS_WINDOWS”已定义,而与使用的编译器无关。 不同的编译器定义自己的特征测试宏,例如'_MSC_VER'。
对于Windows NT,函数调用链与POSIX相同,直到pyurandom()
函数。
static int
pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
{
...
#ifdef MS_WINDOWS
return win32_urandom((unsigned char *)buffer, size, raise);
#else
...
}
win32_urandom()
定义在 bootstrap_hash.c
文件中,然后调用 CryptGenRandom()
。
static int
win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
{
...
if (!CryptGenRandom(hCryptProv, chunk, buffer))
{
...
}
...
return 0;
}
CryptGenRandom()
在wincrypt.h
文件中声明,在Advapi32.lib
和Advapi32.dll
库中定义(这些文件由Microsoft提供)