site de Fabien Torre


Notes de cours sur le langage Python

Introduction à la programmation en Python : syntaxe générale, structures de données, méthodes de tri, manipulation de fichiers en Python et modules importants (pour la documentation et les tests du code, l'aléatoire, les expressions régulières, la manipulation de documents xml, le traitement automatique de la langue, etc.).

Contexte du cours

Dans ce cours, nous passons d'abord en revue les éléments de base du langage Python, en parallèle avec le langage de description vu en cours d'algorithmique (variables, types, structures de contrôle, procédures et fonctions, etc.).

Nous nous focaliserons ensuite sur les traitements des textes à l'aide de Python : chaînes de caractères, gestion des fichiers, expressions régulières, manipulation des documents semi-structurés (XML) et traitement automatique des langues (TAL) avec le module NLTK de Python.

Les éléments du langage Python

Syntaxe Python, variables et types simples

Instructions et blocs d'instructions

Dans le langage Python, chaque instruction occupe une ligne : il n'y a pas de symbole de fin, nous passons simplement à la ligne après chaque instruction. C'est la règle générale mais nous verrons qu'il est possible d'écrire une chaîne de caractères ou une condition sur plusieurs lignes pour une meilleure lisibilité du code Python.

Les blocs d'instructions ne sont pas délimités eux non plus par un symbole particulier mais répérés par l'indentation des instructions :

  • des instructions qui sont dans le même bloc ont le même nombre d'espaces à leur gauche,
  • pour marquer le début d'un nouveau bloc, on va rajouter quelques espaces par rapport à l'instruction précédente.

Idéalement, il faut utiliser un éditeur qui, après un appui sur la touche tabulation du clavier, amène le curseur à l'endroit adéquat en produisant le nombre d'espaces nécessaires.

Types et variables

Python autorise la manipulation de types classiques : booléens (bool), entiers (int), réels (float) et chaîne de caractères (str). À noter que les noms des types indiqués sont aussi des opérateurs de conversion.

Ni les variables ni leurs types n'ont besoin d'être déclarés.

msg = "toto"  # déclaration et affectation d'une chaîne de caractères
pi  = 3.14    # déclaration et affectation d'un nombre

Notons la syntaxe permettant d'écrire des commentaires dans le code Python : le signe # indique que la suite de la ligne n'est pas destinée à Python mais à un lecteur humain. Nous verrons que des commentaires bien formatés permettent aussi la production de commentaires et des tests automatiques.

Comme suggéré ci-dessus, le symbole = est réservé à l'affectation d'une valeur à une variable. Le symbole ==, lui, permet d'exprimer un test d'égalité qui ne modifie en rien les variables.

Comme déjà évoqué, lorsque nous souhaiterons des conversions explicites d'un type à l'autre, nous utiliserons str pour obtenir une chaîne de caractères (à partir d'un nombre) et int pour obtenir un entier (à partir d'un texte par exemple).

pitxt    = str(pi)         # obtention de la chaîne de caractères "3.14"
sixtxt   = "6"             # une chaîne de caractères
résultat = 7 * int(sixtxt) # ... convertie en entier pour calcul
réponse  = str(résultat)   # ... et retour en chaîne pour affichage

Opérateurs

Nous disposons en Python des opérateurs arithmétiques classiques : +, -, *, /, // pour la division entière et % pour l'opération modulo (reste de la division).

salaire      = 2000
augmentation = 7 * salaire / 100

Nous retrouvons également les habituels opérateurs logiques (and, or et not) pour manipuler les booléens qui, en Python, sont notés True et False.

ok = False
ko = not(ok)

Premières chaînes de caractères et instruction d'affichage

En Python, les chaînes de caractères doivent être délimitées, au choix, par des apostrophes ou des guillemets.

"coucou hé !"
'ça va ?'

Nous reviendrons en détail sur la manipulation des chaînes de caractères en Python dans une section dédiée.

Les affichages sont réalisés à l'aide de l'instruction print() : elle affiche les éléments qui lui sont confiés puis passe à la ligne, les éléments sont séparés par un espace.

print("coucou hé !")  # affiche et passe à la ligne
print("toto","titi")  # titi suit toto, passage à la ligne
print()               # simple passage à la ligne

Le passage à la ligne en fin d'affichage et l'espace séparateur sont des paramètres de print que l'on peut modifier. Voici quelques exemples qui illustrent différentes utilisations possibles de print.

print("toto",end="!")        # termine par un ! et pas par un passage à la ligne
print("titi")                # titi colle à toto et passage à la ligne
print("lulu","lala",sep="+") # affiche lulu+lala et passe à la ligne

Par ailleurs, l'instruction print nous permet deux observations sur les procédures en Python :

  1. un paramètre peut être optionnel avec une valeur par défaut,
  2. les paramètres peuvent être nommés au moment de l'appel à une procédure, on s'affranchit ainsi de l'ordre fixé dans la définition de la procédure.

Structures de contrôle en Python

Nous retrouvons en Python les structures de contrôle classiques, en particulier celles vues en cours d'algorithmique.

Structure conditionnelle : le si alors sinon

Le si alors sinon introduit une condition et deux blocs d'instructions : le premier bloc sera exécuté si la condition est vraie, le second si la condition est fausse. C'est l'occasion d'observer le délimitation des blocs en Python qui utilise l'indentation, c'est-à-dire le décalage et l'alignement des instructions d'un même bloc vers la droite.

# forme générale
if (une condition ici):
    # ...
    # des instructions ici
    # ...
else:
    # ...
    # des instructions ici
    # ...

# affichages distincts selon un test
temperature = 28
if temperature>25:
    print('il fait chaud !')
else:
    print('il fait frais...')

Si le second bloc ne contient pas d'instructions, on peut se dispenser du else :

# affichage si un test est vrai
temperature = 28
if temperature>25:
    print('il fait chaud !')

La condition doit être interprétée à vrai ou à faux, c'est-à-dire comme un booléen. On peut donc y trouver des variables booléennes et des opérateurs booléens.

if (temperature>25) and (temps=="ensoleillé") :
    print("il fait chaud !")
else:
    print("c'est mitigé")

Si l'on doit enchaîner plusieurs tests, Python propose elif en plus de if et de else :

# enchaînement de tests
temperature = 28
if temperature>25:
    print('il fait chaud !')
elif temperature>20:
    print('il fait bon.')
else:
    print('il fait frais...')

Les elif peuvent être utilisés en nombre quelconque, ce qui permet de limiter l'imbrication du code et donc d'augmenter sa lisibilité.

En Python, une condition doit s'exprimer sur une seule ligne mais, encore une fois pour faciliter la lisibilité du code, il est possible de masquer la fin de ligne à l'aide du caractère d'échappement backslash (signe \) et ainsi étaler des tests sur plusieurs lignes.

if    (lettre == 'a') or (lettre == 'e') \
   or (lettre == 'i') or (lettre == 'o') \
   or (lettre == 'u') or (lettre == 'y') :

    print("«",lettre,"» est une voyelle.")
	
else :

    print("«",lettre,"» N'est PAS une voyelle.")
	

Structure itérative : la boucle tant que

On retrouve la boucle tant que avec sa condition et le bloc d'instructions à exécuter tant que la condition est vraie, bloc indenté vers la droite.

# forme générale
while (une condition ici):
    # ...
    # des instructions ici
    # ...

# exemple : on compte de 1 à 10
i = 1
while i<=10:
    print(i)
    i = i + 1

Structure itérative : la boucle pour

Dans une utilisation courante, la boucle for ... in ... parcourt un intervalle défini par l'instruction range.

  • range peut prendre jusqu'à trois arguments entiers : début, fin et pas.
  • range va fournir les entiers depuis min, avancer de pas en pas et s'arrêter avant fin.
  • début et pas sont optionnels, ils valent par défaut respectivement 0 et 1.
  • pas peut être négatif mais pas nul.
# forme générale
for i in range(début,fin,pas):
    # ...
    # des instructions ici
    # ...

# on compte de 0 à 4
for i in range(5):
    print(i)

# on compte de 1 à 10
for i in range(1,11):
    print(i)

# on compte à rebours 5 à 0
for i in range(5,-1,-1):
    print(i)

# affichage de 10 étoiles
for i in range(10):
    print('*')

Une utilisation plus générale de cette boucle est possible, lorsque l'objet donné après le in est itérable. C'est le cas du range et il en sera de même pour les chaînes de caractères et pour les listes.


Fonctions/procédures, modules, objets

Nous nous intéressons aux différents moyens proposés par Python pour mettre en commun, rassembler, isoler et protéger du code. Même si l'on ne se place pas en créateur de ces objets, il est difficile d'en éviter l'utilisation en Python et il est donc important d'en comprendre la syntaxe.

Procédures et fonctions

Les procédures et les fonctions ont en commun de rassembler des instructions pour des réutilisations futures. Elles utilisent des paramètres pour généraliser le code. Une fonction doit toujours se terminer par le renvoi d'un résultat (à l'aide du mot-clef return en Python), ce qui n'est jamais le cas pour une procédure. Cela signifie en particulier qu'un appel à une fonction se trouvera souvent dans la partie droite d'une affectation, en revanche un appel à une procédure ne pourra pas être à cette position.

