Introduction aux Fonctions en Python
Introduction aux Fonctions en Python
Fonctions
Rappels
un morceau de programme
portant en général un nom
acceptant zéro, un ou plusieurs paramètres
produisant le plus souvent un résultat.
Des exceptions existent, mais la forme la plus courante d'une fonction est donc proche de celle d'une fonction mathématique.
Lisibilité :
isoler une partie du programme (par exemple un gros calcul compliqué)
éviter une trop grande imbrication des if, des while
Modularité et robustesse:
réutiliser le même code plusieurs fois (évite de recopier le code)
faciliter la correction des bugs, l'évolution et la maintenance
Généricité :
changer la valeur des paramètres (même calcul mais avec différentes valeurs de départ)
En Python, il existe un grand nombre de fonctions prédéfinies, que nous avons déjà utilisées, par exemple :
int(obj) : 1 paramètre, 1 résultat. Reçoit en paramètre un objet (par exemple str ou float), essaie de le transformer en entier et renvoie l'entier
obtenu.
In [1]:
int("34")
Out [1]: 34
len(obj) : 1 paramètre, 1 résultat. Reçoit un objet (par exemple str) et renvoie sa longueur.
In [2]: len("bonjour")
Out [2]: 7
randint(mini, maxi) : 2 paramètres, 1 résultat. Reçoit deux nombres, et renvoie un entier aléatoire compris entre ces deux nombres (inclus).
In [3]:
from random import randint
randint(1, 34)
Out [3]: 8
Définition de fonction
Appel de fonction
Une fois définie, nom_fonction peut être utilisée dans le code (on parle d'un appel) en indiquant entre parenthèses ses paramètres séparés par des
virgules :
# définition de fonction
def nom_fonction(param_1, ..., param_n):
...
# reste du programme
...
# appel de la fonction :
une_var = nom_fonction(expr_1, ..., expr_n)
Exemples
In [4]:
# fonction à deux paramètres produisant un résultat
def maximum(a, b):
if a >= b:
return a
else:
return b
In [5]: nb1 = 14
nb2 = 31
le max de 14 et 31 est 31
le max de 14 et 31 est 31
Entraînement :
Décrire l'exécution pas à pas du programme (avec état de la mémoire). On peut aussi essayer avec Python Tutor.
Dresser un tableau de valeurs de l'exécution du programme
En principe, une fonction sans paramètre devrait avoir toujours le même comportement
Dans l'exemple suivant, on utilise un générateur pseudo-aléatoire, ce qui explique que la fonction ne renvoie pas toujours le même résultat
Une fonction pourrait aussi recevoir des données depuis l'extérieur (utilisateur, requête réseau...).
def lance_de() :
return randint(1,6)
compteur = 1
while lance_de() != 6:
compteur = compteur + 1
print('Obtenu un 6 en', compteur, 'jets de dé.')
Si l'exécution arrive à la dernière instruction du corps de la fonction sans rencontrer d'instruction return expr, alors la valeur de retour par défaut
est None (même comportement si return seul)
En général une telle fonction a quand même un effet sur l'environnement (affichages, dessin, écriture dans un fichier, envoi d'informations sur le
réseau...)
Pour tous les effets autres que le renvoi d'un résultat, on parle d'effets secondaires
In [7]:
from turtle import *
Les programmeurs débutants confondent très souvent la notion de paramètre et celle de saisie (au clavier par exemple) et la notion de valeur de retour
avec celle de valeur affichée.
In [8]:
# ATTENTION CECI EST INCORRECT, A NE PAS REPRODUIRE !!!
def pgcd(a, b):
a = int(input()) # NON !
b = int(input()) # NON !
while a % b != 0:
r = a % b
a = b
b = r
print("le pgcd est", b) # NON !
Comment écrire un programme vérifiant si trois entiers sont premiers entre eux à l'aide de cette fonction ?
Combien ce programme ferait-il de saisies ?
Qu'afficherait ce programme ?
Approfondissement
Composition de fonctions
In [ ]:
def pgcd(a, b):
while a % b != 0:
r = a % b
a = b
b = r
return b
In [ ]:
def simplifier(num, denom):
d = pgcd(num, denom)
return (num // d, denom // d)
In [ ]: simplifier(8, 6)
In [ ]:
simplifier(21, 39)
In [ ]:
def pgcd3(a, b, c):
d = pgcd(a, b)
return pgcd(c, d)
In [ ]:
pgcd3(18, 27, 12)
Point de vigilance :
les paramètres et variables définies dans le corps d'une fonction sont indépendantes des autres variables du programme
elles n'existent plus une fois l'exécution de la fonction terminée
on les appelle des variables locales
In [ ]:
%%nbtutor -r -f
nb1 = 13
nb2 = 3
c = maximum(nb1, nb2)
print("le max de", nb1, "et", nb2, "est", c)
À retenir : Changer les valeurs de a et b dans la fonction n'a pas d'effet sur nb1 et nb2 !
In [ ]:
%%nbtutor -r -f
a = 13
b = 3
c = maximum(a, b)
print("le max de", a, "et", b, "est", c)
À retenir : Changer les valeurs de a et b dans la fonction n'a pas d'effet sur a et b dans le programme principal !
Essayons d'écrire une fonction permettant d'intervertir les valeurs de deux variables :
In [ ]:
# %%nbtutor -r -f # commenter si nb_tutor non installé
x = 1
y = 2
echange(x, y)
print(x, y)
print(temp)
À retenir :
changer les valeurs de a et b dans la fonction n'a pas d'effet sur x et y dans le programme principal !
la variable temp n'existe plus après l'exécution de la fonction
Au moment d'un appel de fonction, un espace de noms local est créé. Il associe à chacun des paramètres la valeur de l'expression correspondante
dans l'appel. Les variables locales sont également créées dans ces espace de noms.
Espace de noms
Un espace de noms est un ensemble de noms (de variables, de fonctions...) défini à un certain point d'un programme
L'ensemble de tous les noms connus à un point du programme est généralement constitué de plusieurs espaces de noms superposés (du plus ancien
au plus récent) :
Pile d'appels
la ligne en cours de chaque appel est affiché (du plus ancien au plus récent)
nom technique : « traceback »
In [ ]: simplifier(4, 0)
possible pour n'importe quel nom défini dans un des espaces de noms antérieurs
si plusieurs espaces contiennent le même nom, c'est le plus récent qui est sélectionné
Affectation :
création d'un espace de noms local contenant p_1 à p_n au sommet de la pile d'appels
chaque expression e_i est évaluée en une valeur v_i et affectée à la variable p_i
exécution du corps de la fonction dans l'espace de noms local
si la fonction exécute l'instruction return expr ou atteint la fin de son bloc d'instructions
l'espace de noms local est détruit
l'expression appelante ma_fonction(e_1, ..., e_n) prend la valeur de expr (respectivement None)
reprise du programme principal dans l'espace global
Vocabulaire :
les noms p_1 à p_n sont appelés paramètres formels (ou paramètres tout court)
les valeurs v_1 à v_n sont appelées paramètres effectifs (ou arguments)
In [ ]:
def triple(n):
"""
Fonction calculant le triple du nombre n (int ou float)
ou la répétition trois fois de la chaîne n.
"""
return n * 3
On peut accéder à la chaîne de documentation d'une fonction en tapant help(nom de la fonction) dans l'interpréteur :
In [ ]:
help(triple)
Cela fonctionne aussi pour les fonctions prédéfinies ou issues de modules :
Comme tout morceau de programme, chaque fonction doit être testée immédiatement pour s'assurer qu'elle fonctionne.
In [ ]: triple(3)
In [ ]:
triple(9.0)
Plutôt que de perdre ces tests, il est utile de les intégrer à la documentation de la fonction, pour pouvoir s'y référer plus tard. Si l'on change le code de
la fonction, cela permet aussi de vérifier que son comportement reste correct.
In [ ]:
def triple(n):
"""
Fonction calculant le triple du nombre n (int ou float)
ou la répétition trois fois de la chaîne n.
>>> triple(3)
9
>>> triple(9.0)
27.0
"""
return n * 3
Il existe des outils qui permettent de lancer automatiquement tous les tests présents dans la documentation, et de vérifier qu'ils produisent les
résultats annoncés.
Par exemple, à la fin d'un programme, on peut écrire le code suivant pour lancer systématiquement tous les tests présents dans le fichier :
import doctest
[Link]()
Exemple :
In [ ]: def triple(n):
"""
Fonction calculant le triple du nombre n (int ou float)
ou la répétition trois fois de la chaîne n.
>>> triple(3)
9
>>> triple(9.0)
27.0
>>> triple('pom')
'pompompom'
"""
return n * 3
In [ ]: def racine(n):
"""
Fonction calculant la racine carrée du nombre n.
>>> racine(0)
0.0
>>> racine(1)
1.0
>>> racine(4)
2.0
"""
return n ** (1/2)
In [ ]:
import doctest
[Link]()
Annotations de type
On peut également préciser le type des paramètres et du résultat d'une fonction à l'aide d'annotations de type
Ces annotations ne sont pas vérifiées directement par Python
Elles sont utilisées par certains environnements de développement (IDE) comme PyCharm, VSCode, etc. pour fournir de l'information à
l'[Link]
Voir la documentation ici par exemple
In [ ]:
def pgcd(a: int, b: int) -> int:
while a % b != 0:
r = a % b
a = b
b = r
return b
In [ ]:
def salutation(nom: str) -> str:
return 'Bonjour ' + nom
Listes
Rappels
En mémoire : tableau à n cases, chacune contenant une référence ("flèche") vers un objet
Création et affichage
In [ ]:
lst = [3, 'toto', 4.5, False, None]
print(lst)
In [ ]: lst = []
print(lst)
In [ ]:
lst = ['test', [1, [2], 3]]
print(lst)
La longueur d'une liste (le nombre d'éléments qu'elle contient) s'obtient par la fonction len.
In [ ]:
lst = [3, 'toto', 4.5, False, None]
print(len(lst))
In [ ]: print(len([]))
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[1])
Attention !
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[3])
Exercice : Écrire une fonction qui affiche tous les éléments d'une liste (un par ligne).
In [ ]:
def affiche_elements(lst):
...
In [ ]:
# Solution avec une boucle while
def affiche_elements(lst):
i = 0
while i < len(lst):
print(lst[i])
i = i + 1
La boucle for
ma_liste = ...
i = 0
while i < len(ma_liste):
faire_un_truc_avec(i)
i += 1
Inconvénients :
Parcours de liste élément par élément avec une boucle for (itération)
ma_liste = ...
for elem in ma_liste:
faire_un_truc_avec(elem)
Avantages :
Inconvénient :
Exercice : Écrire une fonction qui affiche tous les éléments d'une liste (un par ligne) avec une boucle for.
In [25]:
def affiche_elements(lst):
for elem in lst:
print(elem)
3
toto
4.5
On peut aussi parcourir une liste par indices avec une boucle for, à l'aide de la fonction range(). Pour l'instant, il suffit de savoir que la fonction range
fabrique des intervalles d'entiers :
3
toto
4.5
C'est particulièrement utile lorsque l'on a besoin d'accéder à l'indice (on y reviendra).
Nota bene : dans toute la suite, lorsqu'il s'agit de coder une fonction sur une liste, on vous présente la version avec un while et avec un for (par
éléments ou par indices suivant les cas), en mettant en premier celle qui est la plus élégante.
In [ ]:
lst = [3, 'toto', 4.5, False, None]
print(lst[2])
lst[2] = 'titi'
print(lst)
Attention, ceci ne crée pas une nouvelle liste mais modifie la liste sur place !
In [ ]:
lst = [3, 'toto', 4.5, False, None]
lst_bis = lst
lst[2] = 'titi'
lst_bis
Concaténation et répétition
Comme pour les chaînes de caractères (str) on peut utiliser les opérateurs + pour fabriquer la concaténation de deux listes et * pour répéter une liste.
In [ ]:
[] + [3, 'toto', 4.5] + []
In [ ]:
3 * ['a', 'b']
In [ ]: [0] * 13
In [ ]:
lst = [3, 'toto', 4.5]
lst2 = lst
lst3 = lst + []
lst4 = lst * 1
Test d'appartenance
Exercice : Écrire une fonction recevant une liste et une valeur, et renvoyant True si la valeur apparaît dans la liste (False sinon)
In [11]:
def appartient(lst, val):
...
Avec un for :
In [13]:
def appartient(lst, val):
for elem in lst:
if elem == val:
return True
return False
if appartient(lst, 'Médor'):
print('Bon chien !')
True
Bon chien !
print(appartient(lst, 'Cunégonde'))
if appartient(lst, 'Médor'):
print('Bon chien !')
True
Bon chien !
In [ ]:
lst = ['Hildegarde', 'Cunégonde', 'Médor']
'Cunégonde' in lst
In [ ]:
lst = ['Hildegarde', 'Cunégonde', 'Médor']
if 'Médor' in lst:
print('Bon chien !')
Exercice : Écrire une fonction recevant une liste non vide d'éléments comparables entre eux et renvoyant la valeur du plus petit élément qui apparaît
dans la liste
In [14]:
def minimum(lst):
...
In [15]:
def maximum(lst):
...
Avec un for :
In [22]:
def maximum(lst):
res = lst[0]
for elem in lst:
if elem > res:
res = elem
return res
In [23]:
lst = [4, 6.6, 2, -7, 13, -6, 0]
print(minimum(lst), maximum(lst))
-7 13
Avec un while :
In [16]:
def minimum(lst):
res = lst[0] # plante si lst == []
i = 1
while i < len(lst):
if lst[i] < res:
res = lst[i]
i += 1
return res
In [17]:
def maximum(lst):
res = lst[0]
i = 1
while i < len(lst):
if lst[i] > res:
res = lst[i]
i += 1
return res
In [18]:
lst = [4, 6.6, 2, -7, 13, -6, 0]
print(minimum(lst), maximum(lst))
-7 13
In [ ]:
lst = [4, 6.6, 2, -7, 13, -6, 0]
print(min(lst), max(lst))
Attention : pour que cela fonctionne il faut que tous les éléments soient comparables !
In [ ]:
lst = [3, 'toto', 4.5, False, None]
print(min(lst))
Une erreur se produit aussi si l'on appelle ces fonctions sur une liste vide :
In [24]:
min([])
Exercice : Écrire une fonction somme(lst) qui renvoie la somme des éléments d'une liste dont tous les éléments sont des nombres.
In [ ]:
def somme(lst):
...
Avec un for :
In [28]:
def somme(lst):
res = 0
for elem in lst:
res += elem
return res
print(somme([1, 2, 3]))
Avec un while :
In [ ]:
def somme(lst):
res = 0
i = 0
while i < len(lst):
res += lst[i]
i += 1
return res
print(somme([1, 2, 3]))
In [ ]:
sum([1, 2, 3])
Attention : la fonction sum provoque une erreur si un élément de la liste n'est pas un nombre !
In [ ]:
sum([3, 'toto', 4.5, False, None])
On va maintenant énumérer un certain nombre de méthodes prédéfinies sur les listes, permettant des modifications plus complexes. Pour plus de
détails, on pourra consulter la documentation en ligne.
Les fonctions append et pop sont appelées méthodes, ou fonctions s'appliquant à un objet (nous en verrons d'autres dans les cours suivants)
Attention, ces instructions ne créent pas une nouvelle liste mais modifient la liste sur place !
In [ ]:
lst = [3, 'toto', 4.5, False, None]
lst_bis = lst
[Link](1)
print(lst_bis)
elem = lst_bis.pop(2)
print(elem)
print(lst)
Exercice : écrire une fonction recevant deux listes et ajoutant tous les éléments de la seconde à la fin de la première
In [ ]:
def etend_liste(une_liste, autre_liste):
...
Avec un for :
In [29]:
def etend_liste(une_liste, autre_liste):
for elem in autre_liste:
une_liste.append(elem)
print(lst)
print(lst2)
etend_liste(lst, lst2)
print(lst)
print(lst2)
Avec un while :
In [ ]:
def etend_liste(une_liste, autre_liste):
i = 0
while i < len(autre_liste):
une_liste.append(autre_liste[i])
i += 1
print(lst)
print(lst2)
etend_liste(lst, lst2)
print(lst)
print(lst2)
[Link](lst2)
print(lst)
print(lst_bis)
In [ ]:
lst = [3, 'toto', 4.5]
lst_bis = lst
lst2 = [False, None]
Exercice : Écrire une fonction recevant deux listes en argument et renvoyant une nouvelle liste contenant tous les éléments de la première suivis de
tous les éléments de la seconde, à la manière de l'opérateur +.
In [ ]:
def concatene(lst1, lst2):
...
In [31]:
def concatene(lst1, lst2):
res = []
for elem1 in lst1:
[Link](elem1)
for elem2 in lst2:
[Link](elem2)
return res
Ou avec un while :
Exercice : Écrire une fonction recevant une liste lst et un entier n en argument et renvoyant une nouvelle liste contenant les éléments de lst répétés n
fois à la manière de l'opérateur *.
In [32]:
def repete(lst, n):
...
In [33]:
def repete(lst, n):
res = []
i = 0
while i < n:
[Link](lst)
i += 1
return res
repete([4, 5, 6], 3)
On peut aussi utiliser une boucle for pour répéter une opération n fois en parcourant les entiers de 0 à n-1 ou de 1 à n grâce à range() :
In [34]:
def repete(lst, n):
res = []
for i in range(n):
[Link](lst)
return res
repete([4, 5, 6], 3)
Comme d'habitude, on peut aussi s'en sortir avec seulement des while, même si c'est moins élégant :
In [35]:
def repete(lst, n):
res = []
i = 0
while i < n:
j = 0
while j < len(lst):
[Link](lst[j])
j += 1
i += 1
return res
repete([4, 5, 6], 3)
Exercice : Écrire une fonction renvoyant le plus petit indice où apparaît un élément x dans une liste lst (on renverra None si x n'apparaît pas dans la
liste)
In [ ]:
def chercher(lst, x):
...
Avec un while :
Exercice : Même exercice que ci-dessus, mais avec une boucle for.
In [ ]:
def chercher(lst, val):
for i in range(len(lst)):
if lst[i] == val:
return i
return None
Attention, cette méthode provoque une erreur si l'élément recherché n'est pas dans la liste !
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
[Link](3.5)
Exercice : Écrire une fonction renvoyant le nombre de fois où apparaît un élément x dans une liste lst
Avec un for :
In [36]:
def compter(lst, x):
cpt = 0
for elem in lst:
if elem == x:
cpt += 1
return cpt
Avec un while :
In [ ]:
def compter(lst, x):
cpt = 0
i = 0
while i < len(lst):
if lst[i] == x:
cpt += 1
i += 1
return cpt
lst = [3, 'toto', 4.5, False, None, 4.5]
print(compter(lst, 4.5))
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
[Link](4.5)
Exercice : Écrire une fonction supprimant tous les éléments de la liste lst
In [ ]:
def vider(lst):
while len(lst) > 0:
[Link]()
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
[Link]()
print(lst)
In [38]:
# ATTENTION : MAUVAISE SOLUTION
def essaie_de_vider_pour_voir(lst):
for i in range(len(lst)):
[Link](i)
On pourrait tout de même s'en sortir, mais lorsque l'on souhaite faire une fonction qui modifie la liste donnée en argument, il vaut généralement mieux
le faire avec une boucle while.
Exercice : Écrire une fonction renversant l'ordre des éléments d'une liste lst
In [ ]:
def renverser(lst, x):
...
In [ ]:
def renverser(lst):
i = 0
j = len(lst) - 1
while i < j:
lst[i], lst[j] = lst[j], lst[i]
i += 1
j -= 1
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
[Link]()
print(lst)
Ici aussi, comme il s'agit de modifier la liste, on utilise un while (idem dans la suite).
Exercice : Écrire une fonction retirant la première occurrence d'un élément x dans une liste lst (on ne fera rien si la liste ne contient pas x)
In [ ]:
def retirer(lst, x):
...
In [ ]:
def retirer2(lst, x): # version utilisant pop(i)
i = chercher(lst, x)
if i is not None:
[Link](i)
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
[Link](4.5)
print(lst)
Attention, cette méthode provoque une erreur si l'élément à retirer n'est pas dans la liste !
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
[Link](3.5)
Exercice : Écrire une fonction qui insère un élément x à la position i de la liste lst (si i est trop grand, la fonction insère x à la fin de lst ; si i est trop
petit, elle insère x au début de lst)
In [ ]:
def ajouter(lst, i, x):
...
In [ ]:
def ajouter(lst, i, x):
# determine une valeur correcte pour i
i = max(0, min(i, len(lst)))
# on ajoute une case vide à la fin
[Link](None)
# decalage des cases sur la droite à partir de i
j = len(lst) - 1
while j > i:
lst[j] = lst[j-1]
j -= 1
lst[i] = x # on insère x à l'indice i
lst = [3, 'toto', 4.5, False, None, 4.5]
print(lst)
ajouter(lst, 3, 79) # indice ok
print(lst)
ajouter(lst, -6, "petit") # indice trop petit
print(lst)
ajouter(lst, 16, "grand") # indice trop grand
print(lst)
Attention, cette méthode insère l'élément à la fin si l'indice est supérieur à len(lst), et au début si l'indice est négatif !
In [ ]:
lst = [3, 'toto', 4.5, False, None, 4.5]
print(lst)
[Link](3, 79)
print(lst)
In [ ]:
[Link](-8, "petit")
print(lst)
[Link](16, "grand")
print(lst)
Enfin, il est possible de trier le contenu d'une liste avec la méthode sort. Programmer ce genre de fonctions fait partie des objectifs du semestre 2.
Attention, cette méthode ne fonctionne pas si les éléments ne sont pas tous comparables !
Notez que la méthode sort modifie définitivement la liste (une nouvelle liste n'est pas créée). Il existe aussi une fonction permettant de fabriquer une
copie triée d'une liste : la fonction sorted.
In [ ]:
lst = [4, 6.6, 2, -7, 13, -6, 0]
print(sorted(lst))
print(lst)
Récapitulatif
opérateur effet
lst[i] (dans une expression) élément d'indice i de lst
lst[i] = expr modifie l'élément d'indice i de lst
lst1 + lst2 concaténation (nouvelle liste)
lst * n répétition (nouvelle liste)
x in lst True si x apparaît dans lst
x not in lst True si x n'apparaît pas dans lst
fonction effet
len(lst) renvoie la longueur de lst
min(lst) renvoie le plus petit élément de lst ⋆♡
max(lst) renvoie le plus grand élément de lst ⋆♡
sum(lst) renvoie la somme des éléments de lst ♣
sorted(lst) renvoie une copie triée de lst ♡
méthode effet
[Link](x) ajoute x à la fin de lst
[Link](lst2) ajoute les éléments de lst2 à la fin de lst
[Link](i, x) ajoute x à l'indice i dans lst
[Link](x) retire la première occurrence de x de lst ⋆
[Link]() retire et renvoie le dernier élément de lst ♣
[Link](i) retire et renvoie l'élément d'indice i de lst ♡
[Link]() vide la liste
[Link]() trie la liste ♢
[Link]() renverse la liste
méthode effet
[Link](x) renvoie l'indice de la première occurrence de x dans lst ⋆
[Link](x) renvoie le nombre d'occurrences de x dans lst ⋆
[Link]() renvoie une copie (superficielle !) de lst ♣
for vs while
Le fichier recap_fonctions_listes.py est un récapitulatif des fonctions que l'on a vues, implémentées avec un for et avec un while. La version préférée
est en premier.
Syntaxe : lst[i, j] construit une liste contenant les éléments d'indices i à j-1 de lst
Attention, l'élément d'indice i est inclus mais celui d'indice j est exclu !
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[1:len(lst)])
print(lst[0:1])
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[:len(lst)-1])
print(lst[:]) # cette instruction crée une copie de lst !
Affectation de slice
On peut aussi utiliser la syntaxe des tranches pour modifier en une seule fois une partie de la liste
Il est possible de compléter la notation des tranches en spécifiant un "pas" k: lst[i:j:k]. Dans ce cas, on sélectionne uniquement les éléments
d'indices i, i+k, i+2*k, etc. en s'arrêtant à l'indice j(exclu).
In [ ]: lst = [0, 1, 2, 3, 4, 5]
print(lst[Link])
Un exemple un peu étrange :
In [ ]:
lst = [0, 1, 2, 3, 4, 5]
print(lst[Link]-1])
Indices négatifs
La connaissance de cette notion n'est pas exigible à l'examen.
Il est possible d'utiliser des indices négatifs pour accéder aux éléments d'une liste. Dans ce cas, les éléments sont numérotés à partir de la droite, en
commençant par l'indice -1 et jusqu'à l'indice -len(lst) :
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[-1])
print(lst[-3])
On voit que lst[-1] est une autre façon de désigner l'élément lst[len(lst)-1, et lst[-len(lst)] désigne lst[0]. Tenter d'accéder à un indice plus
petit provoque une erreur :
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[-4])
Ces indices négatifs sont utiles comme raccourcis d'écriture dans certains cas particuliers, par exemple pour construire la copie d'une liste privée de
son dernier élément :
In [ ]:
lst = [3, 'toto', 4.5]
print(lst[:-1])
La fonction enumerate(iterable) permet d'itérer sur les couples (i, elem) constitués d'un indice et de l'élément correspondant dans iterable.
In [14]:
def chercher(L, x):
for i, elem in enumerate(L):
if elem == x:
return i
return None
In [ ]:
def lister(lst):
for i, elem in enumerate(lst):
print(i, '->', elem)
lister([6, 3, 8])
Mutations de listes
Il existe une syntaxe abrégée, inspirée de la notation ensembliste des mathématiques, qui permet de définir rapidement des listes. On parle de
mutations de listes, ou list comprehensions en Anglais.
La syntaxe est :
lst = []
for <variable> in <iterable>:
[Link](<forme d'un element>)
Ce qui équivaut à :
lst = []
for <variable> in <iterable>:
if <condition>:
[Link](<forme d'un element>)
In [16]:
[i*i for i in range(10)] # liste des 10 premiers carrés
Out [16]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
In [17]: lst = []
for i in range(10):
[Link](i*i)
lst
In [ ]:
[c for c in 'anticonstitutionnellement' if c in 'aeiouy'] # liste des voyelles d'un mot
Ce qui équivaut à :
lst = []
for <variable1> in <iterable1>:
for <variable2> in <iterable2>:
if <condition>:
[Link](<forme d'un element>)
Exemple :
In [ ]:
[[i + j for j in range(5)] for i in range(5)]
Listes de listes
Une liste peut contenir des éléments de n'importe quel type, en donc en particulier des listes, voire des listes de listes, etc.
Exemple : le morpion
Les listes de listes sont particulièrement adaptées lorsque l'on souhaite représenter des grilles carrées ou rectangulaires. C'est le cas du morpion, qui
se joue sur une grille 3x3 (voir aussi l'exercice "retournement", TD 6 ex.1). Par exemple, la grille (les points représentent des cases vides) :
...X.X.O.
In [ ]:
grille = [['.', '.', '.'], ['X', '.', 'X'], ['.', 'O', '.']]
La case de coordonnées (i, j) correspond alors à grille[i][j] (attention : comme d'habitude, la numérotation commence à 0). Graphiquement :
0120...1X.X2.O.
On peut expérimenter :
In [ ]:
grille[2][1] # Vous pouvez faire varier les indices
In [ ]: grille[2][2] = 'O'
grille
Copie superficielle
Pour des raisons d'efficacité, Python est flemmard : lorsqu'on met une liste dans une variable, la liste n'est pas copiée, et la variable est simplement un
alias.
In [ ]:
liste = [1, 2, 3]
fausse_copie = liste
fausse_copie[1] = 4
print(fausse_copie)
print(liste)
# fausse_copie et liste sont toutes deux modifiées !
C'est très instructif d'aller voir ce qu'il se passe mémoire sur PythonTutor : fausse_copie et liste pointent vers la même liste.
Cela vaut aussi pour les listes de listes ! Par exemple :
In [ ]:
ligne = [1, 2, 3]
grille = []
for i in range(3):
[Link](ligne)
# Équivalent à grille = [ligne] * 3
grille[2][1] = 4
print(grille)
# Le contenu des trois lignes est modifié, puisque c'est la *même* liste
Pour bien comprendre cette section, il est utile de revoir la section "Modèle de mémoire de Python" de la séquence 1 du cours. Ainsi, chaque variable
est en fait le nom d'une flèche vers un objet en mémoire : [Link].
In [ ]:
def essaie_de_modifier_pour_voir(x):
x = 5
x = 12
essaie_de_modifier_pour_voir(x)
print(x)
La valeur de x n'est pas modifiée ! En effet, la fonction reçoit seulement la valeur de x, qui est 12, et le x dans la fonction est cantonné à l'espace de
noms local (ce n'est pas le même x).
Plus généralement, une fonction ne peut pas faire en sorte qu'une variable pointe vers un autre objet en mémoire :
In [ ]:
def essaie_de_modifier_pour_voir_2(x):
x = [5]
x = [12]
essaie_de_modifier_pour_voir_2(x)
print(x)
In [ ]: def essaie_de_modifier_pour_voir_3(x):
x = "5"
x = "12"
essaie_de_modifier_pour_voir_3(x)
print(x)
Puisqu'il est impossible qu'une fonction fasse en sorte que son argument pointe vers un autre objet, on pourrait être tenté de modifier cet objet.
Ici, le comportement dépend du type de l'objet. Pour les int, float et bool, on ne voit pas bien comment on pourrait les modifier. Qu'en est-il des
chaînes de caractères (str) ? Essayons :
In [ ]:
def essaie_de_modifier_pour_voir_chaine(x):
x[0] = "C"
Python explose avec l'erreur TypeError: 'str' object does not support item assignment. En effet, il interdit aussi formellement de modifier les
chaînes de caractères.
In [ ]:
x = "Zoucou les AP1 !"
x[0] = "C"
# Même erreur
In [ ]:
x = "Zoucou les AP1 !"
x = "Coucou les AP1 !"
print(x)
En effet, ici, Python crée un nouvel objet "Coucou les AP1 !" et fait en sorte que x pointe vers ce nouvel objet. L'ancien objet "Zoucou les AP1 !" n'est
pas modifié (mais n'a plus personne qui pointe vers lui).
In [ ]:
liste = [1, 2, 3]
liste[2] = 4
print(liste)
Ou encore :
Par conséquent, les fonctions peuvent aussi modifier les listes qu'elles prennent en argument :
In [ ]:
def essaie_de_modifier_pour_voir_liste(x):
x[0] = 5
x = [12]
essaie_de_modifier_pour_voir_liste(x)
print(x)
On observe que le contenu de la liste x est modifié ! En effet, la fonction reçoit en argument la valeur de la variable x, qui est la liste elle-même (x est
seulement un nom pour la flèche vers la liste). Elle peut donc en modifier le contenu de la liste via x[0] = 5, qui exécute une action sur la liste.
Attention cependant ! Comme vu ci-dessus, on ne peut pas modifier directement x en tant que variable, par exemple en lui demandant de pointer vers
une autre liste :
In [ ]:
def essaie_de_modifier_pour_voir_liste_bis(x):
x = [5]
x = [12]
essaie_de_modifier_pour_voir_liste_bis(x)
print(x)
Séquences et itérables
Les itérables sont toutes les structures de données dont on peut parcourir les éléments un par un, c'est-à-dire que l'on peut itérer.
Exercice (rappel) :
Implémenter les fonctions compter, copier et chercher en utilisant des boucles for
In [1]:
def compter(L, x):
cpt = 0
for elem in L:
if elem == x:
cpt += 1
return cpt
Out [2]: 3
In [3]:
def copier(L):
copie = []
for elem in L:
[Link](elem)
return copie
In [ ]:
def chercher(L, x):
for elem in L:
if elem == x:
return ... # ???
# impossible de terminer, on a besoin d'un indice
return None
Nous allons maintenant explorer en détail l'itérable qu'elle produit (un range donc).
x in r, x not in r, r[i]
len(r), min(r), max(r), [Link](x), [Link](x)
list(r) (conversion en liste)
In [4]:
r = range(10)
print(r)
range(0, 10)
In [5]:
r[0]
Out [5]: 0
In [7]:
r[1]
Out [7]: 1
In [8]:
max(r)
Out [8]: 9
Out [9]: 24
In [10]: len(r)
Out [10]: 3
In [11]:
list(r)
In [12]:
for i in range(4):
print(i)
0
1
2
3
Caractéristique importante :
0 -> a
1 -> b
2 -> c
Exercice : Écrire une fonction chercher(L, x) qui recherche la première position de x dans L en utilisant une boucle for et la fonction range.
Les chaînes de caractères (str) sont aussi des séquences (et des itérables) !
[Link]('a'), [Link](s2), [Link](i, x), [Link](), [Link](i), [Link](x), [Link](), s[i] = x, [Link](), [Link]()...
En tant qu'objets itérables, les chaînes peuvent être utilisées dans des boucles for :
In [18]:
s = 'Hildegarde'
# afficher les caractères de s, un par ligne
for c in s:
print(c)
H
i
l
d
e
g
a
r
d
e
In [19]:
s = 'Hildegarde'
list(s)
Out [19]: ['H', 'i', 'l', 'd', 'e', 'g', 'a', 'r', 'd', 'e']
In [20]:
dir("")
Nous les découvrirons au fur et à mesure (et pas toutes). Si vous êtes curieux... pensez à la doc ! En voici quelques unes :
[Link]() : renvoie une copie de la chaîne avec son premier caractère en majuscule et le reste en minuscule.
[Link](sub) : donne la première la position dans la chaîne où sub est trouvé
[Link](iterable) : donne une chaîne qui est la concaténation des chaînes contenues dans iterable
[Link](sep) : renvoie une liste des mots de la chaîne, en utilisant sep comme séparateur de mots. Si sep n'est pas donné, le séparateur utilisé
est l'espace
[Link](old, new) : renvoie une copie de la chaîne dont toutes les occurrences de la sous-chaîne old sont remplacés par new.
etc.
In [24]:
print([Link]("est"))
In [27]:
"--".join(["knights", "who", "say", "ni"])
In [28]:
print([Link](' '))
In [29]:
print([Link]('e'))
In [30]:
print([Link]())
[Link]('a'), [Link](t2), [Link](i, elem), [Link](), [Link](i), [Link](x), [Link](), t[i] = elem, [Link](), [Link]()...
En tant qu'objets itérables, les tuples peuvent être utilisées dans des boucles for :
In [31]:
trucs = ('machin', 'bidule', 'chose')
for elem in trucs:
print(elem)
machin
bidule
chose
Ils s'utilisent par exemple pour mémoriser les coordonnées d'un point du plan, une fraction...
In [32]:
couple = (2, 64)
type(couple)
In [33]:
couple[1]
Out [33]: 64
Il est souvent pratique d'affecter simultanément chacun des éléments d'un tuple à une variable. Par exemple :
In [34]:
fraction = (3, 4)
num, denom = fraction
print(num)
print(denom)
3
4
Les parenthèses sont parfois omises autour des éléments d'un tuple :
In [35]:
fraction = 3, 4 # pas de parenthèses !
num, denom = fraction
print(num)
print(denom)
3
4
On se sert généralement d'un tuple pour renvoyer un résultat composé de plusieurs valeurs dans une fonction :
In [36]:
def produit_fractions(f1, f2):
num1, denom1 = f1
num2, denom2 = f2
return num1 * num2, denom1 * denom2 # c'est un tuple !
In [37]:
frac1 = (1, 3)
frac2 = (5, 2)
produit_fractions(frac1, frac2)
Il est possible de consulter le contenu d'un fichier texte en Python, et de le modifier. On accède la plupart du temps aux lignes successives d'un fichier
à l'aide d'une boucle for.
Fermeture : [Link]()
In [1]:
f = open('[Link]', 'w')
for chaine in ['au', 'revoir', 'tout', 'le', 'monde !']:
[Link](chaine)
[Link]()
In [2]:
f = open('[Link]', 'w')
[Link]("\n".join(['au', 'revoir', 'tout', 'le', 'monde !']))
[Link]()
au
revoir
tout
le
monde !(signé : Machin)
In [8]:
f = open('[Link]')
# Décommentez pour voir :
# for ligne in f:
# print([Link]())
[Link]()