En Python, nous savons manipuler :
On va compléter la panoplie en montrant :
Il est souvent pratique de vouloir regrouper un certain nombre de valeurs « dans le même paquet » :
Quelles solutions ?
Python propose le type de donnée de tuple (ou n-uplet). On écrit simplement les expressions en utilisant des parenthèses et des virgules.
>>> point = (1.5, -3.19)
>>> point
(1.5, -3.19)
>>> point[0]
1.5
>>> point[1]
-3.19
>>> x, y = point
>>> x + y
-1.69
>>> point[0] = 2.2
Traceback (most recent call last):
File "", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t = (1, 2) + (3, 4, 5)
>>> len(t)
5
>>> t
(1, 2, 3, 4, 5)
>>> (42, ) * 5
(42, 42, 42, 42, 42)
>>> (42) * 5
210
#On représente des points par un couple (x, y)
from math import sqrt
def add_point(p1, p2):
return (p1[0] + p2[0], p1[1] + p2[1])
def mult_point(p, k):
return (p1[0] * k, p1[1] * k)
def norm_point(p):
x, y = p
return sqrt(x ** 2 + y ** 2)
…
En Python, la constante None est une valeur spéciale indiquant une « absence de valeur ». Peut être utilisée dans plusieurs cas :
def moyenne(tab):
if len(tab) == 0:
return None
total = 0
for i in range(len(tab)):
total += tab[i]
return total / len(tab)
moyenne([1, 2, 3, 4]) #renvoie 2.5
moyenne([]) #renvoie None
On peut tester qu'une valeur est None avec l'opérateur d'égalité (==). Si une fonction peut renvoyer None, alors il faut toujours tester son résultat avant de l'utiliser, sinon on risque des erreurs:
moy = moyenne(tab)
if moy == None:
print("Erreur, tableau vide !")
else:
print("Moyenne au carré:", moy * moy)
Sans le test, on aurait calculé None * None dans le cas d'un tableau vide, ce qui aurait provoqué une erreur.
On a souvent besoin d'associer des clés à des valeurs. Lorsque les clés sont des entiers consécutifs et commençant par 0, on peut utiliser des tableaux :
jmois = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
…
Mais comment faire lorsque l'on souhaite utiliser d'autres types de clés ?
Par exemple : "janvier" → 31, … "décembre" → 31 ?
C'est une situation très très courante
Python propose le type de donnée
de dictionnaire.
Il est similaire aux tableaux, mais les indices peuvent être (presque)
n'importe quel type de données Python.
On définit un dictionnaire vide par des { }
On peut pré-remplir le dictionnaire avec la notation
{ k1:v1, …,
kv:vn }.
>>> jours = { 'lundi':1, 'mardi':2, 'mercredi':3 }
>>> jours['mardi']
2
>>> jours
{'lundi':1, 'mardi':2, 'mercredi':3}
>>> jours['jeudi'] = 4
>>> jours
{'lundi':1, 'mardi':2, 'mercredi':3, 'jeudi' : 4}
>>> jours['jeudi'] = 42
>>> jours
{'lundi':1, 'mardi':2, 'mercredi':3, 'jeudi' : 42}
Accéder à une clé inexistante est similaire à faire un accès invalide dans un tableau. L'opérateur in permet de tester si une clé est dans le dictionnaire :
>>> jours['toto']
Traceback (most recent call last):
File "", line 1, in <module>
KeyError: 'toto'
>>> 'mardi' in jours
True
>>> 'toto' in jours
>>> jours
False
On peut utiliser d'autres types de valeur pour les clés (entiers, booléens). L'utilisation la plus fréquente reste les chaînes de caractères.
Attention, comme les tableaux, les dictionnaires sont mutables!
On a vu la boucle for sur des entiers, en utilisant la fonction range:
for i in range(len(tab)):
v = tab[i]
…
La boucle for est plus générique que cela. Elle permet d'itérer sur les éléments d'une collection.
for v in [42, 56, -1, 28]:
print(v) #affiche 42, puis 56, puis -1, puis 28
Cela fonctionne avec tous les types de « collections » :
for v in "ABCDEFG":
print(v) #affiche A, puis B, puis C, …
On peut itérer sur les dictionnaires. Plus précisément, on peut itérer sur les clés d'un dictionnaire :
mois = { 'janvier' : 31, 'février': 28, …, 'décembre' : 31 }
for m in mois:
print(m, mois[m]) #affiche janvier 31, puis février 28, …
Dans quel ordre les clés sont-elle considérées ?
On ne fera pas d'hypothèse sur l'ordre des clés
On peut trier un tableau avec la fonction prédéfinie sorted
>>> tab = [10,-1, 100, 13, -5]
>>> sorted(tab)
[-5, -1, 10, 13, 100]
>>> tab
[10,-1, 100, 13, -5 ]
Cette fonction renvoie une copie triée du tableau, le
tableau original reste inchangé.
Sur un dictionnaire, cette fonction renvoie
le tableau trié des clés :
>>> dico = { 'd' : 50, 'a' : 40, 'b' : 100 }
>>> sorted(dico)
['a', 'b', 'd']
On peut donc parcourir un dictionnaire dans l'ordre croissant des clés avec :
for k in sorted(dico):
…
Comme dans d'autres langages, les instructions break et continue peuvent être utilisées dans des boucles for ou while pour :
for i in range(10):
if i == 7:
break
elif i % 2 == 0:
continue
print(i)
Le code ci-dessus affiche 1, 3 et 5.
On appelle modèle mémoire la façon dont les valeurs d'un langage sont représentés comme une séquence d'octet dans l'ordinateur.
En Python, toutes les valeurs sont représentées par l'adresse mémoire (i.e. un entier 64 bits ou 8 octets) d'un bloc alloué dans le tas (une zone particulière de la mémoire).
a = "bonjour, ça va ?"
b = a
« En interne », a contient l'adresse d'un bloc mémoire
contenant "bonjour, ça va ?".
L'affectation b place dans la variable b une
copie de l'adresse et non pas une copie du bloc !
En mémoire :
adresse valeur
tas
0x0128
0xf10
a
0x0158
0xf10
b
0x0f10
Bonjour,
0x0f18
ça va ?_
Il y a donc une différence en mémoire entre :
a = "bonjour, ça va ?"
b = a
et
Il y a donc une différence en mémoire entre :
a = "bonjour, ça va ?"
b = "bonjour, ça va ?"
En mémoire :
a = "Bonjour, ça va ?"
b = a
adresse valeur
tas
0x0128
0xf10
a
0x0158
0xf10
b
0x0f10
Bonjour,
0x0f18
ça va ?_
a = "Bonjour, ça va ?"
b = "Bonjour"
b = b + ", ça va ?"
adresse valeur
tas
0x0128
0xf10
a
0x0158
0xf80
b
0x0f10
Bonjour,
0x0f18
ça va ?_
0x0f80
Bonjour,
0x0f88
ça va ?_
Si on utilise un type de donnée immuable (≡ non modifiable) tel que :
Alors ça ne fait pas de différences fondamentale (un peu de consommation mémoire en plus ou en moins).
Si on utilise un type de donnée mutable (≡ modifiable) tel que :
Alors ça ne fait une énorme différence. Exemple :
a = [1, 2, 3, 4]
b = a
et
Il y a donc une différence en mémoire entre :
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]
a = [1, 2, 3, 4]
b = a
adresse valeur
tas
0x0128
0xf10
a
0x0158
0xf10
b
0x0f10
1
0x0f18
2
0x0f20
3
0x0f28
4
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]
adresse valeur
tas
0x0128
0xf10
a
0x0158
0xf80
b
0x0f10
1
0x0f18
2
0x0f20
3
0x0f28
4
0x0f80
1
0x0f88
2
0x0f90
3
0x0f98
4
Partage de la même référence :
a = [1, 2, 3, 4]
b = a
b[0] = 42
print(a) #affiche [42, 2, 3, 4]
print(b) #affiche [42, 2, 3, 4]
Copie :
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]
b[0] = 42
print(a) #affiche [1, 2, 3, 4]
print(b) #affiche [42, 2, 3, 4]
Super dangereux :
Supposons que l'on veuille créer une matrice 3×3, c'est à dire un tableau de tableaux :
m = [ [0, 0, 0 ], [0, 0, 0 ], [0, 0, 0 ] ]
m[1][1] = 42
print (m)
# affiche [ [0, 0, 0 ], [0, 42, 0 ], [0, 0, 0 ] ]
# tout va bien
m = [ [ 0, 0, 0 ] ] * 3
m[1][1] = 42
print (m)
# affiche [ [0, 42, 0 ], [0, 42, 0 ], [0, 42, 0 ] ]
# WAT !
Dans le code précédent, l'expression [ [ 0, 0, 0 ] ] * 3 se comporte comme:
tmp = [0, 0, 0 ]
m = [None, None, None]
for i in range(len(m)):
m[i] = tmp #la même référence est placée dans les 3 cases!
Il faut être extrèmement rigoureux lorsque l'on manipule des tableaux.
Si on écrit des fonctions prenant des tableaux en argument, il faut préciser clairement si elles font des copies ou modifient le tableau.
Il est presque toujours faux d'écrire une chose du style:
tab1 = [1, 2, 3, 4, 5]
…
tab2 = tab1 #c'est un alias, pas une copie.
#modifier tab1 modifie aussi tab2
Ensemble des méthodes de travail et bonnes pratiques à utiliser
dans le cadre du développement d'un logiciel.
C'est une science de génie industriel.
On donne ici quelques petites notions importantes et on montre
comment Python nous aide à les respecter.
Le génie logiciel est un aspect important de l'informatique, il
est développé tout au cours de la Licence (L1, L2, L3).
Il est très important de documenter ces fonctions :
En Python, on peut mettre une chaîne de caractères comme première
« instruction » d'une fonction. Cette chaîne est sauvegardée et
peut être affichée au moyen de la
commande help.
Cette convention est utilisée par toutes les fonction de la
bibliothèque standard.
>>> def div_mod(a, b):
... """Prend en argument deux entiers a et b et renvoie
... le quotient et le reste dans la division de a par b
... sous la forme d'une paire. Lève une exception si b vaut 0."""
... return (a // b, a % b)
...
>>> div_mod(10, 3)
(3, 1)
>>> help(div_mod)
Help on function div_mod in module __main__:
div_mod(a, b)
Prend en argument deux entiers a et b et renvoie
le quotient et le reste dans la division de a par b
sous la forme d'une paire. Lève une exception si b vaut 0.
>>> help(len)
Help on built-in function len in module builtins:
len(obj)
Return the number of items in a container.
Il est courant de vouloir signaler qu'une valeur n'est pas un
argument valide pour une fonction.
On sait déjà utiliser des exceptions pour signaler cela.
L'instruction assert (e) évalue
l'expression e. Si cette dernière est fausse, le
programme lève une exception AssertionError
def div_mod(a, b):
"""Prend en argument deux entiers a et b et renvoie
le quotient et le reste dans la division de a par b
sous la forme d'une paire. Lève une exception si b vaut 0."""
assert (b != 0)
return (a // b, a % b)
On peut lire assert comme « vérifie que ». Ci dessus : « vérifie que b est différent de 0.
En génie logiciel, il est considéré comme une mauvaise
pratique de mettre dans les mêmes fichier des fonctions qui
n'ont rien à voir.
Si on conçoit un jeu :
En Python, chaque fichier .py définit un module, c'est à dire un ensemble de fonction et de variables.
Par défaut, on ne peut référencer que des fonctions et variables du fichier (≡ du module) dans lequel on se trouve.
La directive import permet d'importer tout ou partie des fonctions et variables d'un module.
La première chose que l'on peut faire est d'importer tout un module.
import math
y = math.sin(math.pi / 2)
z = math.sqrt(499)
t = math.log(29)
Lorsque l'on écrit import toto, l'interprète Python cherche dans le répertoire courant, puis dans les répertoire systèmes (dans cet ordre par défaut) un fichier toto.py. S'il le trouve, il l'évalue :
Attention, pour cette raison, il ne faut jamais appeler un de ses fichiers comme un fichier de la bibliothèque standard !
Parfois, on ne souhaite importer qu'un petit nombre de fonctions.
On peut utiliser la directive from … import :
from math import sin, sqrt, pi
y = sin(pi / 2)
z = sqrt(499)
Dans le code ci-dessus, seul sin, sqrt et pi sont visibles.
La forme : from foo import * importe tous les symboles, sans préfixe. Elle est à proscrire dorénavant. Pourquoi ?
Il y a deux aspects contradictoires :
Exemple :
Sans système de module, on aurait du utiliser une convention arbitraire par exemple math_log et console_log. C'est moche.
from logging import log, WARNING, ERROR
from math import *
…
log (WARNING, "attention !") #Erreur utilise math.log()
…
log (ERROR, "erreur fatale !")
Dans le code ci-dessus, on a masqué involontairement la fonction log du module logging, par une fonction qui fait complètement autre chose.
Python possède des outils pour aider à écrire du code propre, il faut les utiliser :