# forme générale d'une procédure
def nom_de_la_procédure (paramètres):
    # ...
    # des instructions ici
    # ...

# procédure qui affiche entre étoiles un texte donné
def affiche_joliment (msg):
    print('***',msg,'***')

# on appelle la procédure et on renseigne son paramètre
affiche_joliment('coucou')

En Python un paramètre peut être optionnel, il convient alors de préciser la valeur à utiliser si elle n'est pas précisée lors de l'appel. De plus, les paramètres peuvent être nommés au moment de l'appel à une procédure, on se dispense ainsi de respecter l'ordre fixé dans la définition de la procédure.

# procédure qui affiche un carré
def carré (taille, symbole="*"):
       for l in range(taille):
           for c in range(taille):
               print(symbole,end="")
           print()

# appels à la procédure
carré(10,"·")
carré(5)
carré(symbole="o",taille=7)

Comme indiqué, une fonction se doit de renvoyer un résultat avec l'instruction return.

# forme générale d'une fonction
def nom_de_la_fonction (paramètres):
    # ...
    # des instructions ici
    # ...
    return(résultat)

# fonction qui calcule la somme de deux entiers et la renvoie
def addition (a,b):
    s = a+b
    return(s)

L'appel à cette fonction pourra se trouver dans la partie droite d'une affectation ou être impliqué dans un test.

# on appelle la fonction en renseignant ses paramètres et on récupère le résultat
somme = addition(2,2)
print('le résultat est',somme)
print('le résultat est',addition(1,3))

# appel dans un test
if addition(20,42)>50:
    print("c'est grand !")
else:
    print("c'est petit")

Enfin, il est possible en Python d'associer aux procédures et aux fonctions des commentaires bien formatés qui permettent la production de documentation et des tests automatiques.

Accès aux éléments d'un module

Un module est un fichier qui regroupe des variables, des procédures et des fonctions, pour que celles-ci puissent être utilisées dans des scripts Python différents. Plusieurs syntaxes sont possibles pour charger ces modules et utiliser les procédures/fonctions en provenance de ces modules.

# nous précisons la fonction qui nous intéresse dans le module
from module import fonction
...
# appel de la fonction sans rappeler le nom du module
fonction()
...
# plusieurs fonctions sont récupérées et appelées
from module import f1,f2
...
f1()
...
f2()
...
# toutes les fonctions du module sont récupérées et appelées
from module import *
...
f1()
...
f2()
...
f3()
...

Il peut devenir délicat de comprendre l'exécution d'un code Python si l'on importe une variable ou une fonction depuis un module alors que notre propre code définit un objet du même nom, avant ou après l'importation du module. Il est préférable d'utiliser les espaces de noms (comme en XML) pour préciser systématiquement le module d'origine d'une variable ou d'une fonction.

# on va utiliser des fonctions du module
import module
...
# on précise à nouveau le nom du module
# avec chaque utilisation d'une fonction de ce module
module.fonction()
...

Voyons un exemple avec les fonctions mathématiques proposées par Python à travers le module math.

import math         # importation du module mathématique de Python

math.sqrt(2)        # utilisation de la fonction racine carrée du module math
math.floor(math.pi) # arrondi de la valeur de π

Il est possible, à l'aide du mot-clef as, de définir alias au nom du module, plus court ou plus informatif.

import math as mth # importation du module mathématique sous le nom « mth »
mth.sqrt(2)        # utilisation de la racine carrée du module« mth »

import math as mathématiques # importation sous le nom « mathématiques »
mathématiques.sqrt(2)        # utilisation de la racine carrée du module « mathématiques »

Nous verrons plus loin, en détail, des modules Python d'utilisation courante.

Syntaxe objet

En programmation objet, les variables embarquent en elles des caractéristiques qui les décrivent (les propriétés de l'objet) et des procédures et fonctions qui permettent de les traiter (les méthodes de l'objet).

Nous n'étudions pas dans le cadre de ce cours les techniques de programmation orientée objet que permettent Python. Cependant, nous sommes contraints d'utiliser la syntaxe objet pour manipuler certaines variables Python, qui de fait sont des objets. La syntaxe pour appeler une méthode d'un objet est : le nom de la variable, suivi d'un point, suivi d'un appel à la procédure ou à la fonction propre à l'objet.

Sur l'exemple suivant, nous voyons que les chaînes de caractères sont en Python des objets, et que l'on peut s'adresser à leurs méthodes.

prénom = "Fabien"  # création d'un objet chaîne de caractères
prénom.upper()     # demande à l'objet de fournir une version en majuscules de lui-même

Nous retrouvons la syntaxe pointée et les espaces de noms vus avec les modules, mais objet et module sont bien deux notions différentes.


Fichiers en Python

Il y a plusieurs manières en Python d'interagir avec les fichiers. Nous présentons ici l'instruction open et la méthode close qui permettent respectivement d'ouvrir et de fermer un flux vers un fichier. Pour l'instruction open, il conviendra de préciser le nom du fichier visé, le mode d'ouverture (lecture ou écriture) et l'encodage du fichier (le plus souvent utf-8).

Dans le cas d'un fichier en écriture, le mode d'ouverture est noté w. Pour l'écriture elle-même, nous avons choisi de conserver l'instruction print avec précision du flux dans lequel écrire.

# ouverture du fichier en écriture
flux_out = open('premier.html',mode='w',encoding="utf-8")

# affichage dans le flux ouvert
print("<h1>Premier essai d'écriture</h1>",file=flux_out)

# fermeture du flux vers le fichier
flux_out.close()

