davit.tech

Generating random things using Python

03, May 2020 - 8 min read

Introduction ☜

In this article, we are going to generate random values things using Python. The list of contents is below.


Contents ☜


What is random? ☜

random - happening, done or chosen by chance rather than according to a plan

In our programs we often generate random numbers, characters, hashes, etc... But are they truly random with taking the fact that computers are deterministic? or how do computers produce random values?

random player

In computer science, there are several random number generators and the most common one is a pseudorandom number generator(PRNG) which generates numbers that look random. The disadvantage of PRNG is that the numbers that it generates are deterministic and can be reproduced if the state of the PRNG is known.

Another type of generator is the hardware random number generator (HRNG) or true random number generator (TRNG). It is a device that generates random numbers from a physical process, rather than by means of an algorithm(like PRNG). The disadvantage of HRNG is that it relies on an external device and not very fast.

hardware random number generator

However, carefully designed cryptographically secure pseudo-random number generators (CSPRNG) also exist, with special features specifically designed for use in cryptography.

It's important to note that pseudorandom number generators should not be used for security purposes, cryptographically secure pseudorandom number generators should be used instead.

In this article, we will go over some examples of generating both predictable and cryptographically secure random values using Python.


random module in Python ☜

We will use Python's random module a lot. Some facts about the module:

  • Module implements pseudorandom number generators.
  • Almost all module functions depend on the basic function random().
  • Python uses the Mersenne Twister as the core generator.
  • The pseudo-random generators of this module should not be used for security purposes.

random() function generates a random float uniformly in the semi-open range [0.0, 1.0).

>>> import random
>>> random.random()
0.7320183271158612

random holds a state that is being used in the generation and it is being changed "every time" we use random(). There is a getstate() function that returns an object capturing the current internal state of the generator. This object can be passed to setstate(state) later to restore the state.

