Python : Révision des outils de base grâce à Jupyter notebook

Ce notebook est proposé dans le cadre d'une préparation au Cours de mathématiques & informatique en B.C.P.S.T.2 - Externat des enfants nantais proposé par J. Laurentin. On pourra, si on le souhaite, trouver des compléments notamment sur le programme officiel en visitant le site de la classe mathsinfo-bcpst2-eden.fr

1. Prise en main

Le Notebook d'IPython est un environnement intéractif pour écrire et faire tourner du code Python.
Des cellules de code, précédées d'un In [] permettent de rentrer des lignes de codes comme vous avez pu le faire jusqu'à présent dans la fenêtre de commande de Pyzo.
A titre d'exemple, placez votre curseur sur la ligne ci-dessous et appuyez sur la touche <enter>. Vérifiez que l'instruction n'a pas été exécutée mais en revanche un saut de ligne a été inséré...

Pour exécuter le code Python d'une cellule, il vous faut, au choix, taper <maj-enter> ou cliquer sur le bouton [lecture] de la barre des tâches ci-dessus (ou encore utiliser le menu Cell>Run and Select Below...).
Il suffit d'essayer :

In [1]:
a,b = 1,5
print('a*b vaut',a*b)
a*b vaut 5

Il est toujours possible d'effacer une cellule grâce aux ciseaux du bandeau situé en haut de page et en créer une nouvelle grâce au symbole '+'.
Mais c'est le plus souvent inutile car il est possible de relancer une ligne autant de fois qu'on le souhaite après l'avoir modifiée.
Par exemple, modifiez la ligne de code In [1] pour initialiser la variable b à 5 et afficher la valeur de a+b.
vous devriez voir afficher 'a+b vaut 6'.

D'un point de vue pratique, on notera que toutes les fonctionnalités d'Ipython sont ici opérationnelles.
On peut par exemple retrouver le chemin du dossier dans lequel on travaille :

In [2]:
pwd
Out[2]:
'C:\\Users\\Jérôme\\Documents\\BCPST2\\Jupyter'

On peut aussi importer les bibliothèques de son choix.
Par exemple :

In [3]:
from math import *
import numpy as np

On peut désormais faire :

In [4]:
x = 2
y = exp(x)-1
In [5]:
print(x,'|',y)
2 | 6.38905609893065
In [6]:
X = np.linspace(0,1,10)
In [7]:
print(X)
[ 0.          0.11111111  0.22222222  0.33333333  0.44444444  0.55555556
  0.66666667  0.77777778  0.88888889  1.        ]

Il est aussi possible d'écrire des fonctions plus complexes.
Par exemple pour la suite de Fibonacci définie par $u_0=1=u_1$ et $u_{n+2}=u_{n+1}+u_n$, $\forall n\geq 0$
on calculera et on affichera ses dix premiers termes en écrivant :

In [8]:
a,b=1,1 # initialisation de deux premiers termes
print(a,b,end = " ")
n=1
while n<11 :
    a,b = b,a+b
    print(b,end = " ")
    n = n+1
1 1 2 3 5 8 13 21 34 55 89 144 

Attention Vous noterez que tous les termes de la suite ont été affichés sur une seule ligne.
Ce résultat est obtenu grâce à l'argument end = " " fourni à la fonction print().
Supprimez cet argument et vérifiez que les termes sont affichés les uns en dessous des autres.

2. Quelques éléments sur Python, langage Orienté Objet

Avec Python, tout est objet : données, fonctions, modules...