Concernant un fichier en lecture, le mode à utiliser est noté w. Nous mettons ensuite en œuvre une boucle tant que pour lire le fichier ligne par ligne. Cela se fait avec l'opérateur morse (walrus operator) de Python : celui-ci permet l'affectation d'une valeur à une variable dans une expression plus large, par exemple dans le test d'un if ou d'un while. Ici il nous permet de tester s'il reste des lignes à lire dans le fichier et de récupérer la prochaine ligne en même temps. Chaque ligne est obtenue avec la commande readline (notons qu'une ligne arrive logiquement avec, pour la terminer, le caractère de fin de ligne).

# ouverture du fichier en lecture
flux_in = open('horla.txt',mode='r',encoding="utf-8")

# boucle tant que sur les lignes du flux
while ligne := flux_in.readline():
    print(ligne) # on réaffiche la ligne et, ici, on passe deux fois à la ligne

# fermeture du flux vers le fichier
flux_in.close()

Nous verrons qu'il est aussi possible de capter en Python le flux produit par un autre programme en train de s'exécuter (avec l'instruction popen du module os).


Les structures de données en Python

Chaînes de caractères en Python

Écriture des chaînes de caractères en Python

Le langage Python permet de délimiter les chaînes de caractères soit par des apostrophes, soit par des guillemets. Il est pratique d'utiliser les guillemets lorsque le texte contient une apostrophe, et vice-versa.

prénom = 'Toto'
print("salut",prénom)
print("ça va aujourd'hui ?")

Il est aussi possible d'utiliser le caractère d'échappement backslash (signe \) pour changer la signification Python du caractère qui le suit. Cela permet d'indiquer qu'un guillemet ou une apostrophe ne sont pas des délimitations de fin de chaînes mais des caractères quelconques à l'intérieur de la chaîne. Cela permet également d'ajouter des instructions dans les chaînes de caractères comme le passage à la ligne (\n) ou la tabulation (\t).

print('oui, ça va aujourd\'hui !')  # échappement de l'apostrophe grâce au backslash
print("\t super !")                 # ajout d'une tabulation en début de ligne

En conséquence, le caractère backslash a une signification particulière et doit être lui aussi protégé (par un backslash !) si l'on veut simplement l'inclure dans une chaîne de caractère. Python propose une autre solution, les raw strings ou chaînes brutes dans lesquelles il est convenu que le backslash n'a pas de signification particulière et n'ont donc pas besoin d'être protégés. Ces chaînes sont préfixées par la lettre « r ».

# deux variables qui contiennent la même chaîne de caractère : 'Voici un backslash protégé : \\ ok ?'
info_1 = "Voici un backslash protégé : \\ ok ?"
info_2 = r"Voici un backslash protégé : \ ok ?"

# print donne des affichages identiques
print(info_1) # affichage : Voici un backslash protégé : \ ok ?
print(info_2) # affichage : Voici un backslash protégé : \ ok ?

texte_1 = r"\n" # définition d'une chaîne de caractères brute
texte_1         # contient \\n
len(texte_1)    # donne la valeur 2
print(texte_1)  # affiche \n

texte_2 = "\n"  # définition d'une chaîne de caractères ordinaire
texte_2         # contient \n
len(texte_2)    # donne la valeur 1
print(texte_2)  # affiche une ligne vide (deux passages à la ligne)

Ces raw strings seront particulièrement pertinentes quand nous aurons à écrire des expressions régulières.

Il y aussi en Python les chaînes formatées, préfixées par la lettre f. Elles permettent d'abord d'embarquer des expressions Python dans les chaînes de caractères. Ces expressions sont placées entre accolades, elles sont évaluées et le résultat remplace les accolades et leur contenu dans la chaîne. À noter les expressions particulières qui se terminent par le symbole égal (=).

f"essai 1 : {2+2}"    # "essai 1 : 4"
f"essai 2 : {2+2 = }" # "essai 2 : 2+2 = 4"

prénom = "Fabien"
f"Je m'appelle {prénom}."         # "Je m'appelle Fabien."
f"Je m'appelle {prénom.upper()}." # "Je m'appelle FABIEN."
f"{prénom.lower() = }"            # "prénom.lower() = 'fabien'"

Dans les chaînes formatées, il est possible d'ajouter des indications de formatage pour chaque expression entre accolades. Le format souhaité se place lui aussi dans les accolades : à la suite de l'expression à évaluer, en les séparant par le caractère « deux points » (:). Ces formats permettent de spécifier des alignements, des séparateurs, des places occupées, des conversions, etc.

Certains formats sont dédiés aux textes...

f"Prénom : {'Fabien':<10} !"  # "Prénom : Fabien     !"
f"Prénom : {'Fabien':>10} !"  # "Prénom :     Fabien !"
f"Prénom : {'Fabien':^10} !"  # "Prénom :   Fabien   !"

... et d'autres aux nombres.

f"Résultat : {1/4:+}"         # "Résultat : +0.25"
f"Résultat : {-1/4:+}"        # "Résultat : -0.25"

f"Prix : {45000:,} €."        # "Prix : 45,000 €."
    
f"Pourcentage : {0.225:5.2%}" # "Pourcentage : 22.50%"

f"Entier : {2*12:10d} !"      # "Entier :         24 !"
f"Entier : {2*12:<10d} !"     # "Entier : 24         !"

f"Réel : {1/3:6.2f} !"        # "Réel :   0.33 !"
f"Réel : {1/3:<6.2f} !"       # "Réel : 0.33   !"										  

Enfin, Python autorise les chaînes de caractères sur plusieurs lignes : la convention est d'encadrer ces chaînes par des triples guillemets (""").

"""coucou
ça va ?
ou bien ?
"""
# c'est la chaîne de caractères 'coucou\nça va ?\nou bien ?\n'

Opérations sur les chaînes de caractères en Python

Codage et décodage des caractères en Python. Il est possible de repérer un caractère par son numéro UNICODE. Par exemple, les lettres de a à z en minuscules correspondent aux codes de 97 à 122. La fonction Python chr permet de passer de l'entier au caractère correspondant, la fonction ord réalise la conversion inverse.

print(chr(100)) # affichage du caractère 100 : "d"
print(chr(425)) # affichage du caractère 425 : "Ʃ"

print(ord("m")) # affiche le code UNICODE du "m" : 109
print(ord("œ")) # affiche le code UNICODE du "œ" : 339

De premières comparaisons entre chaînes de caractères peuvent être réalisées avec < et >... mais ces comparaisons sont basées sur les codes UNICODE des caractères !

ord("a"), ord("b"), ord("â"), ord("z") #(97, 98, 226, 122)

"a" < "b" # True
"â" < "z" # False

Nous verrons plus loin une méthode plus pertinente, basée sur l'ordre alphabétique de la langue manipulée.

Sur les chaînes de caractères, nous trouvons l'opérateur de concaténation noté par un plus (symbole +, comme pour l'addition entre entiers) et un opérateur de répétition des chaînes avec un fois (symbole *, comme pour la multiplication entre entiers).

température = 25
    
texte       = 'La variable « température » vaut ' + str(température)
texte       = "\n" + texte + "\n"
print(texte)

print("coucou ! " * 3) # répéter une chaîne : coucou ! coucou ! coucou !

La longueur d'une chaîne de caractères est obtenue à l'aide de la fonction len.

len("héhéhé")               # 6
len("coucou")+len("Fabien") # 12
len("coucou"+"Fabien")      # 12

température = 25
texte       = 'La variable « température » vaut ' + str(température)
len(texte)  # 35

Pour accéder à un caractère d'une chaînes de caractères, Python propose la syntaxe « crochets » ([]), laquelle permet de préciser la position du caractère qui nous intéresse. La numérotation des caractères commence à 0.

prénom = "Fabien"
print(prénom[0]) # "F"
print(prénom[5]) # "n"

Cette notation se généralise en Python pour permettre l'extraction de sous-chaînes de caractères, on parle alors de slices (des « tranches »).

# syntaxe générale pour les slices
chaîne[début:fin:pas]
# construit une nouvelle chaîne avec les caractères extraits entre début et fin, en avançant de pas en pas

prénom = "Fabien"

print(prénom[0:2]) # "Fa"
print(prénom[5:6]) # "n"

# un caractère sur deux, dans un sens puis dans un autre
print(prénom[0:6:2])  # "Fbe"
print(prénom[5:0:-2]) # "nia"

# début et fin sont optionnels et ont donc des valeurs par défaut
print(prénom[2:])   # "ien"
print(prénom[:3])   # "Fab"
print(prénom[:])    # "Fabien"
print(prénom[::-1]) # "neibaF"

# on peut se repérer par rapport à la longueur de la chaîne...
print(prénom[len(prénom)-2:]) # "en"
# ... et, en raccourci, indiquer des positions négatives
print(prénom[-2:])            # "en"

Les chaînes de caractères en Python ne sont pas modifiables. Il est donc impossible d'utiliser la syntaxe « crochets » pour modifier un caractère particulier dans une chaîne. Toute modification implique alors la création d'une nouvelle chaîne de caractères. Ainsi, modifications/suppressions dans les chaînes de caractères passent par les opérations déjà vues (concaténation et multiplication), ou par l'extraction des parties à conserver avec des slices suivie de la construction d'une nouvelle chaîne. Éventuellement, la nouvelle chaîne peut être stockée dans la variable d'origine.

question = "ça va"
question = question + " ?"    # ajout d'un caractère en fin : "ça va ?"

question = "Ç" + question[1:] # changement de la première lettre : "Ça va ?"

nq = question[:5] + " ou bien" + question[5:] # nouvelle question : "Ça va ou bien ?"

Ci-dessous, quelques méthodes des chaînes de caractères qui nous seront utiles.

  • lower, upper, capitalize et title : passent des caractères de la chaîne en minuscules ou majuscules,
  • strip, rstrip et lstrip : suppriment les caractères spécifiés, par défaut des espaces au sens large (espace, tabulation, passage à la ligne, etc.), en début ou en fin de chaîne,
  • replace : remplace toutes les occurrences d'une sous-chaîne dans une chaîne,
  • find : fournit la position de la première occurrence d'une sous-chaîne (-1 si la cible n'est pas présente).
