The random
module in python contains two interfaces(classes) of pseudorandom number generators(PRNGs). You can view it as two ways to generate random numbers.
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() #secrets._sysrand
randbits = _sysrand.getrandbits #secrets.randbits
choice = _sysrand.choice #secrets.choice
def randbelow(exclusive_upper_bound): #secrets.randbelow
...
return _sysrand._randbelow(exclusive_upper_bound) #uses SystemRandom
def token_bytes(nbytes=None): #secrets.token_bytes
...
return os.urandom(nbytes)
def token_hex(nbytes=None): #secrets.token_hex(uses token_bytes())
...
return binascii.hexlify(token_bytes(nbytes)).decode('ascii')
def token_urlsafe(nbytes=None): # secrets.token_urlsafe(uses token_bytes())
...
tok = token_bytes(nbytes)
return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')
How does Random.random() work?
random.random() is defined in the 'random.py' module on line 749(for me)
_inst = Random()
...
random = _inst.random
The class random.Random()
does not define the random()
method per se but inherits _random.Random()
(which does define a method called random()
), which is a class called Random()
located at the module _random
.
The C
source code of the _random
(it is a built-in module) module can be found here(it is actually called _randommodule.c
. See explanation below)
(Historically, if a module is called spam, the C file containing its
implementation is called spammodule.c; if the module name is very
long, like spammify, the module name can be just spammify.c.)
The _random.Random.random()
(or random.random()
) method is defined as _random_Random_random_impl()
in the _randommodule.c
file.
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()
is a function defined by the Mersenne Twister PRNG implementation which returns a 4-byte number.
How does SystemRandom().random() work?
(I know you didn't ask for SystemRandom(), but at the time I wrote this I hadn't realized)
I've made this image as an overview of my answer(However, I do encourage you to read it all)

SystemRandom().random()
is defined in the module 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**
The function uses another function called urandom() defined in the module os.py
from os import urandom as _urandom
The os.py
module does not define the function urandom()
itself but imports it from a built-in module. os.py
will import the posix
built-in module if you are on a POSIX OS or the nt
built-in module if you are on a Windows NT OS. These modules contain the definition for urandom().
if 'posix' in _names:
name = 'posix'
linesep = '\n'
from posix import *
OR
elif 'nt' in _names:
name = 'nt'
linesep = '\r\n'
from nt import *
posix
and nt
are built-in modules, so they don't have the
__file__
attribute.
POSIX
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()
is defined in the bootstrap_hash.c
file which then calls pyurandom()
int
_PyOS_URandom(void *buffer, Py_ssize_t size)
{
return pyurandom(buffer, size, 1, 1);
}
pyurandom()
is defined in the bootstrap_hash.c
file which then calls dev_urandom().
static int
pyurandom(void *buffer, Py_ssize_t size, int blocking, int raise)
{
...
return dev_urandom(buffer, size, raise);
...
}
dev_urandom
is defined in the bootstrap_hash.c
file which then uses the /dev/urandom
directory to get random bytes.
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
It may look a bit weird(I thought that as well) but the posixmodule.c
file is also used for the NT systems, here is a quote(comment) from the beginning of the file
This file is also used for Windows NT/MS-Win. In that case the
module actually calls itself 'nt', not 'posix', and a few functions
are either unimplemented or implemented differently. The source
assumes that for Windows NT, the macro 'MS_WINDOWS' is defined
independent of the compiler used. Different compilers define their
own feature test macro, e.g. '_MSC_VER'.
For Windows NT the function call chain is the same as for POSIX until the pyurandom() function
pyurandom()
is defined in the bootstrap_hash.c
file which then calls win32_urandom().
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()
is defined in the bootstrap_hash.c
file which then calls CryptGenRandom()
.
static int
win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
{
...
if (!CryptGenRandom(hCryptProv, chunk, buffer))
{
...
}
...
return 0;
}
CryptGenRandom()
is declared in the wincrypt.h
file and defined in the Advapi32.lib
and Advapi32.dll
libraries(These files are provided by Microsoft)