Un objet :

  • possède une identité (à savoir une adresse mémoire);
  • possède un type : un objet est l’instanciation d’une classe qui définit son type (par exemple : int, float, str);
  • contient des données (par exemple, si c'est un objet numérique, cette donnée est sa valeur).

Un objet est référencé par un identificateur :

  • on trouvera également "référence", "nom", "étiquette", comme synonymes d'indentificateur;
In [9]:
a = 5 # a est l'identificateur et 5 est une expression évaluée comme objet.
In [10]:
# il est possible d'afficher l'identité et le type de l'objet référencé :
print(id(a)) # identité
print(type(a)) # type
1409942128
<class 'int'>
In [11]:
b = a # deuxième nom (étiquette ou référence) pour l'entier 5
print(id(b))
print(type(b))
1409942128
<class 'int'>
In [12]:
c = 5.0 # nouvel identificateur qui pointe cette fois vers 5.0
In [13]:
type(c)
Out[13]:
float
In [14]:
print(id(a),id(b),id(c))
1409942128 1409942128 4395697064
In [15]:
a == b # égalité des valeurs des deux objets
Out[15]:
True
In [16]:
a == c # égalité des valeurs
Out[16]:
True
In [17]:
a is c # test l'égalité des identités
Out[17]:
False
In [18]:
a is b # test l'égalité des identités
Out[18]:
True

On retiendra que a, b et c ont même valeur mais que c se distingue de a et b car son identité et son type son distincts.

Attention ! Tous les objets ne sont pas modifiables !

Un objet dont le contenu peut être changé est dit mutable (non mutable dans le cas contraire).
A titre d'exemple une chaîne de caractère est un objet non mutable tandis qu'une liste est un objet mutable.

In [19]:
T = 'Feci est un texte non modifiable'
print(T)
Feci est un texte non modifiable
In [20]:
T[0]='C'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-9794c11e21e5> in <module>()
----> 1 T[0]='C'

TypeError: 'str' object does not support item assignment
In [21]:
L = [12,-5.0,'oups'];L
Out[21]:
[12, -5.0, 'oups']
In [22]:
L[2]=3;L
Out[22]:
[12, -5.0, 3]
In [23]:
print(L)
[12, -5.0, 3]

Remarque importante sur les copies d'objets qui peuvent donner lieu à des erreurs difficiles à déceler :

In [24]:
M = L
print(M)
[12, -5.0, 3]
In [25]:
M[1]=60 # modification de la liste M
print(M)
[12, 60, 3]

La question est de savoir si la liste L aussi a été modifiée...

In [26]:
L
Out[26]:
[12, 60, 3]
In [27]:
L is M # L et M ont la même identité. Ils pointent vers le même objet et il est normal que L soit modifiée également.
Out[27]:
True

Il existe deux solutions pour faire la copie d'un objet : La copie superficielle ou la copie profonde .
Nous verrons ça à la fin du paragraphe 3.2 Modification d'une liste

3. Manipulation des listes :

Les listes représentent un type de données composées extrêment utiles sous Python.
On peut les voir comme une collection d'éléments séparés par des virgules, l'ensemble étant enfermé dans des crochets.
Les éléments peuvent être de type très différents. Par exemple :

In [28]:
L1 = [1,2,3.5,'7',-2.4,'test']
In [29]:
print(L1)
[1, 2, 3.5, '7', -2.4, 'test']

3.1 Accès aux éléments d'une liste :

Il est possible d'accéder à n'importe quel élément d'une liste en indiquant entre crochets l'index numérique qui correspond à la position de l'élément.
Essayez successivement dans la ligne ci-dessous : L1[2], L1[0], L1[5], L1[-1], L1[-1][1]

In [30]:
# Obtenir un élément de la liste :
L1[2]
Out[30]:
3.5

On obtient la longueur d'une liste ou d'une chaîne en faisant appel à la fonction len() :

In [31]:
n1 = len(L1)
print('la longueur de L1 vaut : ',n1)
print('la longueur de la chaîne de caractère "test" vaut : ',len(L1[-1]))
la longueur de L1 vaut :  6
la longueur de la chaîne de caractère "test" vaut :  4

Il est aussi possible de savoir si un élément est dans une liste :

In [32]:
3 in L1
Out[32]:
False
In [33]:
u = 3.5
if u in L1:
    print(u,' est dans L1')
3.5  est dans L1

On notera qu'une liste peut donc est formée de plusieurs types d'éléments.

  1. Les type integer (par exemple L1[0] et L1[1] sont des entiers).
  2. Les type float pour floating point number (L1[2] est un "nombre réel" ou "nombre à virgule flottante).
  3. Les type string (L1[3] et L1[5] sont des chaînes de caractères).

On obiendra le type d'un élement en faisant appel à la fonction type().
Par exemple on essaira ci-dessous type(L1), type(L1[0]), type(L1[2]) ou encore type(L1[3]) :

In [34]:
type(L1)
Out[34]:
list

Remarque : Dans le cas de L1[3] on vérifiera qu'il s'agit bien d'un caractère et non d'un entier !

Cela étant, il est possible d'accéder à bien mieux qu'à un seul élément d'une liste.
Il est en effet possible d'extraire n'importe quelle sous-liste et ce dans l'ordre de votre choix. Commençons par extraire les trois premiers éléments de L1 :

In [35]:
print(L1[0:3])
print(L1[:3])
[1, 2, 3.5]
[1, 2, 3.5]
In [36]:
print('L1 vaut : ',L1)
print(L1[1:])
print(L1[1:-1])
print(L1[1:4:2]) # index de 1 à 4 (exclus) par pas de 2.
print(L1[4:1:-1]) # index de 4 à 1 (exclus) par pas de -1.
print(L1[::-1]) # Affichage inversé de tout L1.
L1 vaut :  [1, 2, 3.5, '7', -2.4, 'test']
[2, 3.5, '7', -2.4, 'test']
[2, 3.5, '7', -2.4]
[2, '7']
[-2.4, '7', 3.5]
['test', -2.4, '7', 3.5, 2, 1]

A vous de jouer en créant à la suite de ce texte une nouvelle ligne de code (cliquer sur le symbole '+' dans le bandeau en haut de page) au sein de laquelle vous initialisez une liste L2 de votre choix et en s'entraînant à accéder à n'importe laquelle des sous-listes possibles.

Attention : Respecter l'affectation de ce nom (L2) à la liste que vous allez créer, sans quoi les indications du paragraphe 3.2 ne coincideront plus avec la liste L1 sur laquelle nous continuons de travailler !
Souvenez-vous qu'il est toujours possible de supprimer une ou des cellules qui ont été créées en utilisant 'Edit -> Delete Cells' ou bien la 'paire de ciseaux' des icônes ci-dessus.
Il est aussi possible de réinitialiser l'ensemble des cellules, en allant dans l'onglet 'Kernel' et en exécutant 'Restart & Clear Output'.

3.2. Modification d'une liste :

Nous rappelons que les listes sont des objets mutables .
Si on connaît l'index d'un élément, il est facile de le changer ou de le remplacer.
Par exemple :

In [37]:
print(L1)
L1[1] = L1[1]+12
print(L1)
[1, 2, 3.5, '7', -2.4, 'test']
[1, 14, 3.5, '7', -2.4, 'test']

Vous avez dû obtenir en deuxième ligne : [1, 14, 3.5, '7', -2.4, 'test']

Il est aussi possible de modifier le type des éléments de la liste grâce à des "built-in function", qu'on appellera aussi des méthodes ou fonction primitives. On trouvera parmis celles-ci int(), float(), complex(), str() ou encore list(), set().
Par exemple, pour remplacer le caractère "7" par l'entier "7" on fera :

In [38]:
L1[3]=int(L1[3]) # on écrira float(L1[3]) pour le transformer en type float.
print(L1)
[1, 14, 3.5, 7, -2.4, 'test']
In [39]:
type(L1[3])
Out[39]:
int

Exemple d'application de ces méthodes de changement de type (d'après Programmation en Python pour les mathématiques, par A. Casamayou-Boucau, P. Chauvin et G. Connan, (Dunod, 2012), p.38) :
Calculer $u_n=\cfrac{C_n}{n(n+1)}$ où $C_n$ désigne le nombre de chiffres intervant dans l'écriture décimale de $n$.

In [40]:
n = 53
print('u_',str(n),' vaut :',len(str(n))/(n*(n+1)))
u_ 53  vaut : 0.0006988120195667365

Plus généralement, il est particulièrement important de savoir manipuler une liste pour insérer, supprimer un élement, ou encore concaténer des listes.
Utilisez la ligne de code ci-dessous pour tester des méthodes particulièrement efficaces dont voici quelques exemples :

In [41]:
L1.append(14) # ajout de 6 en fin de liste
print(L1)
L1 = L1+[14] # autre méthode possible...
print(L1)
[1, 14, 3.5, 7, -2.4, 'test', 14]
[1, 14, 3.5, 7, -2.4, 'test', 14, 14]
In [42]:
L1.insert(3,54) # insertion de x=54 à la position i=3
print(L1)
[1, 14, 3.5, 54, 7, -2.4, 'test', 14, 14]
In [43]:
L1.index(54) # retourne l'index de la première occurence de x=54
Out[43]:
3
In [44]:
L1.count(14) # dénombre les x=14 présents dans L1 - essayer avec d'autres valeurs...
Out[44]:
3
In [45]:
del L1[6:] # suppression des termes indexés à partir de 7
print(L1)
[1, 14, 3.5, 54, 7, -2.4]
In [46]:
L1.sort() # tri croissant de L1
print(L1)
L1.sort(reverse = True)
print(L1) # tri décroissant
[-2.4, 1, 3.5, 7, 14, 54]
[54, 14, 7, 3.5, 1, -2.4]

Attention On rappelle que les listes sont des objets mutables. En particulier toute méthode appliquée à une liste ne créé pas un objet nouveau mais la modifie. Vos données d'origine sont perdues !
Aussi, dans le cas du tri d'une liste, si on ne souhaite pas perdre la liste de départ, on préfèrera L1_triee = sorted(L1) qui fait une copie triée de L1 sans la modifier.

Voyons maintenant les moyens de faire la copie d'une liste - la copie superficielle puis la copie profonde :

In [47]:
L = [1,[2,3]]
M = list(L) # copie superficielle: M est un nouvel objet `list` initialisé par L
M[0]=10
print(L,M)
M[1][0]=20
print(L,M)
[1, [2, 3]] [10, [2, 3]]
[1, [20, 3]] [10, [20, 3]]

Si tout se passe bien pour M[0], le problème pour M[1][0] vient du fait que M[1] pointe vers une même liste que L[1]...
Reprenons :

In [48]:
L = [1,[2,3]]
from copy import deepcopy
M = deepcopy(L) # copie profonde : M est une copie complète de l'objet référencé par L
M[0]=10
print(L,M)
M[1][0]=20
print(L,M)
[1, [2, 3]] [10, [2, 3]]
[1, [2, 3]] [10, [20, 3]]

3.3 Création de listes particulières :

Création d'une liste de nombres à l'aide de la fonction range() :
Cette fonction génère par défaut une séquence de nombres entiers de valeurs croissantes à partir de 0.
Cette séquence peut s'utiliser directement (par exemple pour une boucle POUR) ou bien être transformée en liste :

In [49]:
list(range(10))
Out[49]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [50]:
for k in range(10):
    print(k**2,end = " ")
0 1 4 9 16 25 36 49 64 81 

Mais bien d'autres possibilités s'offrent à vous :

In [51]:
list(range(2,12))
Out[51]:
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
In [52]:
list(range(2,12,2))
Out[52]:
[2, 4, 6, 8, 10]
In [53]:
list(range(7,-5,-1))
Out[53]:
[7, 6, 5, 4, 3, 2, 1, 0, -1, -2, -3, -4]
In [54]:
[0]*10
Out[54]:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [55]:
[4,8,'bip']*3
Out[55]:
[4, 8, 'bip', 4, 8, 'bip', 4, 8, 'bip']
In [56]:
list(range(5))*2
Out[56]:
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4]