# jeux sur les majuscules/minuscules
phrase = "Je m'appelle Fabien."
phrase.upper()      # "JE M'APPELLE FABIEN."
phrase.lower()      # "je m'appelle fabien."
phrase.title()      # "Je M'Appelle Fabien."
phrase.capitalize() # "Je m'appelle fabien."

# la chaîne de caractères d'origine n'a pas été modifiée
phrase # "Je m'appelle Fabien."

# texte sur plusieurs lignes puis nettoyage des espaces au début ou à la fin
texte = """
    coucou
bonjour chez vous !

"""
texte.strip()  # "coucou\nbonjour chez vous !"
texte.lstrip() # "coucou\nbonjour chez vous !\n\n"
texte.rstrip() # "\n    coucou\nbonjour chez vous !"

# nettoyage du début et de la fin des caractères précisés
phrase.strip("eJn. ") # "m'appelle Fabi"

# remplacement
phrase.replace("e","X") # "JX m'appXllX FabiXn.

# trouve la position d'une sous-chaîne
phrase.find("a")    # 5
phrase.find("e ")   # 1
phrase.find("rien") # -1

La méthode rsrtip sera particulièrement utile lors de la lecture d'un fichier, pour enlever les caractères de fin de ligne.

# boucle tant que sur les lignes du flux
while ligne := flux_in.readline():
    ligne = ligne.rstrip() # on débarrasse la ligne de son retour chariot
    print(ligne)

Pour effectuer, le parcours d'une chaîne de caractères nous retrouvons une boucle for ... in ... :

# définition d'une chaîne de caractères
mot = "coucou"

# parcours d'une chaîne caractère par caractère
for lettre in mot:
    print(lettre)
# chaque lettre du mot est affichée sur une ligne différente

Plus proche de la méthode algorithmique, il est possible de boucler sur les indices des caractères et d'y accéder à l'aide de la notation « crochets » ([]). Mais cela est jugé peu « pythonique ».

# autre parcours caractère par caractère
for i in range(0,len(mot)):
    print(mot[i],end="-")
print()
# affiche "c-o-u-c-o-u-" et passe à la ligne

Nous avons vu en cours d'algorithmique différentes méthodes qui parcourt une chaîne de caractères (ou un tableau d'éléments quelconques), afin de déterminer si un caractère particulier est présent dans la chaîne ou non. En Python, nous pouvons naturellement implémenter ces algorithmes mais il existe déjà à cette fin l'opérateur Python in (lequel est probablement basé sur un algorithme étudié).

"a" in "Fabien" # True
"z" in "Fabien" # False

# opérateur in dans un if
mot = "chanter"
if "a" in mot:
    print("«",mot,"» contient un a.")
else:
    print("«",mot,"» NE contient PAS de a.")

Nous verrons d'autres instructions qui utilisent les chaînes des caractères au cours de l'apprentissage des listes.


Structure de données Python : les listes

Les listes sont l'équivalent des tableaux vus en algorithmique : des cases numérotées avec chacune un contenu.

En Python, il n'y a pas de contrainte sur le contenu des cases, en particulier ces contenus ne sont pas nécessairement du même type. Les cases sont numérotées à partir de 0. Il y a différentes manières de créer une liste et de lui affecter des valeurs.

# une liste de notes
notes = [5,12,8,20,10] # on définit une liste
notes[2] = 9           # on modifie la 2ème case
    
# création d'une liste vide et définition de trois cases
l = []
l.append(2)
l.append(3.14)
l.append('coucou')