>>> random.getstate()
(3, (2147483648, 3487414761, 1383097352, 2873053358, 653991483, 14937...

If the state is the same then the generated numbers will be equal.

>>> state = random.getstate()  # <--- keeping the current state
>>> first_random = random.random()  # <--- generating a random float
>>> first_random
0.8147563911510902
>>> state == random.getstate()  # <--- checking the state change
False
>>> second_random = random.random()
>>> second_random
0.4963458268419496  # <--- state is different so the number is also different
>>> first_random == second_random
False
>>> random.setstate(state)  # <--- changing state back to the first one
>>> third_random = random.random()
>>> third_random
0.8147563911510902
>>> first_random == third_random
True  # <--- same number was generated
>>> forth_random = random.random()
>>> second_random == forth_random
True  # <--- continuance is repeated

As you can see, the result of the random() function depends on the current state of the random.


How to get a random boolean in Python? ☜

There are many ways to get a random boolean in Python.

Comparing the result of random() with a half:

>>> random() < 0.5

Using choice(seq) function which returns a random element from a non-empty sequence:

>>> random.choice([True, False])

Using getrandbits(k) function which returns a Python integer with k random bits. If k=1 we will get 0 or 1 and can coerce it to boolean:

>>> bool(random.getrandbits(1))

or:

>>> random.getrandbits(1) == 1

How to get a random letter in Python? ☜

string module comes to help us with this problem. ascii_letters holds all letters that we can choose from:

>>> import string
>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

That could be a sequence for the choice(seq) function to choose from:

>>> random.choice(string.ascii_letters)
'H'

Another way to achieve this is by using the combination of chr(i) and randrange(start, stop[, step]).

chr(i) - returns the string representing a character whose Unicode code point is the integer i.

randrange(start, stop[, step]) - returns a randomly selected element from range(start, stop, step).

ascii for the letter 'a' is 97 and there are 26 letters in the alphabet, so the range will be from 97 to 97 + 26:

>>> chr(random.randrange(97, 97 + 26))
'p'

How to get N random items from a sequence in Python? ☜

We already did this in the previous example. Strings are also sequences and we got a random letter from a string. But now we want to get more than one randomly selected item from a sequence. choices() can help us with that:

>>> population = [1, 2, 3, 4, 5, 6, 7]
>>> random.choices(population, k=3)  # <--- getting 3 randomly selected items from the list
[5, 1, 1]  # <--- integer 1 appears more than once

Note that choices could be duplicated. In the next section, we will show an example of getting N length of unique elements chosen from a sequence.


How to get N random and unique items from a sequence in Python? ☜

Using sample(population, k) function, which returns a k length list of unique elements chosen from the population sequence or set:

>>> population = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> random.sample(population, k=5)
[2, 1, 5, 3, 6]

Note that the sequence that we feed to sample function doesn't contain any duplicate elements, but if it does sample could return a list with duplicate values in it:

>>> population = [1, 1, 1, 2, 2, 2, 5]
>>> random.sample(population, k=3)
[1, 2, 1]

We can convert the population to a set to have a strictly unique collection of randomly selected items in the end:

>>> population = [1, 1, 1, 2, 2, 2, 5]
>>> random.sample(set(population), k=3)
[5, 2, 1]

How to get a random number between integer range in Python? ☜

Let's say we want to generate a random integer between 0 and 9. Using randint() function is the easiest way in this case:

>>> random.randint(0, 9)
3

or using randrange():

>>> random.randrange(0, 9 + 1)
5

also, we can create a list of integers between 0 and 9 and use the choice() function passing that sequence:

>>> integers = [n for n in range(10)]  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> random.choice(integers)
8

How to get a random number between float range in Python? ☜

Using uniform(a, b) function, which returns a random floating point number N such that a <= N <= b for a <= b and b <= N <= a for b < a:

>>> random.uniform(1.1, 8.2)
4.443894738120874

How to get a random string in Python? ☜

string module contains collection of upper case and lower case letters and also digits. We can use them to generate k-length random strings.

using only uppercase letters:

>>> import string
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> ''.join(random.choices(string.ascii_uppercase, k=k))
'CGLLZLK'

using only uppercase and lowercase letters with digits:

>>> string.ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'
>>> string.digits
'0123456789'
>>> collection = string.ascii_uppercase + string.ascii_lowercase + string.digits
>>> ''.join(random.choices(collection, k=k))
'0fN7SRr'

As we noted at the beginning, these ways to generate strings are not secure. In the next section, we will see the way to generate secure strings.


How to get a cryptographically secure random string in Python? ☜

The secrets module(new in version 3.6) is used for generating cryptographically strong random values suitable for managing data such as passwords, account authentication, security tokens, and related secrets.

>>> import secrets
>>> ''.join([secrets.choice(collection) for _ in range(k)])
'Wzhs4i8'

Data generated using secrets module should be unpredictable enough for cryptographic applications, though its exact quality depends on the OS implementation.


How to get a random row through SQLAlchemy? ☜

Most databases have the ability to order rows by a random function:

>>> from sqlalchemy.sql.expression import func, select
>>> select.order_by(func.random())  # <--- PostgreSQL, SQLite
>>> select.order_by(func.rand())  # <--- MySQL
>>> select.order_by('dbms_random.value')  # <--- Oracle

and we need to limit the number of results to 1.


How to get a random row through Django ORM? ☜

Ordering also works here. Passing ? to order_by() method will randomly order rows and all we need is to select only one row:

>>> MyModel.objects.order_by('?').first()

Note: order_by('?') queries may be expensive and slow, depending on the database backend you’re using.

If you have millions of rows, getting the count of rows and using it to generate a random limit and offset will be more efficient:

>>> from django.db.models.aggregates import Count
>>> count = MyModel.objects.aggregate(count=Count('id')]['count']
>>> random_index = random.randint(0, count - 2)
>>> row = MyModel.objects.all()[random_index:random_index + 1]

The disadvantage of this approach is, it's 2 SQL queries and if the count changes in between, it might be possible to get an out of bounds error.

However, it will execute faster compared with database ordering in some cases.


Conclusion ☜

There are many other ways to generate random values using Python. We tried to put here the most common ones. If you have any interesting solutions or ideas to generate random values, feel free to comment them below.

This might interest you.