On peut aussi créer des listes "en compréhension" :

In [57]:
L = [k**2 for k in range(11)]
print(L)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
In [58]:
L2 = [k**2 for k in range(11) if k%2 == 0]
print(L2)
L3 = [k**2 for k in range(11) if (k > 4) and (k%2 == 0)]
print(L3)
[0, 4, 16, 36, 64, 100]
[36, 64, 100]

Et même créer des listes de listes, utiles pour constituer des tableaux ou des matrices :

In [ ]:
L4 = [[i**2+j for i in range(3)] for j in range(2)]
print(L4)
In [ ]:
print('la première ligne vaut : ',L4[0])
print('la seconde ligne vaut : ',L4[1])
print('le deuxième terme de la première ligne vaut : ',L4[0][1])

Si on possède deux listes de même longueur, il est possible de les associer pour former des couples (appelés aussi tupples) :

In [ ]:
Couleur = ['carreau','coeur','pique','treffle']
Hauteur = ['Roi','Dame','Valet','As']
for (c,h) in zip(Couleur,Hauteur):
    print(h,' de ',c)

ou encore, si on souhaite faire le produit scalaire de deux vecteurs $u$ et $v$ :

In [ ]:
u = [1,0,2]
v = [-1,1,3]
print(sum(x*y for x,y in zip(u,v)))