 ## Intro to the Python Random Module

### Introduction

Even for someone not interested in computer programming, the usefulness of generating random numbers in certain circumstances is something obvious. In most board games we throw dice to generate an unpredictable number that defines the player's next move. Also, we can all agree that playing any card game would be pointless without a proper shuffle between rounds.

But random numbers are not only important in relatively trivial fields like entertainment or gambling. They're especially crucial in the field of cryptography. In order to ensure safe transmission of data, every time a secure connection is necessary, a random key has to be generated. Many different kinds of electronic communication use this kind of security. It's very important for the key to be difficult to guess - the best way to ensure that is by making it random since the moment someone guesses the key, they are able to decipher the message - and the communication is not secure anymore.

### True Randomness vs Pseudo-randomness

Random numbers can be obtained as a result of applying methods called random number generators (RNG), which can be divided into two categories: true random-number generators (TRNGs – also called hardware random number generators) and pseudo-random number generators (PRNGS).

#### True Random Number Generators

The true random number generators are methods which derive the randomness, or unpredictability, from unpredictable aspects of physical processes. These methods do not directly produce numbers, but rather states, that can then be interpreted as numbers – this is why they are usually called random event generators (REGs). Some of them, which use macroscopic events, are commonly known – methods like throwing dice, flipping coins or shuffling cards.

These true random number generators often use more complicated physical phenomena. Some of them, like radioactive decay, thermal noise or radio noise, derive their unpredictability from peculiarities of quantum mechanics. Other methods use the unpredictability of atmospheric noise or even the behavior of lava lamps.

#### Pseudo Random Number Generators

The truth is, very often generating numbers that are actually, truly random, is not necessary. In many cases, all we need are sets of numbers that seem random. This kind of data can be derived from pseudo-random number generators. These are algorithms, that use a tiny portion of information (called a seed) and then apply complicated mathematical formulas in order to generate deterministic sets of numbers resembling truly random sets. The seed may be a value derived from a true random number generator, or another source, like the system's clock or the current datetime.

Running the generator multiple times using the same seed will result in the same output every time. The resulting numbers are mostly unrecognizable from numbers derived from true random number generators, although there actually are some hidden regularities in their distribution. Still, for many applications, this kind of deterministic pseudorandomness is absolutely sufficient.

### Python Random Module

Python, obviously offers a super easy-to-use toolkit to handle random numbers. A module, for some reason called random, implements a pseudo-random number generator, and contains methods that let us directly solve many different programming issues where randomness comes into play.

The random module is based on Marsenne Twister - a very popular algorithm, which is the default pseudo-random number generator not only for Python, but also for many other popular software systems such as Microsoft Excel, MATLAB, R, or PHP. Its important advantages include permissive licensing, random-likeness confirmed by many statistical tests, and relatively high speed compared to other PRNGs.

### The random() Method

The most important method of the random module is the random() method. Most of the other functions depend on it. The random() method generates a random float in range (0.0, 1.0).

>>> import random
>>> random.random()
0.8474337369372327


### The seed() Method

If we don't set a seed for our pseudo-random number generation, the default seed is the current system time. However, we can set the exact value of the seed manually - which comes in handy especially if we want to replicate our pseudo-random results in the future. For this purpose, we can use the random.seed() method.

>>> random.seed(5)
>>> random.random()
0.6229016948897019
>>> random.random()
0.7417869892607294
>>> random.random()
0.7951935655656966
>>> random.seed(5)
>>> random.random()
0.6229016948897019


The random.seed() method influences all methods of the random module that we use after calling it. In the example shown above, we set the seed to 5 and then execute the random.random() function multiple times. It is important to note that the user-defined seed is used only the first time another random method is executed - after that, the seeds for the following methods are modified using the previously generated random values.

This lets Python come up with new numbers every time. But still, after re-setting the seed using the random.seed() method, we can reproduce the exact same sequence of pseudo-random numbers at any time. This is very useful for things like running tests. If you give the same seed every time you run a test that uses one of random's methods then you will still be able to know what the output should be for the tests.

### Other Examples of the random Module

The random.random() method, which gives us a random float value from a certain range, would be enough even for an inexperienced Python developer to design any kind of random-based manipulations around it. You can probably imagine throwing in an if or two to write a function that randomly draws value from a list or returns a random integer instead of a float. Well, the random module lets us take care of those tasks automatically as well. Below, I show a couple of cool methods that simplify common randomized operations. You can get to know the entire potential of the random module in Python's official documentation.

>>> random.randint(1,10)
4


The random.randint() method takes two arguments describing the range from which the method draws a random integer.

>>> random.randrange(2,10,2)
2
>>> random.randrange(2,10,2)
4
>>> random.randrange(2,10,2)
8
>>> random.randrange(2,10,2)
6


In the script above, the random.randrange() method is similar to random.randint() but it also lets us define the third argument, which is the step point of the defined range. In the example above, we only require even numbers from a range between 2 and 10.

>>> cards = ['ace_spades','10_hearts','3_diamonds','king_hearts']
>>> random.choice(cards)
'10_hearts'


In the above script, the random.choice() method picks a random element from a list.

>>> cards = ['ace_spades','10_hearts','3_diamonds','king_hearts']
>>> random.shuffle(cards)
>>> print(cards)

In the previuos script, the random.shuffle() method shuffles a list of elements. It is important to note that it alters the list in-place - which means it returns None and actually modifies our cards variable. 