Initiation à Python 3

Le langage de programmation fixé comme référence pour cette formation est Python, en version 3. Ce premier TP a pour but de vous donner des bases de programmation suffisantes pour transcrire n’importe quel algorithme travaillant sur des types de données simples : nombres, chaînes de caractères et listes. Il ne s’agit pas d’être virtuose, mais simplement que le langage ne soit pas un obstacle à la mise en pratique.

Si vous connaissez déjà bien Python, ne vous attardez pas trop. Le choix de l’environnement de développement (ici IDLE) n’est qu’une suggestion : faites comme à la maison.

Si vous connaissez déjà un autre langage de programmation, c’est l’occasion d’en découvrir un nouveau, peut-être assez différent.

Dans ces deux cas, ce sujet ne devrait pas trop poser de problème, et vous pourrez vous attaquer à un authentique TP de programmation tout de suite après.

Et si c’est votre premier contact avec la programmation, pas d’inquiétude, on avancera pas à pas.

Lancer un interpréteur

Si ce n’est déjà fait, il faut bien sûr commencer par installer Python :

  • si vous travaillez sous Windows ou Mac, les fichiers nécessaires sont accessibles sur le site http://www.python.org/ : choisir la dernière version stable (Python 3.?.?) ;

  • sous GNU/Linux, installez simplement les paquets python3 et idle3 en utilisant le gestionnaire de paquet de votre distribution.

Avec un peu de chance, ces instructions sont inutiles dans la salle informatique du site de formation. :-)

Un programme Python est un simple fichier de texte qui contient les instructions à faire exécuter par la machine, ou plutôt l’interpréteur Python. Par exemple :

   1 def discriminant(a,b,c):
   2         return b*b-4*a*c
   3 
   4 def nbracines(a,b,c):
   5         d = discriminant(a,b,c)
   6         if d>0:
   7                 return 2
   8         elif d<0:
   9                 return 0
  10         else:
  11                 return 1
  12 
  13 print("Le polynome 2X^2+3X-3 a", nbracines(2,3,-3), "racines réelles.")

On pourrait donc se contenter de taper ce code source dans un éditeur de texte (ce qui est différent d’un logiciel de traitement de texte), puis le faire exécuter par l’interpréteur. Mais l’interpréteur peut également être utilisé de manière interactive. C’est ce que nous ferons pour les premiers pas qui suivent.

Lançons donc l’environnement graphique IDLE. On se retrouve devant une fenêtre comme celle-ci :

IDLE.png

L’invite >>> nous… invite à taper des instructions Python. Ce que nous allons nous empresser de faire.

Python comme calculatrice

Lorsque vous tapez une instruction, l’interpréteur affiche la valeur éventuellement produite.

Par exemple :

   1 >>> 42
   2 42

(j’ai tapé « 42 », puis fait « Entrée » et l’interpréteur a affiché 42).

On peut bien sûr former des expressions arithmétiques plus compliquées. Testez par exemple les expressions suivantes et essayez de deviner ce que ça calcule, si possible avant de le taper :

   1 21+21
   2 32*(42+10)
   3 2**3
   4 2**4
   5 2**5
   6 2.5*2
   7 2/3
   8 1/1
   9 2//3
  10 13.0//6
  11 13%6
  12 1//1

Si vous connaissez Python 2, certains des résultats précédents vous ont peut-être surpris. On les expliquera sur place.

Et si vous avez le moindre doute sur ce qui vient de se passer, il faut le dire !

À tout moment, vous pouvez revenir sur une ligne précédente (en cliquant dessus ou en utilisant les flèches du clavier) et appuyer sur « Entrée » pour recopier automatiquement son contenu : pratique quand on a fait une erreur de frappe ou quand on tape des lignes un peu répétitives.

À ce stade, que pouvez-vous dire de la manière dont Python 3 gère les nombres ?

Python comme calculatrice scientifique

On n’est bien sûr pas limités aux opérations arithmétiques. Python est en fait un langage assez prisé dans le monde du calcul scientifique moderne, en particulier parce qu’il fournit des bibliothèques de calcul aux fonctionnalités avancées. Il n’en sera pas question ici, mais on peut déjà trouver pas mal de choses dans la bibliothèque standard.