# liste obtenue à partir d'un intervalle décrit par range (entiers de 1 à 10)
premiers = list(range(1,11)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# liste obtenue à partir d'une chaîne de caractères
lettres = list("coucou")     # ['c', 'o', 'u', 'c', 'o', 'u']

On note d'emblée une différence par rapport aux chaînes de caractères : on peut modifier les listes, on dit qu'elles sont « mutables ».

Nous retrouvons l'instruction len qui cette fois fournit la taille d'une liste.

Python propose plusieurs syntaxes pour parcourir une liste, toutes basées sur la boucle for in.

# accès direct aux contenus des cases
for note in notes:
    print(note)

# en obtenant indices et contenus des cases
for i,note in enumerate(notes) :
    print("La case",i,"contient la note",note)

# en utilisant range et longueur de la liste pour obtenir les indices
for i in range(0,len(notes)) :
    print(notes[i])

Méthodes Python dédiées aux listes :

  • append : ajoute un élément en fin de liste,
  • pop : supprime le dernier élément d'une liste et le fournit,
  • list : copie une liste (ou assimilée) existante pour en créer une nouvelle,
  • insert : ajoute un élément à une position donnée d'une liste,
  • remove : efface la première occurrence d'un élément dans une liste.

On peut y ajouter l'instruction del de Python qui permet de supprimer un objet quelconque. Dans le cas des listes, del nous permet de supprimer une case d'après son numéro.

notes = [5,12,9,20,12,10] # définition d'une liste de notes
l = list(notes)           # faire une copie de la liste de notes

l.append(12)   # ajout de 12 en fin de liste
l.insert(3,14) # ajout de 14 en position 3
del(l[2])      # suppression de la case 2
l.remove(12)   # suppression du premier 12
l.pop()        # suppression du dernier élément
print(l)       # résultat des modifications : [5, 14, 20, 12, 10]

# la liste initiale n'a pas été modifiée
print(notes)   # donne [5, 12, 9, 20, 12, 10]

Afin de tester la présence d'un élément dans une liste, nous retrouvons l'opérateur in déjà vu sur les chaînes de caractères.

notes = [5,12,9,20,12,10]

# opérateur in dans un if
if 20 in notes:
    print("Quelqu'un a eu 20/20.")
else:
    print("Personne n'a eu 20/20.")

Reste à discuter des liens entre listes et chaînes de caractères. Il est facile de voir une chaîne de caractères comme une liste de caractères, et effectivement Python met en commun certaines opérations. Mais nous avons déjà noté que les listes étaient mutables, ce qui n'est pas le cas des chaînes de caractères.

L'opérateur + permet de fusionner des listes, tandis que l'opérateur * les répètent.

[1,2,3] + [4,5,6] # fusion des listes : [1, 2, 3, 4, 5, 6]

[1,2,3] * 3       # répétition d'une liste : [1, 2, 3, 1, 2, 3, 1, 2, 3]

Terminons avec deux opérations intéressantes qui font le lien entre listes et chaînes de caractères :

  • split : découpe une chaîne de caractères en une liste de sous-chaînes.
  • join : concatène les chaînes de caractères d'une liste en une nouvelle chaîne unique.

Dans les appels à ces deux fonctions, un séparateur entre en jeu.

# découpages avec split
phrase = "Je m'appelle Fabien."
phrase.split()    # découpage par défaut selon les espaces : ["Je", "m'appelle", "Fabien."]
phrase.split("'") # découpage selon le séparateur spécifié : ["Je m", "appelle Fabien."]

# concaténation avec join
mots = ["ceci","est","un","dernier","exemple"]
"".join(mots)  # concaténation sans séparateur : "ceciestundernierexemple"
" ".join(mots) # concaténation avec l'espace comme séparateur : "ceci est un dernier exemple"

séparateur = "-"
séparateur.join(mots) # concaténation avec une variable pour stocker le séparateur

Structure de données Python : les ensembles

Manipulation des ensembles Python

Un ensemble, set en Python, est comme son nom l'indique une collection d'éléments, non ordonnée et sans doublon.

En Python, on obtient un ensemble, au choix :

  • avec le constructeur set() pour définir un ensemble vide ou à partir d'un objet d'un autre type,
  • avec les accolades (symboles {}) pour définir explicitement un ensemble d'au moins un élément.
# définitions d'ensembles Python
set()         # ensemble vide : { }
set("coucou") # à partir d'une chaîne de caractères : {'c', 'o', 'u'}

{ "coucou" , "salut"} # ensemble de deux éléments

Python met à disposition des méthodes sur les ensembles pour ajouter et supprimer des éléments. Naturellement, nous avons aussi les opérations ensemblistes usuelles. Enfin, nous retrouvons len pour connaître le nombre d'éléments présents dans un ensemble.

# ajouts et suppressions dans les ensembles
lettres = set("coucou") # {'u', 'o', 'c'}
lettres.add("x")        # {'x', 'u', 'o', 'c'}
lettres.add("x")        # {'x', 'u', 'o', 'c'}, pas de doublon dans un ensemble
lettres.remove("u")     # {'x', 'o', 'c'}

# accès à la taille de l'ensemble
len(lettres) # 3

# méthodes ensemblistes qui produisent de nouveaux ensembles
s1 = {1,2,3}
s2 = {3,4,5}
s1.intersection(s2) # {3}
s1.union(s2)        # {1, 2, 3, 4, 5}
s1.difference(s2)   # {1, 2}
s2.difference(s1)   # {4, 5}

Nous pouvons parcourir les ensembles Python, simplement avec une boucle for ... in ..., mais sans garantie sur l'ordre d'apparition des éléments.

# forme générale d'une boucle parcourant un ensemble
for élément in ensemble:
    print(élément)

Terminons avec l'opérateur in qui permet ici de tester si un élément est présent un ensemble.

amis = {'Lili', 'Toto', 'Baba'}
    
"Toto" in amis # True
"Lulu" in amis # False

Dans la section suivante, nous discutons de cet opérateur in dans le contexte des ensembles, ainsi que d'une utilisation possible.

Test d'existence rapide à l'aide des ensembles Python

Rappelons que l'opérateur in fonctionne sur les chaînes de caractères, sur les listes et sur les ensembles. De plus, la syntaxe Python seule ne permet pas toujours de distinguer au premier coup d'œil si l'opérateur in travaille sur une liste ou sur un ensemble. Cependant, ce sont des algorithmes bien distincts qui sont utilisés dans chacun de ces cas et les performances sont bien différentes.

Illustrons cela avec le cas où l'on souhaite tester la présence d'un mot dans une liste de mots. Il s'agit par exemple de faire de la vérification orthographique. Dans ce cas, nous avons besoin d'une liste de mots français autorisés. Si celle-ci est stockée dans un fichier, il suffit de la lire et de la charger dans un ensemble Python.

liste_mots = set() # création d'un ensemble vide

flux_dico = open('liste-mots.txt',mode='r',encoding="utf-8")

while ligne := flux_dico.readline():
    mot = ligne.rstrip().lower() # nettoyage et normalisation du mot
    liste_mots.add(mot)          # ajout du mot à l'ensemble

flux_dico.close()

Nous pouvons maintenant tester simplement l'existence d'un mot dans l'ensemble créé, à l'aide de l'opérateur in.

if mot in liste_mots : # on teste l'existence
    print("«",mot,"» est connu et correct.")
 else :
    print("«",mot,"» est inconnu.")

Remarquons que ce code est exactement le même, que liste_mots soit une liste Python ou un ensemble Python (avec des temps de calcul bien différents comme on l'a dit).


Structure de données Python : les dictionnaires

Les dictionnaires constituent les équivalents des tableaux associatifs, réalisés par des tables de hachage, évoqués en algorithmique. Ils sont proches des listes Python mais avec la particularité que les cases ne sont plus indicées par des numéros mais par des objets plus complexes, comme des chaînes de caractères. Ces textes qui servent d'indices sont appelés les clefs du dictionnaire.

Un dictionnaire peut être défini à vide ou avec des valeurs explicites, dans les deux cas on utilise des accolades (symboles {}) (pour distinguer les dictionnaires des listes qui utilisent les crochets). Il existe également le constructeur dict pour obtenir un dictionnaire à partir d'une liste de couples clef/valeur.

Pour récupérer le contenu d'une case, on retrouve la syntaxe crochets (avec une clef entre les crochets).

Premiers dictionnaires Python

Nous proposons, comme premier exemple d'utilisation des dictionnaires Python, la définition d'un carnet d'adresses mails : les clefs du dictionnaire sont les noms des personnes et les contenus des cases correspondent aux mails.

# définition d'un dictionnaire vide
mails = {}

# définition du mail de Titi
mails['Titi'] = 'titi@univ.fr'

# définition du mail de Toto
mails['Toto']  = 'toto.machin@free.fr'

# affichage du mail de Toto
print('Mail de toto :',mails['Toto']);

Autre exemple de dictionnaire Python, l'implémentation d'un type abstrait « produit », ou « enregistrement » vu en cours d'algorithmique : ici, un type « livre ».

# définition d'un premier livre
livre1 = {}
livre1['titre'] = 'Apprendre Python'
livre1['année'] = 2010
print(livre1['titre'])

# autre syntaxe pour un second livre
livre2 = { 'titre': 'Algorithmique' , 'année': 1970 }

# procédure d'affichage dédiée à ce type livre
def affiche_livre (l):
  print('titre :',l['titre'])
  print('année :',l['année'])

# appel à la procédure sur le livre1
affiche_livre(livre1)

Opérations Python sur les dictionnaires

Nous retrouvons des fonctions déjà vues sur les listes :

  • len : donne le nombre de couples dans le dictionnaire,
  • del : permet de supprimer une entrée du dictionnaire.

D'autres méthodes Python sont spécifiques aux dictionnaires :

  • keys : donne la liste des clefs d'un dictionnaire,
  • values : donne la liste des valeurs d'un dictionnaire,
  • items : fournit le contenu du dictionnaire sous la forme d'une liste de couples clef-valeur,
  • get : extrait la valeur associée à une clef, comme le fait la notation crochets mais permet de prévoir une valeur par défaut pour le cas où la clef n'existe pas dans le dictionnaire.
# définition d'un dictionnaire
carnet = { 'Titi':'titi@univ.fr' , 'Toto':'toto.truc@free.fr' }

print(carnet.keys())   # ['Toto', 'Titi']
print(carnet.values()) # ['toto.truc@free.fr', 'titi@univ.fr'] 
print(carnet.items())  # [('Toto','toto.truc@free.fr') , ('Titi','titi@univ.fr')]

print(len(carnet)) # affiche la valeur 2

print(carnet['Toto']) # toto.truc@free.fr
print(carnet['Lulu']) # KeyError: 'Lulu'

print(carnet.get("Toto","contact inconnu")) # toto.truc@free.fr
print(carnet.get("Lulu","contact inconnu")) # contact inconnu

del(carnet['Toto']) # suppression des infos sur Toto
print(carnet.get("Toto","contact inconnu")) # contact inconnu

Voyons maintenant comment parcourir les dictionnaires Python.

# parcours des clefs et affichage des couples clef/valeur (nom/mail)
for nom in mails:
    print(nom,':',d[nom])

# parcours direct des couples clef/valeur (nom/mail)
for nom, mail in mails.items():
    print(nom,":",mail)

Terminons avec l'opérateur in qui permet ici de tester si une clef existe dans un dictionnaire.

"Titi" in carnet # True
"Lulu" in carnet # False

Cet opérateur in a déjà été vu dans le cas des chaînes de caractères, des listes et des ensembles. L'algorithme sous-jacent dans le cas des dictionnaires est proche de celui déjà utilisé pour les ensembles, et il a la même rapidité.

Comptage à l'aide des dictionnaires Python

Nous terminons avec une utilisation possible des dictionnaires Python : compter les occurrences des mots dans un corpus. Nous voulons donc obtenir un compteur associé à chaque mot. Nous utilisons à cette fin un dictionnaire Python avec les mots en clefs et les compteurs comme valeurs. À la rencontre d'un mot, nous allons mettre à jour son compteur comme suit.

# première occurrence    : on initialise la case à 1
# si la case existe déjà : on lui ajoute 1

compteurs[mot] = compteurs.get(mot,0) + 1

Comme nous le voyons, la méthode get permet en une même ligne de gérer deux situations distinctes :

  • si le mot est rencontré pour la première fois, il est absent des clefs du dictionnaire Python, get fournit alors la valeur par défaut 0, à laquelle nous ajoutons 1 et finalement le dictionnaire mémorise que nous avons vu ce mot une fois ;
  • lors des rencontres ultérieures du même mot, get trouve le mot parmi les clefs et fournit la valeur associée dans le dictionnaire, valeur qui est incrémentée pour noter que le mot a été vu une fois de plus.

Nous notons avec cet exemple que le get commence par un test d'existence semblable au in (sur les dictionnaires, donc notre comptage bénéficie de la rapidité de cet opérateur).


Tris des données en Python

En informatique, « trier » signifie ordonner des éléments, sans en éliminer : par exemple des notes de la plus petite à la plus grande, des mots par ordre alphabétique, etc. En Python, les tris sont assez naturellement associés aux listes avec :

  • la méthode sort : effectue le tri d'une liste sur place,
  • la fonction sorted : fournit une copie triée mais sans modifier la liste passée en paramètre.

Cependant il est également possible d'obtenir des listes ordonnées en appliquant sorted aux autres objets Python que nous avons vus : les chaînes de caractères, les ensembles et les dictionnaires.

Il y a de plus deux paramètres pour préciser le tri voulu :

  • reverse est un booléen qui dit si l'on doit inverser ou non le sens du tri,
  • key permet de préciser une fonction à appliquer aux objets, ils sont alors ordonnés selon leurs résultats par la fonction donnée.

Commençons par trier des listes, par copie ou sur place, comme nous l'avons indiqué.

# tri d'une liste de nombres
valeurs = [1,12,10,50,3]
sorted(valeurs)              # [1, 3, 10, 12, 50]
sorted(valeurs,reverse=True) # [50, 12, 10, 3, 1]

# tri d'une liste de chaînes de caractères
mots = ["favori","école","abri"]
sorted(mots)         # selon ordre lexicographique + codes UNICODE : ['abri', 'favori', 'école']
sorted(mots,key=len) # selon la taille des mots : ['zoo', 'abri', 'école', 'favori']

# tri d'une liste sur place avec la méthode sort
valeurs               # la liste n'a pas été modifiée par sorted : [1, 12, 10, 50, 3]
valeurs.sort(key=str) # la liste est triée sur place par ordre alphabétique
valeurs               # [1, 10, 12, 3, 50]

Voyons maintenant comment trier les caractères d'une chaîne de caractères. Rappelons-nous que les chaînes de caractères en Python sont non mutables. Il est possible d'obtenir les caractères triés dans une liste, et éventuellement de revenir ensuite à une (nouvelle) chaîne de caractères.

# tri des caractères d'une chaîne de caractères
sorted("bachibouzouk") # ['a', 'b', 'b', 'c', 'h', 'i', 'k', 'o', 'o', 'u', 'u', 'z']

# on reconstitue une chaîne pour obtenir "abbchikoouuz"
"".join(sorted("bachibouzouk"))

Comment trier les éléments d'un ensemble ? Par définition, les ensembles sont non ordonnés mais l'on peut trier les éléments d'un ensemble pour obtenir une liste ordonnée. Cette fois, si l'on revient à un ensemble, on perd bien sûr l'ordre défini par le tri.

# sur les éléments d'un ensemble
sorted({5,1,3}) # [1, 3, 5]

Comme les ensembles, les dictionnaires n'ont pas d'ordre et trier un dictionnaire revient à utiliser sorted pour récupérer une liste ordonnée des clefs, clefs qui resteront un bon point d'entrée vers le dictionnaire d'origine. Par défaut sorted va trier un dictionnaire selon ses clefs.

# définition d'un dictionnaire d'un compteur de mots
compteurs = { "coucou":2 , "dada": 5 , "zozo":3 , "mama":1 }

# tri alphabétique sur les clefs d'un dictionnaire
vocabulaire = sorted(compteurs) # ['coucou', 'dada', 'mama', 'zozo']

# parcours du dictionnaire selon les clefs triées
for mot in vocabulaire:
    print(mot,compteurs[mot])

Si l'on veut trier un dictionnaire selon ses valeurs, il nous faut revenir sur le paramètre key et introduire la notion de fonction lambda. On a vu l'utilisation de la fonction len sur des chaînes de caractères pour les trier par taille. On a vu également la fonction str sur des entiers, pour les trier comme des chaînes de caractères, c'est-à-dire par ordre alphabétique. Parfois, la fonction à utiliser n'est pas prédéfinie en Python et l'on ne souhaite pas la créer avant le tri. Dans ce cas, Python permet de définir une fonction lambda, laquelle nous dispense du mot clef def, du nommage de la fonction, du return, etc.

# forme générale d'une fonction lambda
lambda arguments: expression

# exemples de fonctions lambda
lambda x: x+2
lambda x,y: x+y

# utilisations directes de fonctions lambda
(lambda x: x+2)(4)     # 6
(lambda x,y: x+y)(4,5) # 9

Une telle fonction lambda peut être placée directement dans le paramètre key. Cela nous permet finalement d'ordonner les clefs d'un dictionnaire selon les valeurs associées, avec une fonction lambda qui à partir d'une clef renvoie la valeur associée dans le dictionnaire. À nouveau, on obtient une liste de clefs triée par ordre des valeurs, liste que l'on peut parcourir tout en accédant au dictionnaire.

# tri par valeurs d'un dictionnaire avec une fonction lambda
vocabulaire = sorted(compteurs,key=lambda e: compteurs[e],reverse=True) # ['dada', 'zozo', 'coucou', 'mama']

# parcours du dictionnaire par ordre numérique des valeurs
for mot in vocabulaire:
    print(mot,compteurs[mot])

Rappelons que les tris alphabétiques que nous venons de mettre en œuvre sont basés les codes UNICODE des caractères et pas sur la langue utilisée. Pour trier alphabétiquement, il nous faudra combiner les techniques vues dans cette section et l'utilisation du module locale.


Les modules Python

Nous avons déjà vu comment bénéficier d'un module dans un code Python. Sont décrits ci-dessous quelques modules pertinents pour nos objectifs.

Documentation et tests automatiques : le module doctest

Lors de la définition d'une procédure/fonction, on peut ajouter après la première ligne une docstring. Il s'agit d'une chaîne de caractères, délimitée par des triples guillemets, qui décrit la fonction et propose des tests à travers des exemples d'utilisation.

def nom_d_agent (verbe) :
    """
    Fournit le nom d'agent associé au verbe fourni

    paramètre verbe : (string) le verbe à traiter
    valeur renvoyée : (string) le nom d'agent associé

    Exemples :

    >>> nom_d_agent('chanter')
    'chanteur'

    >>> nom_d_agent('danser') == 'danseur'
    True
    """
    radical     = verbe[0:-2]
    terminaison = verbe[-2:]
    nom         = radical+"eur"
    return (nom)

# affiche la documentation de la procédure, c'est-à-dire sa docstring
help(nom_d_agent)

L'implication du module doctest permet de surcroît de lancer les tests qui apparaissent dans la documentation. Chacun de ces tests est préfixé par trois signes supérieurs (>>>) et la ligne suivante présente le résultat attendu.

import doctest

doctest.testmod()
TestResults(failed=0, attempted=2)

doctest.testmod(verbose=True)
Trying:
    nom_d_agent('chanter')
Expecting:
    'chanteur'
ok
Trying:
    nom_d_agent('danser') == 'danseur'
Expecting:
    True
ok
1 items had no tests:
    __main__
1 items passed all tests:
   2 tests in __main__.nom_d_agent
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=2)

Gestion de l'aléatoire : le module random

Le module random propose différentes fonctions visant à produite de l'aléatoire :

  • random.randint : choisit aléatoirement un entier dans un intervalle donné,
  • random.randrange : choisit aléatoirement entre 0 et un entier donné (exclu), permet aussi de préciser une borne inférieure autre que 0 et un pas de progression (à la manière de range),
  • random.choice : fournit aléatoirement un élément parmi ceux d'une liste,
  • random.shuffle : mélange sur place les éléments d'une liste.

Quelques exemples d'utilisation du module random de Python :

import random

print(random.randint(1,10))       # un entier entre 1 et 10

print(random.randrange(10))       # un entier entre 0 et 9
print(random.randrange(10,100,2)) # un entier pair entre 10 et 98

notes = [5,12,8,20,10]            # on définit une liste

print(random.choice(notes))       # une note au hasard

print(notes)
random.shuffle(notes)             # mélange aléatoire, sur place
print(notes)

Expressions régulières : le module re

En Python, les expressions régulières s'expriment comme des chaînes de caractères. Les classes de caractères préfixées par des backslash (\d pour les chiffres par exemple) nous incitent à utiliser les raw strings, pour ne pas avoir à protéger les backslash nous-mêmes.

La fonction re.search permet de tester si une expression régulière matche ou non une chaîne de caractères.

import re

texte = "beau"

if re.search("^[aeiouy]",texte) : # est-ce que le texte commence par une voyelle ?
    print(texte,"commence par une voyelle.")
else :
    print("Ne commence pas par une voyelle.")

if re.search(r"\d+ ans",texte):   # est-ce que le texte contient une indication d'âge ?
    print("Contient une indication d'âge.")

La fonction re.search permet également de récupérer des sous-chaînes matchées. Ces sous-chaînes doivent être répérées par des parenthèses dans l'expression régulière.

import re

verbe = "chanter"

matching = re.search("^(.*)(..)$",verbe) # parenthésage des éléments à récupérer

print(matching.group(0)) # texte matché par l'expression régulière        : chanter
print(matching.group(1)) # texte matché par le premier jeu de parenthèses : chant
print(matching.group(2)) # texte matché par le second jeu de parenthèses  : chant

La fonction re.sub est utile pour effectuer une substitution, c'est-à-dire remplacer un motif par une nouvelle chaîne de caractères. Ici encore, il est possible de repérer et de récupérer des sous-chaînes matchées. Cette fois, nous pouvons les réutiliser immédiatement à l'aide de variables numérotées (préfixées par un backslash).

import re

verbe = "chanter"
nom   = re.sub("er$","eur",verbe) # remplacement d'une terminaison
print(verbe,"donne",nom)          # chanter donne chanteur

# remplacement avec des variables numérotées
ligne          = "Sabre laser. De 6 à 10 ans. Sans pile."
ligne_enrichie = re.sub(r"^(.*)(\d+ ans)(.*)$",r"\1 😀 \2 😀 \3",ligne)
print(ligne_enrichie)

Enfin, la fonction re.split découpe une chaîne selon un expression régulière.

import re

mots = re.split(r"\W+",ligne)# découpage d'une ligne en mots
for mot in mots:
    print(mot)

Précisons que l'insensibilité à la casse des méthodes évoquées peut être demandée de deux manières. Au choix :

  • re.IGNORECASE ou re.I en dernier paramètre des méthodes,
  • (?i) au début des expressions régulières.

Couleurs dans le terminal : le module termcolor

Le module termcolor permet des affichages colorés dans le terminal. Signalons d'abord la procédure cprint qui se substitue au classique print pour ajouter des couleurs au texte, des couleurs de fond, etc. Différents attributs sont également disponibles pour modifier l'affichage : gras, sombre, clignotant, souligné, etc.

from termcolor import cprint

# affichages colorés par cprint
cprint("coucou","white","on_red")
cprint("salut","white","on_green",attrs=['bold'])

Le module termcolor permet aussi de produire une chaîne de caractères colorée, sans l'afficher, à l'aide de la fonction colored.

from termcolor import colored

# affichages de textes colorés produits par colored
print(colored("tout va bien","blue"))
print(colored("tout va très bien","blue",attrs=["bold","underline"]))

# stockage de textes colorés produits par colored
pion_rouge = colored("●","red",attrs=['bold'])
pion_jaune = colored("●","yellow",attrs=['bold'])
pion_vide  = colored("●","grey",attrs=['bold'])

# affichages des textes colorés précédemment produits par colored
print()
for i in range (8) :
    print(pion_rouge,pion_vide,pion_jaune,end=" ")
print()

Langues et localisations : le module locale

locale désigne les paramètres régionaux de la machine. Le module locale permet d'y accéder depuis des scripts Python.

import locale
    
locale.setlocale(locale.LC_ALL,"")            # soit on choisit la locale par défaut sur la machine
locale.setlocale(locale.LC_ALL,"fr_FR.UTF-8") # soit on précise la locale voulue

locale.localeconv()["int_curr_symbol"]        # symbole monétaire à utiliser d'après la locale : EUR

Nous avons vu que les tris alphabétiques n'étaient pas réellement efficients, car basés sur les codes UNICODE et pas sur l'alphabet du pays. Le module locale fournit deux fonctions qui permettent de résoudre cette difficulté :

  • strcoll : fonction à deux paramètres, renvoie une valeur négative si le premier mot est avant le deuxième dans l'ordre alphabétique,
  • strxfrm : transforme un mot en une nouvelle chaîne de caractères qui pourra être comparée à d'autres obtenues de la même manière, les comparaisons standards donneront alors des réponses conformes à l'ordre alphabétique régional, pour les chaînes de caractères de départ.
import locale
    
locale.setlocale(locale.LC_ALL,"")

# comparaison de mots deux à deux
print(locale.strcoll("école","abri"))   # +45 : 'école' est après 'abri'
print(locale.strcoll("école","favori")) # -23 : 'école' est avant 'favori'

# tri alphabétique utilisant la locale
l = ["favori","école","abri"]
sorted(l,key=locale.strxfrm) # ['abri', 'école', 'favori']

Manipulation des documents semi-structurés : le module xml

Le module xml de Python fournit principalement deux interfaces pour traiter les fichiers XML : SAX et DOM. Il s'agit d'API qui l'on retrouve dans différents langages de programmation (voir l'API DOM dans le cours de JavaScript ou la présentation généale des API SAX et DOM dans le cours sur XML).

