None .. An html document created by ipypublish
outline: ipypublish.templates.outline_schemas/rst_outline.rst.j2 with segments: - nbsphinx-ipypublish-content: ipypublish sphinx content
6. Containers and loops¶
*** Basile Marchand (Materials Center - Mines ParisTech / CNRS / PSL University)** *
6.1. Containers — because a variable is good but a large package is better¶
6.1.1. In practice - a variable containing other variables¶
We have seen previously that we can easily with Python define variables of type number, boolean or character string. It is good but however it is far from being sufficient to be able to automate the tasks or to make numerical calculation. This is why we are now going to see the containers. As its name suggests, a container will be a variable in which we will store a set of variables. For example, to store all the notes you will have for the course project, I will need a list.
We will see that there is not only one type of container in Python but several, in Python that I will call basic there are four: * Tuples * Lists * Dictionaries * The sets
Within the framework of this course we will only be interested in the first three, the last having little interest for our applications. Obviously if several containers exist, it is because each one has its particularities and its limits that we are going to present.
6.1.2. Tuples¶
It is the first Python container and probably the most used. You have already used it without realizing it. In French we could translate the notion of tuple to that of tuple. It is therefore a set of values (homogeneous or not), that is to say that a tuple can store objects of different types.
The syntax for defining a tuple is as follows:
name_of_tuple = (value1, value2, ..., valueN)
To access the value of a tuple, all you have to do is use the operator [] with the index of the element you want to access between brackets.
Attention: In all Python containers the indices start at zero! That is, to access a first element of a tuple, you must request the element with index 0.
For example :
[1]:
un_tuple = (10, "une_string", 1.e-5, False)
print(un_tuple[0])
print(un_tuple[1])
print(un_tuple[2])
print(un_tuple[3])
10
une_string
1e-05
False
We can also extract sub-tuples by specifying between brackets i:j:s where: * i is the index of the first element that we want to retrieve * j is the index of the last element that we want to recover +1 * s is the step between each element that is extracted
If you don’t specify i by default Python takes i=0, if you don’t specify j it takes j=size of the tuple and if you don’t specify s it takes s = 1
[2]:
print(un_tuple)
print(un_tuple[1:3])
print(un_tuple[:3])
print(un_tuple[2:])
print(un_tuple[::2])
(10, 'une_string', 1e-05, False)
('une_string', 1e-05)
(10, 'une_string', 1e-05)
(1e-05, False)
(10, 1e-05)
Finally, if you want to know the size of a tuple, just use the command len
[3]:
print(un_tuple)
print(len(un_tuple))
(10, 'une_string', 1e-05, False)
4
Note: in this part we have not seen how to modify a value in a tuple and it is normal because it is not possible. One of the peculiarities of tuples is that they are immutable variables, that is to say that once defined it is no longer possible to modify it. The major benefit this can have is if you want to make sure that values will be constant throughout the execution of your code.
To have variables similar to tuples but which can be modified, you have to use lists.
6.1.3. The lists¶
Lists are the second basic usable container in Python. As with tuples, it is a set of values (homogeneous or not) which can subsequently be modified. The definition of a list in Python is done in a very similar way to the construction of a tuple except that one replaces the parentheses by brackets.
name_of_the_list = [value1, value2, ..., valueN]
Access to the value of a list is done in the same way as for tuples, ie using the operators []. Similarly, we can extract a sublist using the notation i:j:s.
[4]:
une_liste = [10, "une_string", 1.e-05, False]
print(type(une_liste))
<class 'list'>
[5]:
print(une_liste[0])
print(une_liste[2])
print(une_liste[2:])
print(une_liste[::2])
10
1e-05
[1e-05, False]
[10, 1e-05]
To know the size of a list, just use the same command as for tuples, namely len
[6]:
print(len(une_liste))
4
Among the other actions that can be carried out on the lists and that can make life easier, there is the use of the keyword in which allows to test if an element is in the list or not
[7]:
print( 3 in une_liste )
print( "une_string" in une_liste )
False
True
To access the index of an element in a list, just use the command index
[8]:
une_liste.index("une_string")
[8]:
1
Lists can also be concatenated using the + operator. It should be remembered that the sum of two lists does not return the sum term by term of the two lists but the concatenation.
[9]:
liste_a = [1,2,3]
liste_b = [4,5,6]
print( liste_a + liste_b )
[1, 2, 3, 4, 5, 6]
Finally we will see how to modify the values within a list. To do this, we use the operator [] once again, but it is followed by an assignment operation, i.e. it is followed by a = a value. You can also modify sublists using the notation i:j:s.
[10]:
print(une_liste)
[10, 'une_string', 1e-05, False]
[11]:
une_liste[0] = -1
print(une_liste)
[-1, 'une_string', 1e-05, False]
[12]:
une_liste[3] = [0,1]
print(une_liste)
[-1, 'une_string', 1e-05, [0, 1]]
[13]:
une_liste[:2] = [0,1]
print(une_liste)
[0, 1, 1e-05, [0, 1]]
However you may have noticed that for the moment we have only changed the values in the list without changing its size. To add elements to a list by enlarging it, you must use the functions:
append which adds an element at the end of the list
insert which adds an element at a given position
[14]:
print(une_liste)
[0, 1, 1e-05, [0, 1]]
[15]:
une_liste.append( 10000 )
print(une_liste)
[0, 1, 1e-05, [0, 1], 10000]
[16]:
help(une_liste.insert)
une_liste.insert(2, "new_item")
print(une_liste)
Help on built-in function insert:
insert(index, object, /) method of builtins.list instance
Insert object before index.
[0, 1, 'new_item', 1e-05, [0, 1], 10000]
And if you want to remove elements from a list, you can use the command remove or the keyword del for delete.
[17]:
une_liste.remove(1.e-5)
print(une_liste)
[0, 1, 'new_item', [0, 1], 10000]
[18]:
del une_liste[1]
print(une_liste)
[0, 'new_item', [0, 1], 10000]
Attention: You have to be very careful with one-thing lists, when you copy a list if you don’t do it the right way you won’t have the expected behavior and you will potentially take a very long time to find the source of the problem. This “problem” is linked to the fact that in Python everything is done by passage by reference, we will see later in the course what this means.
Drawing :
[19]:
liste_a = [1,2,3,4,5]
liste_b = liste_a ### On pense faire une copie de liste_a dans liste_b
liste_b[0] = 10
print(liste_b)
print(liste_a)
[10, 2, 3, 4, 5]
[10, 2, 3, 4, 5]
We then see that the modification we made in list b also has repercussions on list a. And this is quite normal, because if we look at the memory addresses of each of the list a and list b variables we will see that they are identical.
[20]:
print(hex(id(liste_a)))
print(hex(id(liste_b)))
0x7f58486c3e10
0x7f58486c3e10
Why this strange behavior? Because for Python when you write
list_b = list_a
It includes, created for me a variable named list b pointing to the same memory area as list a and therefore when you use the list variable list_b you actually access the same memory cell as list_a.
If you don’t want this behavior, you have to be a little more explicit and proceed as follows:
[21]:
liste_a = [1,2,3,4,5]
liste_b = liste_a.copy()
## ou bien
liste_b = liste_a[:]
liste_b[0] = 10
print(liste_b)
print(liste_a)
[10, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
6.1.4. Dictionaries¶
We will now see the last container natively available in Python that we will study in this course, namely dictionaries.
Dictionaries are quite different containers in lists and tuples in the sense that they are not ordered and there is no notion of index. Access to the elements is done using a key. Indeed the Python dictionary is based on doublets (key, value). Each key in a dictionary must be unique and of type int or string. The syntax for defining a dictionary is as follows:
un_dictionary = {key1: value, key2: value, ...}
The access to the values of a dictionary is done by using the operator [] to which one provides the key of the element which one wishes to recover.
[22]:
un_dict = {"cle1": 1, "cle2": 2, 1035: False}
print(un_dict)
print(un_dict["cle1"])
print(un_dict[1035])
{'cle1': 1, 'cle2': 2, 1035: False}
1
False
The interest of dictionaries is multiple and its applications are numerous. The modification of an element of the dictionary is done quite simply by making the element considered an assignment operation with the new value.
[23]:
print(un_dict)
un_dict["cle1"] = 18
print(un_dict)
{'cle1': 1, 'cle2': 2, 1035: False}
{'cle1': 18, 'cle2': 2, 1035: False}
If you want to add new entries to a dictionary it is very easy. It suffices to proceed commercial for the modification of a value since if the key which one gives does not exist it is automatically created.
[24]:
print(un_dict)
un_dict["new_key"] = "new_val"
print(un_dict)
{'cle1': 18, 'cle2': 2, 1035: False}
{'cle1': 18, 'cle2': 2, 1035: False, 'new_key': 'new_val'}
Finally to make your life easier with the dictionaries there are a few tips to know. For example to retrieve the list of keys in the dictionary
[25]:
print( un_dict.keys() )
dict_keys(['cle1', 'cle2', 1035, 'new_key'])
To retrieve all the values stored in the dictionary:
[26]:
print(un_dict.values())
dict_values([18, 2, False, 'new_val'])
And finally to retrieve all the key and value doublets in a list:
[27]:
print(un_dict.items())
dict_items([('cle1', 18), ('cle2', 2), (1035, False), ('new_key', 'new_val')])
The use of these methods can be accompanied by the keyword in for example:
[28]:
if "new_key" in un_dict.keys():
print( un_dict["new_key"] )
new_val
6.1.5. No more ? No Matlab-style vector matrices?¶
We have therefore just taken a tour of the main containers available natively in Python. The question you must certainly ask yourself is how I do to simulate with Python therefore manage matrices, vectors, … Indeed in native Python there is no notion of vectors or matrices as in Matlab which only manages that.
But no panic, however, because people did the job. We will see a little later that Python has a certain number of additional modules and that among these modules there is Numpy which defines the notion of vector and matrix.
6.2. Loops — or how to take advantage of computer stupidity¶
6.2.1. In what interest?¶
Much of computer programs require repetitive processing of data, usually stored in lists or tables. To perform these treatments, it is therefore necessary to have repeat commands available. Like all programming languages (to my knowledge) Python has two types of commands to repeat a set of statements: * The for loop which allows you to repeat a series of instructions N times. * The while loop which allows you to repeat a series of instructions as long as a certain condition is true.
6.2.2. Loops for¶
The loop, known as the for loop, allows you to repeat an operation N times with N a known integer before entering the loop. It is therefore a suitable loop when you know in advance the number of times you have to repeat the instruction block.
The Python syntax for the for loop is as follows:
for i in an _iterable:
instruction_ 1
instruction_2
Note: we find in the syntax of the for loop something similar to that of the if, namely a first line ending with the character : and then an indented instruction block.
You may notice that the first line involves what I call an iterable. These are Python objects that we can iterate on. You are no further ahead I know. In practice, this is a special object that allows you to browse all the elements contained automatically using a for loop among others. In the context of this course I will not go into detail about iterators, I will just give you a list of iterables that you can handle.
The first iterable, the simplest is a list. You can easily browse the elements of a list using the for loop. Indeed we can write the following code:
[29]:
ma_liste = [1,2,3,4,5]
for x in ma_liste:
print(x)
1
2
3
4
5
The same is possible with a tuple of course.
[30]:
mon_tuple = (1,2,3,4,5)
for x in mon_tuple:
print(x)
1
2
3
4
5
Sometimes we don’t need to write a loop to iterate through the elements of a list but just to repeat a given operation N times, with N a positive integer. In this case you must use the range command which will generate an iterable. The syntax of the range command is as follows:
range (A, B, S)
where the parameters are: * A: (int) the starting value of the iterable, by default A = 0* B: (int) the final value of the iterable +1, no default value * S: (int) the step between two iterated, by default S = 1
For example :
[31]:
for i in range(0,5): ## S=1 implicitement
print(i)
0
1
2
3
4
[32]:
for i in range(3): ## A=0, S=1 implicitement
print(i)
0
1
2
[33]:
for i in range(0,10,2):
print(i)
0
2
4
6
8
Then you could tell me why not use range (len (my_list)) to iterate through a list? Yes indeed it works as you can see below:
[34]:
for i in range(len(ma_liste)):
print(ma_liste[i])
for x in ma_liste:
print(x)
1
2
3
4
5
1
2
3
4
5
But this syntax is not recommended at all, it is even to be avoided for several reasons.
It’s heavy and ugly
It is not optimized. That is, if len (my _list) >> 1 your code will be slow.
If you are doing this to access both the value of your iterable and its index know that Python does everything for you. There is indeed the enumerate command whose syntax is as follows:
for i, x in enumerate(an_ iterable):
instruction
In practice, this makes it possible to write:
[35]:
for i,x in enumerate(ma_liste):
print("ma_liste[{}] = {}".format(i,x))
ma_liste[0] = 1
ma_liste[1] = 2
ma_liste[2] = 3
ma_liste[3] = 4
ma_liste[4] = 5
And finally if you want to browse several lists of the same sizes simultaneously there is also a trick, the zip command whose syntax is as follows:
for x, y, z in zip(list_x, list_y, list_z):
instruction
[36]:
list_x = [0,1,2,3,4]
list_y = [10,11,12,13,14]
list_z = [20,21,22,23,24]
for x,y,z in zip(list_x, list_y, list_z):
print("x={}, y={}, z={}".format(x, y, z))
x=0, y=10, z=20
x=1, y=11, z=21
x=2, y=12, z=22
x=3, y=13, z=23
x=4, y=14, z=24
Of course, it is quite possible to combine the zip and enumerate commands. However, pay attention to the syntax (placement of parentheses to the left of the in).
[37]:
for i,(x,y,z) in enumerate(zip(list_x, list_y, list_z)):
print("{} => x={}, y={}, z={}".format(i, x, y, z))
0 => x=0, y=10, z=20
1 => x=1, y=11, z=21
2 => x=2, y=12, z=22
3 => x=3, y=13, z=23
4 => x=4, y=14, z=24
6.2.3. The keywords break and continue¶
In the Python language there are two particular keywords intended to modify the behavior of a for loop (or while as we will see later) they are: * break: which allows to interrupt a loop prematurely * continue: which allows you to go to the next iteration without executing the code that follows the continue
Concretely the behavior of these two key words is the following one:
[38]:
ma_liste = [1,2,3,4,5]
for x in ma_liste:
if x == 3:
break
print("x = {}".format(x))
x = 1
x = 2
[39]:
for x in ma_liste:
if x == 3:
continue
print("x = {}".format(x))
x = 1
x = 2
x = 4
x = 5
6.2.4. A somewhat peculiar use of for — The “comprehension lists”¶
The for keyword can also be used in a slightly different form to construct what are called “comprehension lists”. The idea is to define is to fill a list in a single command line. The interest is of course to have a more designed code but also to have a syntax which is closer to that used in mathematics.
The Python syntax for defining a list comprehension is as follows:
name_of_my_list = [expression(x) for x in iterable if condition(x)]
The test with the if condition(x) is optional.
For example, let’s say I want to build the list defined by:
[40]:
la_liste = [ x**2 for x in range(10) ]
print(la_liste)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
And if we now want to add a condition, for example to build:
[41]:
la_liste = [ x**2 for x in range(10) if x**2 < 30 ]
print(la_liste)
[0, 1, 4, 9, 16, 25]
6.2.5. The while loop¶
The so-called while loop allows in computer science to repeat a series of instructions as long as a certain condition (specified by the developer when writing the loop) is verified. Therefore the while loop is suitable when you do not know in advance the number of times you will have to repeat the instruction block. A typical example are the methods of solving minimization problems where the convergence loop must not stop until the solution has been found.
The Python syntax for the while loop is as follows:
while condition:
instruction _1
instruction_ 2
The condition must necessarily be a boolean which will depend on what is done by the instructions. As long as this condition is true then the instructions are repeated and when the condition becomes false the loop stops.
For example :
[42]:
x = 1.1
n_iter = 0
while x<10:
x = x**2
n_iter +=1
print("Il y a eu {} ( => x={}) execution du bloc avant que la condition ne deviennent fausse".format(n_iter, x))
Il y a eu 5 ( => x=21.1137767453526) execution du bloc avant que la condition ne deviennent fausse
Of course, the stop condition of the while loop can be as complex as necessary and call on Python functions. For example
[43]:
ma_liste = [100,]
i = 0
while len(ma_liste)<100 and ma_liste[i]>0.001:
ma_liste.append( ma_liste[i] / (i+1) )
i += 1
print("La boucle s'est arrétée pour i={0} avec ma_liste[{0}]={1}".format(i, ma_liste[i]))
print( "La liste vaut {}".format(ma_liste))
La boucle s'est arrétée pour i=9 avec ma_liste[9]=0.00027557319223985895
La liste vaut [100, 100.0, 50.0, 16.666666666666668, 4.166666666666667, 0.8333333333333334, 0.1388888888888889, 0.019841269841269844, 0.0024801587301587305, 0.00027557319223985895]
In the same way as for lists, we can use the keyword break in a while loop to stop execution prematurely.
[44]:
ma_liste = [100,]
i = 0
while len(ma_liste)<100 and ma_liste[i]>0.001:
ma_liste.append( ma_liste[i] / (i+1) )
i += 1
if i>5: break
print("La boucle s'est arrétée pour i={0} avec ma_liste[{0}]={1}".format(i, ma_liste[i]))
print( "La liste vaut {}".format(ma_liste))
La boucle s'est arrétée pour i=6 avec ma_liste[6]=0.1388888888888889
La liste vaut [100, 100.0, 50.0, 16.666666666666668, 4.166666666666667, 0.8333333333333334, 0.1388888888888889]