Essayez ce qui suit :

   1 from math import * # On fait ici un peu de magie, qui sera expliquée plus tard
   2 sqrt(2)
   3 pi
   4 log(1)
   5 log(e)
   6 cos(pi/3)
   7 log(1024,2)

Fonctions

La manière recommandée de travailler l’algorithmique en Python est d’identifier la notion d’algorithme avec celle de fonction: un bout de programme qui porte un nom (disons f), attend un certain nombre d’arguments (disons x1, x2, …), et dont l’évaluation retourne un résultat.

En Python, ça correspond à la construction def:

   1 def f (x1, x2, ...):
   2         ... des instructions ...
   3         return resultat

définit une nouvelle fonction qui, partant des valeurs initiales de x1, x2, ... effectue les instructions requises, et renvoie la valeur de l’expression resultat. Si on n’utilise pas l’instruction return finale, ça fonctionne aussi sauf que rien n’est renvoyé (cas d’une procédure).

Jusqu’ici nos programmes ne comportaient qu’une seule instruction, qu’on tapait directement dans l’interpréteur.

Dès qu’on veut dépasser le stade de la calculatrice, il est bien plus pratique de taper du code dans une fenêtre d’édition, et de faire tourner ce code dans l’interpréteur.

Avec IDLE, ça demande quelques étapes de préparation :

  • créer un nouveau fichier à travers le menu File -> New File, ce qui ouvre une nouvelle fenêtre d’édition

  • taper notre code dans la fenêtre d’édition, par exemple les fonctions discriminant et nbracines données au début de cette page

  • lancer le programme en appuyant sur la touche F5 (c’est un raccourci pour le menu Run -> Run Module)

  • un dialogue nous indique que pour lancer un programme python, il faut d’abord le sauvegarder dans un fichier : faisons ça, par exemple sous le nom test.py dans notre dossier personnel ou sur le bureau (il faut terminer le nom par .py pour indiquer au système que c’est du python

  • l’interpréteur est alors redémarré et notre programme chargé : les fonctions définies dans le programme sont maintenant disponibles dans l’interpréteur
  • testez votre code en appelant par exemple nbracines(1,0,-1) ou nbracines(1,2,1) depuis l’interpréteur

Notez que les fonctions print() et input() que vous connaissez peut-être deviennent inutiles avec ce type de fonctionnement : les entrées et l’affichage sont gérées par l’interpréteur, et on peut se concentrer sur le contenu algorithmique.

Exercez-vous: Écrivez une fonction

   1 def moy(a,b):
   2         ...

qui renvoie la moyenne des arguments (en supposant qu’il s’agit de nombres). Vous pouvez vous contenter d’ajouter la définition de votre fonction au fichier déjà créé ci-dessus. Testez ensuite votre fon

Si tout fonctionne comme prévu, vous pouvez arrêter de suivres ces notes, et commencez à vous exercer sur des problèmes classiques: posez des questions ou revenez ici dès qu’il vous manque des éléments pour les traiter.

Chaînes de caractères

Bien sûr Python n’est pas limité au calcul numérique : le cœur de l’informatique moderne (disons à partir des années 70), ce sont les chaînes de caractères. En Python, une chaîne littérale est indifféremment entourée d’apostrophes droites « ' » ou de guillemets droits « " ».

Tentons un laconique :

   1 'Bonjour.'

Notez que les apostrophes restent présentes dans la sortie : l’interpréteur nous signale que nous avons bien construit une chaîne. Même chose avec :

   1 "Bonjour."

L’opération de base sur les chaînes est la concaténation :

   1 'Bon' + "jour."

Oui, ça se note comme une somme.

On peut aussi calculer la longueur d’une chaîne :

   1 len('Bon' + "jour.")

Notez que Python 3 n’est pas inquiété par tous les caractères bizarres que vous pourrez entrer :

   1 'Considérons la fonction φ : x ↦ x³.'
   2 len('1 €')

(par contre, votre navigateur peut l’être).

Ces deux dernières lignes en étonneront peut-être certains (qui savent que les caractère dits bizarres sont souvent codés sur plusieurs octets) : les caractères de Python 3 sont ceux de la norme Unicode.

Pour afficher une chaîne, on utilise l’instruction print :

   1 print('Bonjour.')

Cherchez la différence avec :

   1 'Bonjour.'

La question de l’affichage revient souvent dans les formations, mais elle n’a en fait pas beaucoup d’intérêt : ce qui nous intéresse ici, c’est comment calculer un résultat, d’une manière correcte et modulaire ; c’est-à-dire qu’on veut pouvoir utiliser ce résultat dans un calcul ultérieur.

Afficher un résultat d’une manière agréable et lisible, c’est le travail d’une interface utilisateur : ce n’est qu’une utilisation possible parmi d’autres. Et surtout ça ne concerne a priori que le résultat final. En pratique, afficher quelque chose de manière prématurée est une source d’erreurs : on aura l’occasion d’en rediscuter plus bas.

Variables et types

Un élément essentiel de l’écriture d’algorithmes est la possibilité de stocker des informations intermédiaires dans des variables : cette notion est omniprésente en programmation.

En Python, le nom d’une variable est nécessairement constitué de caractères alphanumériques, plus éventuellement le caractère « souligné » "_". Le premier caractère doit être une lettre.

Des exemples :

x
x1
ma_variable_preferee

L’affectation d’une valeur à une variable est notée par le signe d’égalité « = », un choix mathématiquement regrettable mais assez répandu. Essayons :

   1 x = 32
   2 y = 52
   3 somme = x+y
   4 produit = x*y

On peut afficher une valeur numérique de la même manière qu’une chaîne :

   1 print(somme)
   2 print(produit)

Et on peut afficher plusieurs choses sur la même ligne :

   1 print('x :',x,'y :',y,'Somme :',somme,'Produit :',produit)

Continuons :

   1 x = -1
   2 print('x :',x,'y :',y,'Somme :',somme,'Produit :',produit)

Quelles remarques pouvez-vous déjà faire sur l’utilisation des variables en Python ? Pouvez-vous expliquer ce qui se passe ?

Continuons :

   1 x = 'Ceci est une chaîne !'
   2 print('x :',x,'y :',y,'Somme :',somme,'Produit :',produit)

Le résultat vous surprend peut-être : la variable x était d’abord un nombre, voilà que c’est une chaîne. Pourtant, pas de message d’erreur ! On pourrait croire que Python ignore l’information de type. C’est plus subtil.

En Python, toute valeur a un type bien déterminé. D’ailleurs, l’interpréteur peut nous le donner :

   1 type(1)
   2 type(0.5)
   3 type('a')
   4 type('un mot')
   5 type('€uro')

Par contre les variables ne sont pas typées a priori : une variable n’est qu’un nom auquel on peut associer temporairement une valeur, de type quelconque.

   1 x=1
   2 type(x)
   3 x='a'
   4 type(x)

En particulier, en Python, on ne déclare pas les variables.

De plus, les opérations sont surchargées : on a vu que le + est défini entre deux nombres ou entre deux chaînes, et que le type de retour pouvait être adapté.

Tentons :

   1 print('Le résultat est '+42)

Que se passe-t-il ? Ce cas n’est pas visiblement pas prévu.

Il est facile de convertir un nombre en la chaîne qui le représente, et réciproquement :

   1 str(42)
   2 int('100')
   3 float('1')

On peut aussi forcer la conversion entre types de nombres de la même manière :

   1 float(1)
   2 int(100.0**100)

Si x est de type float, int(x) renvoie l’entier le plus proche. Attention, pour les négatifs, ce n’est pas la partie entière: celle-ci s’obtient par la fonction float du module math (disponible après l’invocation from math import *).

Plus étonnant peut-être :

   1 100**100
   2 100**100-100**99
   3 100.0**100
   4 100.0**100-100**99
   5 2**2**2
   6 2**2**2**2
   7 2**2**2**2**2 # C’est peut-être un peu risqué !
   8 2**2**2**2**2**2 # À tous les coups ça plante…

Si le programme a l’air bloqué, vous pouvez interrompre l’exécution avec la combinaison de touches « Ctrl+C ».

Une utilisation standard :

   1 entree = input('Entrez un nombre entier N : ')
   2 N = int(entree)
   3 print('N² = '+str(N*N))

La fonction input affiche son argument, puis récupère ce qui est entré par l’utilisateur : c’est une chaîne, qu’il faut convertir si on veut l’utiliser comme un nombre.

Là encore : demander une valeur à l’utilisateur, c’est l’affaire d’une interface, ça n’a rien avoir avec l’algorithme sous-jacent.

Et il y a bien d’autres manières d’obtenir des valeurs d’entrée sur lesquelles faire travailler nos algorithmes.

Quand on débute, cet aspect interactif est parfois amusant, mais il est bien plus important de savoir comment utiliser une valeur d’entrée de manière générique.

Booléens

Un dernier type de base, que nous n’avons pas encore croisé : les booléens. C’est le type à deux valeurs : vrai et faux.

   1 True
   2 False
   3 type(True)

On fabrique des booléens avec des tests :

   1 1 > 0
   2 1 < 0
   3 1 >= 1
   4 1 <= 0
   5 1 != 0
   6 1 == 0 # Attention, on double le signe = pour le distinguer de l’affectation
   7 'a' == 'b'
   8 'a' + 'a' == 'aa'

Et on peut les composer via les opérations booléennes and, or, not :

   1 'toto' == '' or len('toto') > 2 

Algorithmique de base

Jusque là, on a parlé des opérations de base du calcul. Reste à introduire l’algorithmique à proprement parler.

Le branchement conditionnel prend la forme if conditioninstruction. Vous pouvez deviner ce que fait :

   1 if x<0:
   2   x=-x

Si le branchement comprend une alternative, on utilise else :

   1 if x<0:
   2   y=0 
   3 else:
   4   y=x

Si plusieurs instructions doivent suivre, on les met ligne par ligne en respectant l’indentation :

   1 a, b, c = 1, -2, 1 # On utilise un raccourci pour l’affectation simultanée de plusieurs variables
   2 b = -b / 2
   3 delta = b * b - a * c
   4 if delta > 0:
   5         n  = 2
   6         x1 = (b - sqrt(delta)) / a
   7         x2 = (b + sqrt(delta)) / a
   8 else: 
   9         if delta == 0:
  10                 n  = 1
  11                 x  = b/a
  12         else:
  13                 n = 0

Cette convention trouble souvent les programmeurs de langages comme C ou Java. Elle a pourtant le mérite d’être concise, lisible et d’éviter la frappe de délimiteurs.

La structure else: if: imbriquée, peut-être abrégée en elif, ce qui économise un niveau d’indentation :

   1 if delta > 0:
   2         n  = 2
   3         x1 = (b - sqrt(delta)) / a
   4         x2 = (b + sqrt(delta)) / a
   5 elif delta == 0:
   6         n  = 1
   7         x  = b/a
   8 else:
   9         n = 0

La boucle tant que s’écrit naturellement sur le même modèle while condition:

   1 n = 0
   2 while n<10:
   3         print(n)
   4         n = n+1

Une source très courante d’erreurs en Python est le non respect des contraintes d’indentation. Précisons donc.

L’indentation est la nature et le nombre des caractères blancs (espaces et tabulations) situés avant le premier caractère d’une ligne de texte. Au lieu d’avoir des caractères de délimitation (comme { et } en C ou Java), Python définit un bloc d’instructions comme une suite de lignes avec la même indentation. Les sous-blocs d’une instruction structurée (branche, boucle, etc.) doivent avoir une indentation compatible et supérieure à celle de l’instruction.

Comparez:

n=0
i=0
while i<10:
  n = n + i
  i = i + 1
  print(n)

avec

n=0
i=0
while i<10:
  n = n + i
  i = i + 1
print(n)

Quelques subtilités

Essayez la chose suivante :

   1 def incremente(a):
   2         a = a+1
   3         print('Nouvelle valeur de a : '+str(a))
   4 
   5 a = 1
   6 incremente(a)
   7 print(a)

Que se passe-t-il ? Que pouvez-vous dire sur la portée des variables en Python ? Quelles autres expériences proposez-vous pour mieux comprendre ?

Un excellent outil pour étudier le fonctionnement de vos programmes est Python Tutor: celui-ci vous fournit en direct une représentation de la mémoire à chaque étape d’exécution.

Listes, chaînes, n-uplets

On a déjà décrit le cœur algorithmique, les opérations sur les types de base et le mécanisme d’affectation des variables. Légitimement, on pourrait s’arrêter là : on a déjà un modèle de calcul complet.

Mais Python, comme tout langage contemporain, nous permet de composer ces types de base en introduisant des types dits séquentiels : listes et n-uplets. Ceux-ci rassemblent un nombre quelconque de valeurs au sein d’un même objet composé. On forme un n-uplet avec des parenthèses :

   1 z = (1,2,3)

une liste avec des crochets :

   1 l = ['a','b','c']

On obtient des objets de types tuple et list respectivement.

En fait, les parenthèses ne sont pas nécessaires autour d’un n-uplet : c’est la virgule qui sert de constructeur.

   1 zz = 1,2,3
   2 z == zz

La longueur se calcule comme pour les chaînes:

   1 len(z)

On peut fabriquer des séquences vides:

   1 ()
   2 []

et des singletons:

   1 1,
   2 [1]

(notez la virgule nécessaire pour le 1-uplet). On accède aux éléments d’une séquence avec les crochets :

   1 print(z[1])
   2 print(l[0])
   3 print(l[1])
   4 print(l[2])
   5 print(l[3])

Que remarquez-vous ?

Et au fait, que se passe-t-il avec

   1 print(l[-1])

?

Les séquences sont la solution idéale pour renvoyer un nombre variable de résultats:

   1 def discriminant(a,b,c):
   2         return b * b - a * c
   3 
   4 def resout1(a,b):
   5         # résout l'équation ax+b=0 si a est non nul
   6         return -b/a
   7 
   8 def resout (a,b,c):
   9         # renvoie la liste des solutions de l'équation ax²+bx+c=0 si a ou b est non nul
  10         b = -b / 2
  11         if a == 0:
  12                 return [resout1(b,c)]
  13         delta = discriminant(a,b,c)
  14         if delta > 0:
  15                 x = (b - sqrt(delta)) / a
  16                 y = (b + sqrt(delta)) / a
  17                 return [x,y]
  18         elif delta == 0:
  19                 return [b/a]
  20         else:
  21                 return[]
  22 
  23 def  nbsol(a,b,c):
  24         return len(resout(a,b,c))

On peut concaténer les séquences avec le +, comme dans :

   1 l = l + ['d']

On peut également fabriquer une sous-séquence, en donnant un intervalle plutôt qu’un numéro de case:

   1 l[1:3]
   2 "bonjour"[2:5]
   3 (1,2,3)[2:2]

Notez que machin[a:b], est la sous-séquence de machin, de la position a incluse à la position b non incluse. On peut aussi utiliser des positions négatives, et si on oublie une borne, ça va jusqu’au bout. Ainsi on peut définir:

   1 def découpe(sequence):
   2     n = len(sequence)//2
   3     début = sequence[:n]
   4     fin = sequence[n:]
   5     return début,fin

qui découpe une séquence en deux morceaux de longueur à peu près égales.

La différence entre les deux types list et tuple, c’est qu’on peut changer les éléments d’une liste, mais pas ceux d’un n-uplet :

   1 l[0] = 'A'
   2 z[0] = '0' # ça plante

On dit que les éléments d’une liste sont mutables.

En fait, les chaînes de caractères sont en partie de la même nature que les n-uplets : ce sont des suites… de caractères. Et on ne peut pas les changer.

   1 s = 'Bonjour.'
   2 s[0]
   3 s[0]='b' # ça plante

La valeur d’une liste, c’est l’adresse en mémoire du tableau qui contient ces valeurs! Ainsi, si on écrit

   1 l = list("bonjour")
   2 m = l

non seulement les valeurs de m sont les mêmes que celles de l, mais quand on fait

   1 m[0] = "B"

on a aussi l[0] == "B".

La représentation des listes Python Tutor est particulièrement éclairante. Testez-y le programme suivant:

   1 def zero_partout(l):
   2     for i in range(len(l)):
   3         l[i] = 0
   4     return l
   5 une_liste = [1, 2, 3]
   6 une_autre_liste = zero_partout(une_liste)

et essayez d’expliquer la valeur de une_liste à la fin du programme.

Le programme de seconde n’insiste pas sur la notion de liste: on pourra se contenter de les voir comme une manière pratique de manipuler un ensemble de valeurs, et non pas comme des objets qu’on a envie de générer ou modifier de manière algorithmique. En particulier, à ce niveau, on peut passer sous silence le concept même d’affectation d’une nouvelle valeur à un élément de liste.

Une autre manière de modifier une liste en place, c’est d’utiliser les méthodes append, pop, etc. Par exemple :

   1 def mon_range(n):
   2     l = []
   3     i = 0
   4     while i<n:
   5         l.append(i*i)
   6         i = i+1

est une manière un peu tordue de générer la liste des entiers de 0 à n-1.

L’intérêt de ces méthodes est leur efficacité en temps et en mémoire. Quand on écrit :

   1 l.append(a)

la valeur de a est ajoutée dans une nouvelle case à la fin de l, tandis que quand on écrit

   1 l = l + [a]

on construit une nouvelle liste, qui contient les éléments de l plus le nouvel élément, puis on donne ça comme nouvelle valeur de l. Pour visualiser la différence, omparez dans Python Tutor ce que font les deux programmes :

   1 l = [1,2,3]
   2 m = l + [4]

et

   1 l = [1,2,3]
   2 m = l
   3 m.append(4)

Encore une fois, tout ça (et en particulier les méthodes) est exclu du programme de seconde.

La boucle for de Python permet de répéter un bloc d’instructions pour chacun des éléments d’une séquence. Par exemple :

   1 for k in 1,2,3:
   2         print(k)
   3 s = ''
   4 for k in 'Un truc.':
   5         s = s + k + k
   6 print(s)

En fait, on peut librement mélanger les types au sein d’une séquence :

   1 for k in 1, 'a', 100.0**.5 :
   2         print(k)

Et on peut même imbriquer les séquences :

   1 def mat22 (a,b,c,d):
   2         return((a,b),(c,d))
   3 M=mat22(1,2,3,4)
   4 M[1][0]
   5 def affiche22 (M):
   6         for l in M:
   7                 print(l)
   8 affiche22(M)

La fonction range, très souvent utile, permet de générer une valeur qui se comporte comme une liste d’entiers consécutifs. Essayez-la :

   1 for i in range(10):
   2         print(i)
   3 for i in range(1,10):
   4         print(i)

Notez que certains arguments des fonctions fournies par Python peuvent être optionnels. Pour l’instant, on s’en tiendra à la non-explication « magique »: range peut recevoir un ou deux arguments, et print peut en recevoir un nombre quelconque.

On peut obtenir une liste triée des éléments de n’importe quelle séquence avec la fonction sorted:

   1 sorted([3,1,2])==[1,2,3]

Enfin, une curiosité pythonesque : les listes en compréhension. On peut construire une liste (ou un tuple) en énumérant les valeurs prises par une expression lorsqu’on fait varier un paramètre, possiblement en sélectionnant les valeurs du paramètre qui satisfont un test :

   1 [i*i*i for i in range(10)]

est la liste des cubes des entiers de 0 à 9 et

   1 [i*i*i for i in range(10) if i%3>0]

est la liste des cubes des entiers de 0 à 9 qui ne sont pas des multiples de 3.

Fin

Si vous êtes arrivés jusque là, vous avez toutes les briques pour traiter l’algorithmique courante (et même plus) en Python 3. :) Reste à pratiquer, avec les exercices que vous n’auriez pas encore traités.

EnsInfo: Initiation à Python 3 (dernière édition le 2021-03-19 14:09:28 par LionelVaux)