DOM est un modèle de document construit en mémoire à la lecture d'un fichier XML, modèle que l'on peut interroger. Nous nous concentrons ici sur le noyau de DOM fournissant des méthodes valables pour tout document XML (en particulier, des documents non-HTML). Nous devenons capables d'interagir avec un fichier XML à l'aide de DOM. Ici un script Python qui compte les personnages dans un fichier XML TEI.

# chargement d'une librairie qui gère le DOM/XML
from xml.dom.minidom import parse

# création d'un parser et lecture du fichier XML
document = parse('sarrasine-balzac.xml')

# récupération des personnages dans le DOM
persos = document.getElementsByTagName("persName")

# comptage des personnages
compte_persos = {}
for perso in persos:
    persname = perso.firstChild.nodeValue
    compte_persos[persname] = compte_persos.get(persname,0) + 1

# affichage des personnages classés et de leurs compteurs
for perso in sorted(compte_persos,key=lambda p: compte_persos[p]):
    print(compte_persos[perso],end="\t",perso)

Python permet également de créer un nouveau document DOM avec des instructions comme createElement, appendChild, toxml, toprettyxml, etc.

Plus de détails sur l'API DOM dans le cours sur les interfaces XML.

Voyons maintenant comment interagir avec un fichier XML à l'aide de SAX. Il s'agit cette fois d'implémenter les méthodes d'une classe Python, méthodes qui seront déclenchées par la survenue d'événements pendant la lecture d'un fichier XML : début du document, ouverture ou fermeture d'une balise, rencontre d'une feuille textuelle, etc. À l'inverse de DOM, SAX ne stocke rien en mémoire, il lit séquentiellement le fichier XML et réagit aux événements. Il nous reste à programmer le comportement à avoir pour chaque événement.

