How exactly does random.random() work in python?
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.
- Random uses the Mersenne Twister PRNG. It is not cryptographically secure
- SystemRandom uses either the /dev/urandom file on POSIX systems or the CryptGenRandom() function on Windows NT systems. Both are Cryptographically secure 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() #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)
Naming convention for python modules written in C/C++
(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
andnt
are built-in modules, so they don't have the__file__
attribute.
Diving in the source code:
POSIX
urandom()
is defined in theposixmodule.c
as os_urandom_impl() which calls _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()
is defined in thebootstrap_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 thebootstrap_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 thebootstrap_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 fileThis 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 thebootstrap_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 thebootstrap_hash.c
file which then callsCryptGenRandom()
.
static int
win32_urandom(unsigned char *buffer, Py_ssize_t size, int raise)
{
...
if (!CryptGenRandom(hCryptProv, chunk, buffer))
{
...
}
...
return 0;
}
CryptGenRandom()
is declared in thewincrypt.h
file and defined in theAdvapi32.lib
andAdvapi32.dll
libraries(These files are provided by Microsoft)
random.random()
is actually defined here:
random = _inst.random
However, it is just a reference to C implementation.
Here is a quote from the source:
General notes on the underlying Mersenne Twister core generator:
- The period is 2**19937-1.
- It is one of the most extensively tested generators in existence.
- The random() method is implemented in C, executes in a single Python step, and is, therefore, threadsafe.
You probably want to look at the article on Mersenne Twister. To speak briefly, the state of the generator is not the same as "previous number", it is much more complicated thing. So you are wrong in «…pseudo-random number generators work by performing some operation on a value. Generally this value is the previous number generated by the generator».
As for SystemRandom.random()
, it is in a way unrelated to random.random()
. It is possible in Python that function with the same name imported from different modules are different, so you can't rely on function's name here.