# chargement d'une librairie qui gère le SAX/XML
from xml.sax import ContentHandler,make_parser

class TEIHandler(ContentHandler):

    def __init__(self):
        self.compte_persos = {}
        self.pers_encours  = False
    
    def startDocument(self):
        """ce que l'on fait au début du document"""
        print("> lecture du document : début")

    def endDocument(self):
        """ce que l'on fait à la fin du document"""
        print("> lecture du document : fin")
        for perso in sorted(self.compte_persos,key=lambda p: self.compte_persos[p]):
            print(self.compte_persos[perso],end="\t",perso)
    
    def startElement(self, name, attrs):
        """ce que l'on fait pour chaque balise ouvrante"""
        if name=="persName":
            self.pers_encours = True

    def endElement(self, name):
        """ce que l'on fait pour chaque balise fermante"""
        if name=="persName":
            self.pers_encours = False

    def characters(self, content):
        """ce que l'on fait pour le texte"""
        if self.pers_encours:
            self.compte_persos[content] = self.compte_persos.get(content,0) + 1


# Parsing d'un fichier avec SAX
teih   = TEIHandler()
parser = make_parser()
parser.setContentHandler(teih)
parser.parse('sarrasine-balzac.xml')

Plus de détails sur l'API SAX dans le cours sur les interfaces XML.


Traitement automatique des langues : le module nltk

Le module nltk nous permet de traiter un texte, éventuellement lu depuis un fichier et placé dans une variable.

import nltk

paragraphe = """Harry frissonna et scruta Magnolia Crescent. Qu'allait-il lui arriver ?
Allait-il être arrêté ou simplement banni du monde des sorciers ? Il pensa à Ron et à Hermione
et se sentit encore plus désemparé. Il était sûr que, délinquant ou pas,
Ron et Hermione auraient tout fait pour l'aider, mais ils étaient tous deux à l'étranger
et main tenant qu'Hedwige était partie, il n'avait plus aucun moyen de les contacter.
Il n'avait pas non plus d'argent moldu. Il lui restait un peu d'or de sorcier
dans un porte-monnaie au fond de sa valise, mais le reste de la fortune que ses parents
lui avaient léguée se trouvait à Londres dans une chambre forte de chez Gringotts,
la banque des sorciers. Et il n'aurait sûrement pas la force de traîner sa valise
jusqu'à Londres. À moins que..."""

À partir d'un tel texte, il est possible de le découper selon différents critères proposés par le module nltk.

  • nltk.sent_tokenize : découpe le texte en phrases,
  • nltk.word_tokenize : découpe le texte en mots.
# installation complémentaire propre à NLTK (si nécessaire et seulement une fois)
nltk.download('punkt')

# découpage en phrases
phrases = nltk.sent_tokenize(paragraphe,language="french")

print()
for phrase in phrases:
    print(phrase)
print()

# découpage de l'une des phrases en tokens
tokens = nltk.word_tokenize(phrases[4],language="french")
print(tokens)

Ensuite, les tokens précédemment obtenus peuvent bénéficier d'un étiquetage grammatical à l'aide de la fonction nltk.pos_tag.

# installation complémentaire propre à NLTK (si nécessaire et seulement une fois)
nltk.download('averaged_perceptron_tagger')

# étiquetage des tokens
tokens_étiquetés = nltk.pos_tag(tokens)

print(tokens_étiquetés)

Échanges avec le système d'exploitation : le module os

Parmi les fonctionnalités proposées par le module os : os.system permet d'exécuter une commande du système d'exploitation.

import os

# on efface les lignes présentes dans le terminal Linux
os.system("clear")

Autre fonction intéressante : os.listdir fournit la liste des fichiers dans un dossier donné.

import os

# parcours du contenu du dossier courant
for file in sorted(os.listdir(".")):
    print(file)

Le module os permet aussi de récupérer les paramètres passés sur la ligne de commande, à travers un tableau nommé sys.argv.

import sys

filename = sys.argv[1] # récupération du premier paramètre

Notons enfin la fonction os.popen qui nous autorise à récupérer le flux produit par un autre programme en cours d'exécution.

import os

# lancement d'une commande externe et ouverture d'un flux
pipe = os.popen('ls -l ~')

# parcours classique d'un flux, ligne par ligne
while ligne := pipe.readline():
    print(ligne,end="")

Interaction avec l'interprète Python : le module sys

Si nous avons un algorithme récursif qui nécessite d'augmenter le nombre de récursions autorisées, cela se fait à l'aide du module sys, comme suit :

import sys
    
sys.setrecursionlimit(2000)

Mesure des temps de calcul : le module time

Le module time fournit une fonction qui donne à un instant donné le temps de travail du processeur dépensé par le script Python actuel. La différence entre deux mesures prises à deux instants différents permet de connaître le temps consommé par une portion de code particulière.

import time

t1 = time.process_time()
...
# ici les instructions à chronométrer
...
t2 = time.process_time()

print((t2 - t1),'secondes') # calcul et affichage du temps passé

Dessins avec la tortue Python : le module turtle

Le module Python turtle reprend les instructions du langage de programmation Logo qui permettait de faire des dessins.

  • color(c) et width(w) : décident de la couleur et de l'épaisseur du trait,
  • write(t) : écrit un texte,
  • up() et down() : lève et baisse le crayon,
  • forward(d) et backward(d) : avance et recule d'une certaine distance,
  • goto(x,y) : déplace la tortue aux coordonnées données,
  • circle(r) : dessine un cercle de rayon donnée,
  • begin_fill() et end_fill() : colorient une zone,
  • hideturtle() et exitonclick() : cache la tortue et attend un clic,
  • speed(v) : règler la vitesse de la tortue,
  • reset() : réinitialise.

Ci-dessous le dessin de quelques carrés.

from turtle import *

# procédure pour tracer un carré
def carré (x,y,taille):
    up()                # on lève le crayon
    goto(x,y)           # on se rend aux coordonnées demandées, sans dessiner
    down()              # on baisse le crayon
    for i in range(4):  # quatre fois
        forward(taille) # on avance en dessinant
        left(90)        # on tourne de 90°

# programme principal
speed(5)             # réglage de la vitesse
        
carré(-100,-100,100)
carré(100,100,200)
carré(190,100,20)

hideturtle()         # on cache la tortue
exitonclick()        # on attend un clic pour fermer la fenêtre graphique


Accueil > Enseignement > Cours > Programmation > Python
(contenu mis à jour )
site de Fabien Torre, université de Lille

Description

Survoler un lien de navigation pour lire sa description ici...