Présentation

Ce support vise à introduire des concepts de base en programmation, avec une forte invitation à la pratique et aux bonnes pratiques.

Python-1 intro

Python

Python est un langage inventé par le programmeur Guido van Rossum dans les années 90. C’est un projet open source et multi-plateformes.

Python est un langage interprété, contrairement à d’autres, comme le C qui nécessite une phase de compilation qui produit une version exécutable pour une plateforme cible, le code source d’un programme Python est directement portable (l’analyse et sa traduction en langage machine se fait au cours de son lancement par un interpréteur Python lié à l’OS hôte).

Vérification de votre système

Ouvrir une console système (un terminal), et lancer la commande : python3 --version (ou python --version)

Exemple de résultat attendu (vérifier que vous êtes en 3.x.x) :

Listing 1. Exemple ($ est le prompt, à ne pas recopier)
$ python3 --version
Python 3.10.6

Python peut être lancé en mode interactif ou en mode script.

Python en mode interactif

Il suffit de lancer la commande python3, dans ce cas l’interpréteur attend la saisie de code, qu’il interprétera à partir du moment où il reçoit une donnée de fin de saisie (touche Enter)

Pour sortir de ce mode, lancer la commande CTRL + D (ou CTRL + Z sous windows).

Listing 2. Tester ce code
$ python3
>>> print('Hello World')
Hello World
>>>

Python en mode script

Dans ce mode (usage courant) le code source est placé dans un fichier text, avec .py comme extension.

Tester cette procédure
  1. créer un fichier texte nommé tp1.py

  2. inscrire l’instruction print('Hello World') comme seule ligne de ce fichier

  3. sauvegarder le fichier tp1.py

  4. dans un terminal, lancer la commande : python3 tp1.py.

    Vous devriez voir afficher Hello World

Cette procédure peut être automatisée (la première instruction est équivalente aux étapes 1 à 3 de la procédure présentée). L’opérateur système '>' dirige temporairement le flux standard vers un fichier.

echo "print('Hello World')" > tp1.py | python3 tp1.py

L’opérateur système '>>' dirige le flux standard en fin du fichier cible (ajout).

⇒ La redirection du flux standard vers un fichier est une technique bien connue des hackers.

Documentation

  • Préférer le site officiel docs.python.org et stackoverflow.

  • Consulter la documentation technique des objets du langages help() (et dir()).

    • la fonction help() extrait des chaînes de documentation (modules, fonctions et méthodes). Exemples :

      • help(print)

      • help(sys.exit) (le module sys doit être importé avant cette commande)

  • Consulter régulièrement le référentiel de bonnes pratiques de programmation en python :

Mode interactif

EXERCICE 1

  1. Lancez l’interpréteur dans un terminal

  2. Tester une à une les expressions suivantes et essayez d’expliquer ce qui se passe dans chacun des cas. N’hésitez pas à échanger entre vous et à chercher des explications en ligne. Le langage python est utilisé par une grande communauté internationale. Voir le tableau de correspondances opérateur-fonction : https://docs.python.org/fr/3/library/operator.html#mapping-operators-to-functions

Code Résultat Explications

2 * 11 + 3 + 4 * 5 - 4 + 1

42

La priorité des opérateurs arithmétiques suit les conventions usuelles. (voir priiorité des opérateurs)

2 ** 16

pow(2, 16)

2/3

int(2/3)

round(2/3)

import math math.ceil(2/3) math.ceil(2/5)

4%2

12%7

float(40+2)

"année" + "2023"

Concaténation de 2 chaînes de caractères

s=samedi

s="samedi"

Par la suite des questions suivantes on considère que la variable s est initialisée à "samedi"

s * 4

s[0], s[1]

s[6]

'spam'[0]

"spam"[1]

"spam"[0:2]

"spam"[1:]

('pizza' + s[0]) * 3

Les opérations associées aux structures de données de type conteneur seront étudiées plus loin. Pour les curieux, voir : https://docs.python.org/fr/3/library/stdtypes.html#typesseq

Quel éditeur de code utiliser ?

Vous trouverez ici https://docs.python.org/3/using/editors.html un inventaire de nombreux éditeurs.

Voici une sélection :

  1. vi : l’éditeur incontournable, présent dans toutes les distributions linux. Il vous sauvera de situation délicate (intervention à distance sur un serveur)

  2. Visual Studio Code et son plugin python : léger mais suffisament complet pour démarrer avec python (complétion, vérification de cohérence de type, débogueur intégré…​), également utilisé pour le dev frontend.

  3. pyCharm, l’IDE à destination des professionnels (gain de productivité assuré)

vscode
Figure 1. Visual Studio Code avec le plugin Python, ouvert sur le dossier TPS

Exercices en mode script

Un script python, appelé aussi "module", peut être utilisé directement, comme dans l’exemple python3 tp1.py ou intégré dans un autre module (via l’instruction import).

Python vient avec de nombreux modules préinstallés. Vous pouvez en consulter la liste avec : pip3 list -V. Pour en utiliser d’autres, il faut préalablement les télécharger. Voir ici pour en savoir plus sur la gestion des modules : https://docs.python.org/fr/dev/installing/index.html

Dans un premier temps, placez-vous dans un dossier de votre espace personnel dédié aux exercices en python.

EXERCICE 2

Voici un code source d’un programme python respectant les conventions d’usage :

Listing 3. fichier tp1.py
#!/usr/bin/env python3 (1)

def exo2() -> None : (2)
  """
   exercice 2 du TP1
  """
  nom = input("Entrez votre nom : ")
  print("Bienvenue " + nom + " !")

# vos autres fonctions ici (exo3() exo4() etc.)

# le main (partie exécutée lors du lancement du programme)
if __name__ == "__main__": (3)
  import sys (4)
  exo2() (5)
  sys.exit(0) (6)
1 (optionnel) Shebang. Permet de rendre le script "directement" exécutable. voir https://stackoverflow.com/questions/6908143/should-i-put-shebang-in-python-scripts-and-what-form-should-it-take
2 Définition d’une fonction nommée exo2, qui déclare ne "rien" retourner (None), avec sa chaîne de documentation
3 Si le script est utilisé directement (en argument de l’interpréteur python), alors la valeur de la variable __main__ est "__main__", sinon il est utilisé en import dans un autre script et c’est le nom du script (module), sans son extension. Remarque : les noms de variables encadrés de 2 underscores (__) sont des variables système (pré)définies par l’interpréteur.
4 Importation du module sys (qui contient des fonctions système, dont exit utilisée plus loin)
5 Appel de la fonction exo2.
6 Appel la fonction exit afin de demander la sortie du mode interpréteur de python avec transmission du code de retour. Voir https://docs.python.org/fr/3/library/sys.html#sys.exit, ou, en mode interactif, appeler l’aide sur cette fonction via la commande help(sys.exit). Retourner zéro signifie que le programme se termine avec succès, toute autre valeur signale à l’appelant une anomalie à l’exécution.
Travail à faire
  1. Si ce n’est pas déjà fait, créer un dossier dev

  2. Créer un sous-dossier dev/TPS et ouvrir ce dossier avec l’éditeur visual studio code

  3. Créer le fichier tp1.py

  4. Recopiez le code ci-dessus

  5. (optionnel) Rendez-le exécutable (par exemple avec la commande chmod +x tp1.py)

  6. Exécutez-le (dans un terminal), éventuellement corrigez les erreurs de frappe.

  7. Modifiez la fonction exo2(), afin qu’elle affiche le prénom et le nom. Elle devra pour cela inviter l’utilisateur à entrer son prénom.

EXERCICE 3

On vous présente un programme exprimé en pseudo-langage ainsi qu’une traduction de ce programme en Python (exo3()). Après avoir pris connaissance de la version en pseudo-langage, recopier la traduction proposée en Python (code source ci-dessous), à insérer à la suite de exo2() et avant la partie main, comme nouvelle fonction dans le script tp1.py.

Listing 4. Version pseudo-langage
Afficher("Entrez un nombre entier svp :")

lire un nombre au clavier et placer sa valeur
dans une variable nomméee x (1)

Si x est pair Alors
  Afficher("Ce nombre est pair")
Sinon
  Afficher("Ce nombre est impair")
FinSi
1 ou plus simlement : x <-- lire un nombre au clavier
Listing 5. Version python
def exo3() -> None :
  x = int(input("Entrez un nombre entier svp : "))
  if x % 2 == 0 :  # le reste de division par 2 est-il zéro ?
    print("Ce nombre est pair")
  else :
    print("Ce nombre est impair")
Travail à faire
  1. Intégrer, comme indiqué (avant la partie main), la nouvelle fonction exo3 dans le module tp1.py. Respecter l’indentation.

  2. Appeler cette fonction dans la partie main de tp1.py.

  3. Tester différentes valeurs afin de vérifier la justesse du code. (Si l’utilisateur ne saisit pas un nombre, le programme s’arrête brutalement - c’est normal, la gestion des cas d’erreurs sera abordée ultérieurement)

EXERCICE 4

On souhaite proposer une variante de la fonction exo2, sous la forme d’une nouvelle fonction nommée exo4, de sorte que, si l’utilisateur ne fournit pas d’identité, le programme lui attribut d’office le nom "anonymous".

Voici une version en pseudo-code founit par un de vos collègues.

Listing 6. Version pseudo-langage
Afficher("Entrez votre nom svp :")
nom <-- lire une chaîne de caractère au clavier
Afficher("Entrez votre prenom svp :")
prenom <-- lire une chaîne de caractère au clavier
Si nom est vide Alors
  Afficher("Bonjour Anonymous !")
Sinon
  Afficher("Bonjour " + prenom + " " + nom + " !")
FinSi
Travail à faire
  1. Étudier la version en pseudo-langage ci-dessus puis proposer une traduction fidèle en Python.

  2. Travaillez à partir d’une copie de la fonction exo2 que vous nommerez exo4, puis appelez cette fonction dans le main.

  3. Tester votre code

EXERCICE 5

L’algorithme proposé par votre collègue dans l’exercice précédent manque de logique. Avez-vous repéré ce qui cloche ?

Si l’utilisateur ne décline pas son identité à la demande de son nom, alors le programme ne devrait pas lui demander son prénom.

Proposez une amélioration de la fonction exo4, que vous nommerez exo5, qui respecte cette nouvelle logique.

Travail à faire
  1. Fournir d’abord une version en pseudo-langage

  2. Faire valider votre version par un professeur

  3. Traduire votre version en Python (une nouvelle fonction nommée exo5)

  4. Tester et mettre au point votre fonction

EXERCICE 6

Voici un programme écrit en pseudo-code : .Version pseudo-langage

n <- 60
m <- 7
afficher("Les entiers valent ", m , "et ", n)
afficher("leur somme est ", m+n)
afficher("leur différence est ", m-n)
afficher("leur produit est ", m*n)
afficher("leur quotient est ", m/n)
afficher("le reste de la division entière m/n est ", m modulo n)
Travail à faire
  1. Transcrire ce programme en une fonction Python (nommée exo6).

  2. Puis améliorer la fonction exo6 de sorte que l’utilisateur puisse lui-même fournir des valeurs pour les zones mémoire référencées par les identificateurs m et n. Vérifier la justesse des sorties.

EXERCICE 7

Cet exercice introduit la notion de type. En effet, toute variable est associée, à un instant t, à un et un seul type. Le type de la variable est déterminé par l’interpréteur au moment de l’affectation et peut être consulté à l’exécution par un appel à la fonction type.

Listing 7. Comment connaître le type d’une variable
>>> x = 42
>>> type(x)
<class 'int'>   # <== le type de x est int

Commençons par définir une fonction qui réalise une somme de 2 entiers reçues en argument. Nous appellerons cette fonction somme.

def somme(arg1: int, arg2: int) -> int :
  """
    Return la somme des arguments
  """
  # affecte à la var result le résultat de l'opération +
  result = arg1 + arg2
  return result (1)
1 On remarquera que la fonction "n’affiche" rien. C’est très important. Le fait d’afficher ou non la valeur retournée est de la responsabilité de l’appelant, pas de l’appelé (voir Glossaire Appelant/Appelé)

Voici un exemple de programme (une fonction) qui appelle la fonction somme (ligne 25)

def exo7() -> None:
  print("Bonjour, je suis un programme écrit en Python.")

  # invite l'utilisateur à entrer un nombre entier
  # l'information est stockée dans une zone mémoire
  # référencée par 'str_n1'
  str_n1 = input("Entrez un nombre entier : ")

  # affiche une information sur le type de l'objet crée
  print("Le type de l'objet crée est ", type(str_n1))

  # n1 est l'image de str_n1 par la fonction int(). Le rôle de int()
  # est de tenter de traduire son argument en une valeur
  # numérique (un entier).
  n1=int(str_n1)

  # affiche une information sur le type de l'objet crée
  print("Le type du nouvel objet crée est ", type(n1))

  # idem
  n2 = input("Entrez un second nombre entier : ")

  # appel à la fonction somme, définie plus haut,
  # en vue de réaliser une addition (normalement pb de type ici)
  res = somme(arg1 = n1, arg2 = n2) (1)

  # affichage du résultat
  print("La somme des deux nombres est : ", res)

  # dernière instruction pour une fin annoncée
  print("bye, je meurs...")


if __name__ == "__main__":
  import sys
  exo7()
  sys.exit(0)
1 On remarquera l’usage des valeurs n1 et n2 comme valeurs d’arguments de la fonction somme. Une autre façon d’appeler la fonction est de passer les valeurs par position, par exemple : res = somme(n1, n2), qui aura même effet.
Travail à faire
  1. Adapter le script tp1.py (ajout de la fonction somme et exo7)

  2. Tester et comprendre pourquoi la fonction exo7 bugue

  3. Corriger la fonction exo7

  4. Modifiez la fonction exo7.py afin qu’elle réalise la somme de 3 nombres.

  5. Faire évoluer la fonction exo7.py afin qu’elle réalise, en plus de la somme de 3 nombres, le produit de ces 3 nombres. Pour cela vous devrez créer, juste après la déclaration de somme(), une nouvelle fonction nommée produit(), inspirée de somme().

EXERCICE 8 (FINAL)

A l’issue de cette première séance de travaux pratiques, vous avez appris à programmer des fonctions simples en Python, à les appeler dans la partie main du script/module tp1.py.

Votre mission : Au lancement de tp1.py, permettre à l’utilisateur de choisir la fonction qu’il souhaite exécuter parmi les fonctions exo2(), exo3(), …​, exo7() du module.

Travail à faire
  1. Ajouter une fonction nommée main. Son rôle sera de répondre à cette demande.

  2. Faire en sorte que le code du main de tp1.py appelle cette nouvelle fonction.

  3. Tester le tout

Contrôler vos connaissances et contribuer aux QCMs

Travail à faire
  1. Contrôler vos connaissances sur quizbe.org. (choisir PYTHON-LDV, scope p-1-intro)

  2. Enrichir la base de données QCM. Pour cela, proposer, pour le thème PYTHON-LDV (scope p-1-intro), 2 questions QCM originales et personnelles, sur des thèmes couverts pas cette séquence d’exercices. Il est important d’associer un feedback à chacune des réponses proposées, qu’elles soient justes ou fausses.

Python-2 intro - type

Méta-modèle IPO

Nous avons vu que les exercices de programmation ont en commun le fait qu’ils traitent des informations et produisent un résultat en retour.

C’est en fait le schéma général qui est en action dans l’ensemble des systèmes.

ENTREE → TRAITEMENT → RESULTAT

Le résultat pouvant être une entrée vers un autre processus analogue.

En anglais, ce modèle est connu sous l’acronyme IPO :

INPUT → PROCESS → OUTPUT

Déterminisme

Lorsqu’il s’agit de programmation, le process du modèle IPO, c’est à dire la partie traitement, se doit d’être déterministe.

Par déterminisme, il faut comprendre qu’une fonction doit, pour un même ensemble de données d’entrée, toujours produire la même sortie.

Par exemple, définissons une fonction qui retourne le signe d’un nombre passé en argument :

def signe(x: int) -> str: (1)
  """ (2)
   Retourne le signe de son argument.
   soit '+', '-' ou '' (chaine vide) si x==0
  """
  result = '' # pas de signe
  if (x < 0):
    result = '-'
  elif (x > 0): (3)
    result = '+'
  return result
1 On précise ici le type du premier argument (premier paramètre) ainsi que le type de retour de la fonction (string). Cette possibilité a été introduite à partir de la version 3.5 de python. Cette information n’est pour l’heure pas exploitée par l’interpréteur. (voir https://docs.python.org/3/library/typing.html)
2 C’est une chaîne de documentation ("docstring"). Son but est de présenter le rôle de la fonction. C’est à destination des développeurs (pas de l’interpréteur). N’hésitez pas à mettre des exemples dans cette partie.
3 elif, une contraction de else if, c’est un des mots clés de python.

On pourra appeler autant de fois notre fonction signe avec la même valeur de donnée d’entrée, elle nous retournera toujours le même résultat.

Exemple :

print(signe(42)) # +
print(signe(42)) # +
print(signe(40+2)) # +
print(signe(2+2*20)) # +
...
print(signe(-42)) # -
...
print(signe(0)) #

Une fonction qui ne dépend que des valeurs de ses arguments pour réaliser son travail, sans impacter le système, est qualifiée de fonction pure.

Une fonction pure a de nombreux avantages, dont celui de permettre la programmation parallèle.

Type et opérations

Nous avons constaté (Exercice 7) que les opérations sont dépendantes du type de la donnée sur laquelle elles travaillent.

Il existe des types prédéfinis, dits natifs à l’interpréteur (Built-in). Ce sont les types numériques, les séquences, les dictionnaires, les classes, les instances et les exceptions.

Python manipule des objets (un concept qui sera approfondi dans une prochaine section) et certaines opérations sont prises en charge par plusieurs types d’objets ; en particulier, pratiquement tous les objets peuvent être comparés en égalité, testés en valeur de vérité (valeur booléenne) et convertis en une chaîne de caractères (avec la fonction repr() ou la fonction légèrement différente str()). Cette dernière est implicitement utilisée quand un objet est affiché par la fonction print().

Types numériques

Il existe trois types d’objets numériques : les entiers (integers), les nombres flottants (floating point numbers) et les nombres complexes (complex numbers). En outre, les booléens sont un sous-type des entiers. Les entiers ont une précision illimitée.

Exemples d’opérations courantes (source : documentation opérations) op et types numériques

Chaîne de caractères

Les données textuelles en Python sont manipulées avec des objets str ou strings. Les chaînes sont des séquences immuables de points de code Unicode (une suite ordonnée d’indice dans la table UNICODE).

Une donnée immuable est une donnée qui ne peut pas être modifiée une fois créée.

Les structures de type Collection seront abordées prochainement

Les chaînes littérales peuvent être écrites de différentes manières :

  1. entre quotes : 'cela autorise les "guillemets"' ;

  2. entre guillemets : "cela autorise les 'simples quotes'" ;

  3. entre guillemets triples : '''Trois simples quotes''', """Trois guillemets""".

Les chaînes entre guillemets triples peuvent couvrir plusieurs lignes, tous les caractères de type 'espace' sont alors inclus dans la chaîne littérale.

Travaux pratiques

En python

  • La fonction ord() permet de connaître le code (ou indice) d’un caractère UNICODE. Une valeur d’indice dans la table UNICODE allant de 0 à 1114111 (0x10FFFF en base 16)

  • Inversement, la fonction chr() permet de connaître le caractère UNICODE associé à une valeur d’indice - voir documentation de la fonction chr

Exemple d’utilisation (d’appels) de ces fonctions :

ord('A') ->   65
chr(65)  ->   'A'

La représentation du glyphe d’un caractère UNICODE est de la responsabilité du système d’affichage (dépend de la police et du contexte d’affichage)

Voir plus en détail ici : python et l’unicode

Le type caractère n’existe pas en Python. Un caractère est une chaîne de un seul caractère.

Challenge : Proposez un POC (preuve de concept) de cette remarque !

EXERCICE 20

Dans un script nommé tp2.py, réaliser, dans une fonction nommée exo20, le scénario suivant et l’appeler dans le main :
  1. L’utilisateur est invité à saisir un caractère.

  2. Le programme lui affiche successivement les informations suivantes

    • Le code du caractère

    • Le caractère précédent

    • Le caractère suivant

Listing 8. Exemple d’exécution
Entrez un caractère, svp : A
 # remarque : l'utilisateur choisit le caractère  'A'
 # le programme répond
Le code du caractère est : 65
Le caractère précédent  : @
Le caractère suivant : B

EXERCICE 21

Voici une spécification de la fonction pred()

fonction pred() : Caractère -> Caractère
 # reçoit un caractère en argument
 # rend, s'il existe, le caractère précédent dans la table UNICODE
 # Sinon retourne None (une autre solution consiste à déclencher une exception)
À faire dans tp2.py
  1. Traduire la fonction pred en python.

  2. Concevoir la fonction succ (caractère suivant) en python.

  3. Tester ces 2 fonctions dans une fonction nommée exo21 appelées dans le main

  4. Réécrire la fonction exo20 en exo20bis en conséquence.

(Pour les plus avancés)

Comment déclencher une exception en python ?

Dans le corps de la fonction, utiliser le mot clé raise suivi d’une classe d’exception et d’un message. Exemple :

raise ValueError('A very specific bad thing happened.')

Le déclenchement d’une exception provoque un arrêt brutal de l’interprétation du corps de la fonction dans lequel il est lancé, contrôlable par un gestionnaire try .. catch. En l’absence d’un contrôleur d’exception, l’application s’arrêtera de façon anormale.

EXERCICE 22

Concevoir une fonction nommée exo22 qui respecte le scénario suivant :

  1. L’utilisateur est invité à saisir un code de caractère

  2. Le programme affiche, dans un tableau, le caractère correspondant encadré

    • à droite des 5 caractères suivants

    • à gauche des 5 caractères précédents, à condition que leur code soit supérieur ou égal à celui du caractère espace.

EXERCICE 23

Concevoir une fonction nommée exo23 qui respecte le scénario suivant :

  1. L’utilisateur est invité à choisir une 'langue' parmi 3 autres - à vous de les sélectionner parmi (url) block Unicode

  2. Le programme affiche, dans un tableau, les 16 (ou plus ?) premiers caractères UNICODE correspondants.

Exemple de 'langue' : Hangul_Syllables Coréen. L’indice du block est U+AC00. Testons le premier caractère en mode python interactif:

>>> ord('\uAC00')
44032

# saute les 2 premiers caractères de la chaine 'U+AC00'
>>> int('U+AC00'[2:], base=16)
44032

>>> chr(44032)
'가'

EXERCICE 23Bis

Concevoir une fonction qui attend en 2 paramètres : un indice de block UNICODE et un nombre de caractères à retourner, et qui retourne la chaîne de caractères correspondant à la demande.

Appeler cette fonction dans une nouvelle fonction nommée exo23bis et tester cette nouvelle version dans le main.

Type booléen

Le type bool est sous-type de int, et définit 2 valeurs particulières : True et False, pour 1 et zéro respectivement.

Testons ces caractéristiques annoncées via une fonction native de Python nommée isinstance qui prend 2 arguments : le premier est l’objet à tester, le second et un type (une classe). Elle rend vrai si le premier argument est du type du second.

Exemple : isinstance("42", int) rendra False car "42" est du type str et non int.

Listing 9. POC : type bool is int
>>> isinstance(True, bool)
True
>>> isinstance(False, bool)
True

>>> isinstance(True, int)
True
>>> isinstance(False, int)
True

>>> isinstance(True, float)
False

>>> int(True)
1
>>> int(False)
0

Valeur de vérité

Toute valeur python peut être interprétée en valeur booléenne via la fonction bool()

Listing 10. POC type bool
>>> bool(1)
True
>>> bool(0)
False
>>> bool(True)
True
>>> bool('Hello World')
True
>>> bool(42)
True

En pratique, toute valeure différente de zéro ou 'vide' sera considérée comme True.

Valeurs considérées comme False

Seules quelques valeurs sont considérées comme fausse :

  • Zéro de toute représentation et type numérique : 0, 0_000, 0.0, 0j, Decimal(0), Fraction(0, 1)

  • Les chaînes et collections vides : '', (), [], {}, set(), range(0).

  • None et False

Affecter None à une variable est une façon de dire que cette variable n’a pas de valeur significative ou "n’a pas encore de valeur".
Listing 11. Exemple de POCs
>>> bool(False)
False

>>> bool(None)
False

>>> bool('None')
True

Les opérations et fonctions natives dont le résultat est booléen renvoient toujours 0 ou False pour faux et 1 ou True pour vrai, sauf indication contraire (exception importante : les opérations booléennes or et and renvoient toujours l’une de leurs opérandes).

Opérations booléennes — and, or, not

Table 1. opérateurs par priorité descendante
Opération Résultat Note

not x

si x est faux, alors True, sinon False

not a une priorité inférieure à celle des opérateurs non-booléens, donc not a == b est interprété comme not (a == b) et a == not b est une erreur de syntaxe.

x and y

si x est faux, alors x, sinon y

C’est un opérateur court-circuit, il n’évalue le deuxième argument que si le premier est vrai.

x or y

si x est vrai, alors x, sinon y

C’est un opérateur court-circuit : il n’évalue le deuxième argument que si le premier est faux.

Opérateurs de comparaison

Travaux pratiques

EXERCICE 24
Table 2. à renseigner
Opération Résultat Justification

bool('') # une chaine vide

bool("")

bool(' ') # un espace

bool(2 * 4 - 8)

bool(42)

bool(0)

bool('zéro')

bool(False)

bool(False != True)

bool(True != True)

bool(True * False)

bool(True - 1)

Type chaîne de caractères (string)

En python les chaines de caractères sont instances du type str, et sont des séquences ordonnées de valeurs de point de code UNICODE, pouvant être notées en hexadécimal.

>>> "\U00000394"
'Δ'
>>> ord("\U00000394")
916

Le terminal peut ne pas être en mesure de représenter le glyphe du caractère UTF-8. Dans ce cas, le système affiche le point de code.

>>> "\U00000394"
'\u0394'

Fort heureusement, le développeur utilise rarement les point de code directement, préférant les glyphes dès que permis.

>>> print('\u0394\u0395\u0394')
ΔΕΔ

>>> print('ΔΕΔ')
ΔΕΔ

>>> '\u0394\u0395\u0394'[1]
'Ε'

>>> len('\u0394\u0395\u0394')
3

>>> print("Hello World")
Hello World

EXERCICE 25 Opérations courantes sur les strings

Voici une liste à compléter, non exhaustive, d’opérations usuelles sur le type str, à renseigner par vous-même à partir de ces ressources string-methods documentation et common-sequence-operations - il y a bien entendu d’autres ressources non officielles, comme sur wikibooks.org par exemple.

Table 3. à compléter à partir de cet exemple : x = " Hello World ! " (15 caractères)
Opération Python Exemple

Extraire le "caractère" 'H' (chaîne de longueur 1) de x

Utilisation de la notation [] en spécifiant la position de l’élément souhaité

c = x[1] # 'H' est le 2ième élément de la chaîne référencée par x (le premier est un espace)

Extraction de la sous-chaîne 'Hello'

Changement de casse

(majuscule <-> minuscule)

Suppression de "blancs" en queue et/ou en tête

Recherche d’une occurrence d’une sous-chaîne dans la chaîne

Compteur le nombre de 'l' dans x

Quel est l’opérateur de concaténation ?

(qui crée une nouvelle str à partir de plusieurs autres)

Peut-on comparer des chaines entre elles ?

Comment tester qu’un chaîne est vide ?

Comment obtenir la longueur d’une chaîne ? (nombre de points de code de la chaîne)

Tentative de conversion : chaîne <-> numérique

Travaux pratique

EXERCICE 26

On vous demande de programmer le scénario suivant (fonction nommée exo26) :

Scénario exo 26

L’utilisateur est invité à entrer deux lignes de texte respectant chacune le format suivant :

<public>;<code produit>;<qté vendue>;<mois>;<année>;<commentaire>

Le programme, après avoir vérifié le nombre de champs (6) de chacune de ces lignes, affiche le détail des informations (Mois, Année, Public, Code Produit, Qté vendue et Commentaire)

Il y a deux cas de figure :

  • Le code produit est le même, alors les informations ne seront affichées qu’une seule fois,

  • Le code produit est différent, les informations seront affichées en deux fois.

Exemples de scénarios à reproduire

Listing 12. Scénario A (même code produit pour les deux lignes)
Entrez une première ligne d'informations de ventes :
F;a12;21;mars;2023; Attention, léger défaut...

Entrez une deuxième ligne d'informations de ventes :
F;a12;14;avril;2023; Attention, léger défaut...

Voici la fiche produit :

	Date : MARS-2001, AVRIL-2023
	Public : FEMME
	Code Produit : A12
	Qté vendue  : 35
	Commentaire : Attention, léger défaut...
Listing 13. Scénario B (codes différents, une ligne sans commentaire)
Entrez une première ligne d'informations de ventes :
F;a12;21;mars;2023; Attention, léger défaut...
Entrez une deuxième ligne d'informations de ventes :
H;d45;11;mars;2023;

Voici les fiches produit :

Date : MARS-2023
Public : FEMME
Code Produit : A12
Qté vendue  : 21
Commentaire : Attention, léger défaut...

Date : MARS-2023
Public : HOMME
Code Produit : D45
Qté vendue  : 11
Commentaire : AUCUN
Listing 14. Scénario C (mauvais format de ligne)
Entrez une première ligne d'informations de ventes :
F;a12;21;mars-2001; Attention, léger défaut...
ERREUR de format !

Pour éviter de faire saisir les 2 lignes à l’utilisateur, vous pouvez lui demander de passer ces informations en argument du programme. Exemple :

$ python tp2Final.py < produits.txt

produits.txt est un fichier texte contenant 2 lignes de produits au format attendu (transmis sur le canal standard)

Le module fileinput est très pratique pour ce type d’exploitation. L’exemple ci-dessous affiche le contenu du fichier reçu en argument - en fait il lit l’entrée standard jusqu’à attendre le symbole de fin de transmission (U+0004 - CTRL+D) (ou CTRL+Z sous windows). Testez-le !

# https://docs.python.org/3/library/fileinput.html
def process_data() :
    import fileinput
    for line in fileinput.input(encoding="utf-8"):
        print(line)

EXERCICE 27

On se place dans le scénario A. Nous appellerons A' (A prime) ce scénario étendu. Le voici :

Scénario exo 27

Si le code produit est de nouveau saisi sur la deuxième ligne, alors on considère que l’utilisateur souhaite une mise à jour du produit. Seuls les champs renseignés (non vide) lors de cette seconde saisie seront mis à jour.

Travail à faire
  1. Donner un exemple de scénario A', avec des valeurs comme dans l’énoncé.

  2. Implémenter et, bien entendu, tester le scénario A'

EXERCICE HACKING 28

Voici un programme python faisant usage de caractères unicode non conventionnels. Certains de ces caractères peuvent ne pas avoir un glyphe spécifique associé !!

C’est une technique d'obfuscation de code source, bien connue des hackers malveillants, cherchant à décourager les personnes souhaitant comprendre le code source (souvent encodé à la base)

On vous assure que ce code est sans risque ! n’hésitez pas à l’exécuter.

Travail à faire
  1. Votre mission consiste à déchiffrer ce code (le remettre en clair), en expliquant votre démarche.

import sys as Ꭓ𝑿
import random as X
X.seed('𝒙')
𝒙 = [print,"Hello World !",2,"Hello",__name__,1,"",len,"__main__","!"]
X.shuffle(𝒙)

def Ꭓ(𝑿):
    if 𝑿:
        𝒙[0](𝒙[1], 𝑿, 𝒙[2])
    else:
        𝒙[0](𝒙[3])
if (𝒙[4] == 𝒙[5]):
    (Ꭓ(𝒙[8]) if (𝒙[6](Ꭓ𝑿.argv) < 𝒙[7]) else Ꭓ(Ꭓ𝑿.argv[𝒙[9]]))

Contrôler vos connaissances et contribuer aux QCMs

  1. Contrôler vos connaissances sur quizbe.org. (choisir PYTHON-1, scope p-2-type)

  2. Proposer, pour le thème PYTHON-LDV, scope p-2-type, 2 questions QCM originales et personnelles, sur des thèmes couverts pas cette séquence d’exercices.

Python-3 Déboguer

Qu’est-ce que le débogage ?

  • Le débogage est un processus d’identification d’erreurs dans le code source d’un programme.

  • Les erreurs dans le code sont appelées des "bugs".

  • Les bugs peuvent provoquer des comportements indésirables ou un plantage du programme.

Pourquoi le débogage est-il important ?

  • Le débogage permet de comprendre plus rapidement la nature de certaines erreurs.

  • Le débogage aide à comprendre le fonctionnement du code, ce qui améliore les compétences en programmation.

Méthodes de débogage

Il existe plusieurs approches, complémentaires entre elles.

  1. Analyser la logique du programme pour en détecter les failles.

  2. Afficher des valeurs en cours d’exécution.

  3. Interagir pas à pas avec le programme en cours d’exécution en utilisant un debogger

Afficher des valeurs

La technique consiste à utiliser provisoirement des instructions print afin d’obtenir des traces d’exécution sur la console de lancement du script.

Listing 15. Exemple print
print(f"Le prédécesseur de {chr(x)} est {chr(pred(x))}") (1)
1 Cet exemple utilise la technique qui permet de formater des chaînes littérales incluant des expressions entre accolades {}. Attention à préfixer la chaîne avec 'f' ou 'F'. Ces chaînes sont appelées formatted string literal ou f-string

Cette technique a ses limites. Elle s’avère fastidieuse lorsqu’il y a plusieurs variables à inspecter. De plus, le développeur ne doit pas oublier de supprimer (ou de commenter) ces instructions de débogage une fois le problème réglé.

Utiliser le débogueur (debugger) intégré

Analyser un code avec un débogueur c’est entreprendre une analyse du programme en cours d’exécution.

Le débogage est une action qui consiste à interroger la mémoire vive d’un programme à l’exécution, à un instant t.

L’instant débogué est d’ordinaire celui du pointeur d’exécution [1].

L’idée est d'arrêter provisoirement l’exécution d’un programme afin d’interroger son contexte mémoire, une sorte d’instantané d’une partie de la mémoire vive. Le développeur peut alors consulter la valeur des variables exploitées à ce moment là par le programme et comparer ces valeurs avec ses prédictions afin d’y déceler d’éventuels anomalies.

Python dispose d’un débogueur intégré appelé pdb, qui permet d’exécuter le code pas à pas et d’inspecter les variables à chaque étape.

Utilisez les commandes break, continue, step, print, etc., pour naviguer dans le code et examiner les valeurs.

En mode console

L’instruction breakpoint() permet de mettre le programme en pause afin de consulter des valeurs à un instant t choisi par le développeur.

Listing 16. Exemple d’une instruction de point d’arrêt
def exo3() -> None :
  x=int(input("Entrez un nombre entier svp : "))
  breakpoint() (1)
  if x%2==0 :  # la valeur de x est-elle divisible par 2 ?
    print("Ce nombre est pair")
  else :
    print("Ce nombre est impair")
1 L’exécution du programme s’arrêtera ici. Le développeur peut alors interroger la valeur de la variable x.
Listing 17. Exemple d’interactions avec le débogueur
$ ./test.py
Entrez un nombre entier svp : 2
> test.py(19)exo3() (1)
-> if x%2==0 :  # la valeur de x est-elle divisible par 2 ?
(Pdb) x  (2)
2 (3)
(Pdb) continue (4)
Ce nombre est pair (5)
$
1 Le débogueur Pdb a mis en pause l’exécution du programme (ligne 19)
2 (mode interactif) Le développeur, en interaction avec le débogueur, demande la valeur de la variable x
3 Le débogueur retourne 2 (la valeur saisie par l’utilisateur)
4 Le développeur demande la poursuite du programme jusqu’au prochain point d’arrêt (sinon, pour avancer pas à pas, utiliser step)
5 Le programme s’est terminé car il n’y a plus aucun autre point d’arrêt
EXERCICE 30

Placer l’instruction breakpoint() dans un de vos scripts, et tester le.

Débogueur piloté par l’IDE

C’est la façon la plus souple d’interagir avec un débogueur.

Nous utilisons code (visual studio code) pour la démonstration.

Définition d’un point d’arrêt

Dans l’éditeur, une colonne (à gauche) est dédiée à la pause de point d’arrêt.

Exemple : code-debug-1-dia.png

Avancement pas à pas

Dans l’éditeur, une colonne (à gauche) est dédiée à la pause de points d’arrêt.

Exemple pour entrer dans le code d’une fonction appelée code-debug-2

Exemple pour ne pas entrer dans le code d’une fonction appelée code-debug-3

Exemple de consultation des variables locales code-debug-4

N’hésitez pas à explorer les autres commandes de la bare de debug (continue, restart, stop)

EXERCICE 31

Reprendre l’exemple ci-dessous, supprimer tous les points d’arrêt.

Ajouter un point d’arrêt conditionnel sur la ligne 6. Pour cela, une fois placé le point d’arrêt, faire un clic droit sur celui-ci , puis sélectionner Edit breakpoint…​. Comme valeur de l’expression, inscrire par exemple x == 42.

Lancer le mode debug. Le programme se mettra en pause debug que si la valeur de x égalera 42.

Une technique fort pratique pour pister des erreurs !

Testez-la !

Les assertions pour se prémunir de certains bugs

Les assertions sont des instructions qui vérifient la cohérence de valeurs en cours d’exécution dans le code.

Elles peuvent être utilisées pour vérifier des résultats intermédiaires ou des conditions d’utilisation de fonctions (particulièrement dans le cas de langage de script non typés).

L’instruction assert

L’instruction assert permet de vérifier qu’une condition est vraie à un point particulier du code. Si la condition n’est pas vérifié, une exception (AssertionError) est alors déclenchée qui, si elle n’est pas traitée par l’appelant, entraînera l’arrêt non contrôlé du programme (bug)

Listing 18. Exemple d’instruction assert
def extract_name(full_name : str) -> tuple : (1)
    """
    Algorithme naïf, full_name de la forme "prénom nom"
    Retourne une liste de 2 éléments
    """
    assert isinstance(full_name, str), "Le nom complet doit être une chaîne de caractères."
    names = full_name.split() # place les différentes partie de full_name dans une liste
    assert len(names) >= 2, "Le nom complet doit contenir au moins un prénom et un nom de famille."
    first_name = names[0]
    last_name = names[-1]
    return first_name, last_name

# Exemple d'utilisation
try:
    full_name = "John Doe"
    first_name, last_name = extract_name(full_name)
    print(f"Prénom : {first_name}")
    print(f"Nom de famille : {last_name}")
except AssertionError as e:
    print(f"Erreur : {e}")
1 La structure de données tuple (une liste de valeurs) sera étudiée dans la section suivante

Dans cet exemple, nous avons une fonction extract_name() qui extrait le prénom et le nom de famille d’une chaîne de caractères qui représente un nom complet.

L’instruction assert est utilisée pour vérifier deux choses :

  • La variable full_name doit être une chaîne de caractères. Si ce n’est pas le cas, une exception AssertionError est levée avec le message : "Le nom complet doit être une chaîne de caractères."

  • La chaîne de caractères full_name doit contenir au moins un prénom et un nom de famille. Si la chaîne ne contient pas au moins deux parties (prénom et nom de famille) séparées par un espace, une exception AssertionError est levée avec le message : "Le nom complet doit contenir au moins un prénom et un nom de famille."

Ces assertions nous permettent de nous assurer que la fonction extract_name() est correctement utilisée avec une chaîne de caractères représentant un nom complet. Si un développeur utilise la fonction de manière incorrecte en passant un autre type de variable ou une chaîne de caractères mal formatée, les assertions lèveront une exception pour signaler le problème.

Les assertions sont particulièrement utiles pour valider les entrées de fonction, les préconditions et les invariants, permettant ainsi de s’assurer que le code est utilisé correctement et en accord avec les attentes du développeur concepteur de la fonction.
EXERCICE 33

Tester la fonction extract_name, en pas à pas avec le débogueur, avec au moins 3 scénarios :

  1. un de réussite (à l’instar de celui fourni)

  2. un avec une erreur de type

  3. un avec une erreur de mauvais contenu de chaîne

Pour les plus avancés :

  1. Modifier la fonction pour qu’elle considère que le nom de famille est toute la seconde partie après le prénom (premier mot). À tester avec Guido van Rossum.

Contrôler vos connaissances et contribuer aux QCMs

  1. Contrôler vos connaissances sur quizbe.org. (choisir PYTHON-1, scope p-3-debug)

  2. Proposer, pour le thème PYTHON-1, scope p-3-debug, 2 questions QCM originales et personnelles, sur des thèmes couverts pas cette séquence d’exercices.

Python-4 Collections

Mise en condition

Nous avons vu qu’un programme manipule des valeurs. Ces dernières sont principalement référencées par des variables, ou constante définies directement dans le code source (constante littérale).

Nous avons alors une correspondance simple entre u identifiant et une valeur.

Une variable étant associée, à un instant t, à un type de donnée et à une et une seule valeur.

EXERCICE 42

Prenons un exemple simple d’un programme qui calculerait la note d’un joueur en fonction de ses différents scores aux compétitions, obtenus au cours de l’année en cours. Le calcul de la note d’un joueur est la moyenne de ses scores, mais est exclut de ce calcul un des scores correspondant à la valeur minimale des scores. Exemple, si les scores sont 0, 100, 100, 100 alors le 0 ne sera pas comptabilisé dans la moyenne, donc la note du joueur sera (100 + 100 + 100) / 3 = 100.

Pour les besoins de l’exercice, on conçoit une fonction, nommée note, qui prend 4 valeurs de scores, que l’on nommera score1 à score4 (valeurs supérieures ou égales 0) et qui calcule la note selon la règle précisée ci-dessus. Voici l’interface de la fonction à implémenter en python :

def note(score1, score2, score3, score4) -> int

Travail à faire

  1. Implémenter la fonction note dans le respect de la logique du calcul présentée ci-dessus.

  1. Vérifier que votre fonction retourne bien la valeur attendue. Pour cela nous vous communiquons la fonction de tests suivante :

def test_note() -> bool:
    return (
        note(0, 100, 100, 100) == 100
        and note(0, 100, 100, 0) == 67
        and note(100, 100, 100, 100) == 100
        and note(0, 0, 0, 100) == 33
        and note(0, 0, 100, 0) == 33
        and note(0, 100, 0, 0) == 33
        and note(100, 0, 0, 0) == 33
        and note(0, 0, 0, 0) == 0
        and note(100, 100, 100, 1000) == 400
    )

Lancer votre fonction testNote() à partir du main de votre script.

if __name__ == "__main__":
    print(test_note())
  1. L’appel de la fonction testNote() doit rendre True. Corrigez l’implémentation de votre fonction note si nécessaire.

IMPORTANT : Aborder la suite qu’après avoir réussi à résoudre l’exercice 42. Faites-vous aider le cas échéant. On apprend par nos erreurs

Structure de données de type Collection

Nous avons défini une fonction note, qui prend en argument 4 valeurs. S’il fallait prendre 5 ou plus de valeurs, il nous faudrait définir d’autres versions de cette fonction…​ Exemple.

def note(score1, score2, score3, score4) -> int
def note5(score1, score2, score3, score4, score5) -> int
def note6(score1, score2, score3, score4, score5, score6) -> int
etc.

Mais ce n’est pas la bonne solution !

Rappelez-vous de cela, si un jour vous êtes amené à penser de la sorte, alors votre analyse est certainement à revoir.

Il y a mieux à faire. La bonne solution consiste à passer une liste - ou collection - de valeurs à la fonction note.

Python propose plusieurs types prédéfinis de collections [2]. Pour l’heure nous nous intéressons aux principaux types natifs : list, set, dict et tuple.

  1. list est une structure de données de type séquentiel, au même titre que str, mais muable. Une instance de list dispose des mêmes opérations que str. Donc vous êtes déjà familiarisé avec cette structure, sauf que les éléments de list ne sont pas réduits à des chaînes. Particularité : type séquentiel, donc les index sont des entiers (commence à zéro) et la structure est muable, elle permet donc l’ajout, la suppression et le remplacement d’éléments (contrairement à str).

  2. set est une structure de données muable de type ensemble, non organisée séquentiellement. Particularité : structure non indexée et n’accepte pas d’éléments en doublon.

  3. dict est une structure de données muable de type dictionnaire, non organisée séquentiellement. Particularité : les index sont des objets, par exemple des index de type string, comme un dictionnaire (nom → définition), d’où son nom.

  4. tuple est une structure de données immuable de type séquentiel. Particularité : type séquentiel, donc les index sont des entiers (comme string)

Une classification des principales collections natives :

collections heritage1

À noter : Il existe également un type set immuable : frozenset.

Opérations courantes sur les structures séquentielles

Déclaration de variables Collection

Exemple de déclaration et initialisation de variables de type conteneur (autre nom pour collection)

def declare_collections() :
    """ Exemple de syntaxe de déclaration et initialisation de collections """
    une_liste : list = ['a', 'a', '123', 123] # crochets (bracket)
    un_tuple  : tuple = ('a', 'a', '123', 123) # parenthèses (optionnelles)
    un_ensemble : set = {'a', 'a', '123', 123} # accolades (brace)
    un_dico : dict = {1:'a', 3:'a', 5:'123', 7:123} # accolades avec couples clé:valeur comme éléments (key:value)

    print(f"Nombre d'éléments de la liste : {len(une_liste)}")  # 4
    print(f"Nombre d'éléments du tuple : {len(un_tuple)}")      # 4
    print(f"Nombre d'éléments de l'ensemble : {len(un_ensemble)}") # 3 (pas de doublons)
    print(f"Nombre d'éléments du dictionnaire : {len(un_dico)}")   # 4
    print('-------------------------------------------------------')
    print(f"Premier élément de la liste (index 0) : {une_liste[0]}")
    print(f"Premier élément du tuple (index 0) : {un_tuple[0]}")
    print("Premier élément du set : Non Applicable ") # de plus l'ordre des éléments dans un `set` n'est pas garanti...
    print(f"Élément du dico (ici clé = 1) : {un_dico[1]}")
Les structures de données natives ont leur propre syntaxe qu’il faut connaître. []list, {}set et dict, {key:value}dict, ()tuple.

Création d’instances de tuple

Les tuples peuvent être construits de différentes façons :

  • en utilisant une paire de parenthèses pour désigner le tuple vide : () ;

  • en utilisant une virgule, pour créer un tuple d’un élément : a, ou (a,) ;

  • en séparant les éléments avec des virgules : a, b, c ou (a, b, c) ; (parenthèses optionnelles)

  • en utilisant la fonction native tuple() : tuple() ou tuple(iterable).

Création d’instances de set

Les ensembles peuvent être construits de différentes manières :

  • création d’un ensemble vide : set() ({} est réservé au dictionnaire)

  • en utilisant une liste d’éléments séparés par des virgules entre accolades : {'Bob', 'Alice'}

  • en utilisant un ensemble en compréhension : {c for c in 'abracadabra' if c not in 'abc'} (donne {'d', 'r'});

  • en utilisant le constructeur du type : set(), set('foobar'), set(['a', 'b', 'foo']).

Les set sont de type muable (mutable), mais il existe aussi le type frozenset qui est un set immuable (immutable).

Ces deux classes disposent des opérations sur les ensembles bien pratiques telles que : la différence, l'union et l'intersection de 2 ensembles.

ens operations venn

Création d’instances de dict

Les dictionnaires peuvent être construits de différentes manières :

  • en utilisant une liste de paires clé: valeur séparées par des virgules entre accolades : {'Bob': 13, 'Alice': 42} ou, inversion clé/valeur, {13: 'Bob', 42: 'Alice'}

  • en utilisant un dictionnaire en compréhension : {}, {x: x ** 2 for x in range(10)} ;

  • en utilisant le constructeur du type : dict(), dict([('foo', 100), ('bar', 200)]), dict(foo=100, bar=200).

Choisir la bonne structure de données

Si la collection de type liste n’a pas à être modifiée une fois créée, préférer un type immuable. Ces structures de données sont plus efficaces. Donc tuple au lieu de list.

Si les index sont des valeurs métier, le dictionnaire s’impose, sinon une structure indexée par des entiers est plus efficace en générale.

Si la structure de données conteneur doit faire l’objet d’opérations ensembliste comme l’union, l’intersection etc. alors préférer le type set.

Itérer sur tous les éléments d’une collection

Boucle for idiomatique :

fruits = ['tomates', 'bananes', 'kiwis'] (1)

for fruit in fruits : (2)
    print(fruit)  (3)
1 (IMPORTANT) Il est d’usage d’utiliser le pluriel (fruits) pour le noms de variables de type collection
2 La variable fruit (au singulier) est une variable de boucle (créée pour l’occasion), qui var prendre, successivement, la valeur de chacun des éléments de la liste.
3 Le corps de la boucle. Ici on affiche la valeur de l’élément courant (ne pas hésitez à pauser un point d’arrêt sur cette ligne pour comprendre l’itération )

Un exécution donnera :

tomates
bananes
kiwis

Attention, dans le cas des dictionnaires, cette structure retournera les clés, pas les valeurs.

un_dico : dict = {1:'a', 3:'a', 5:'123', 7:123}
for k in un_dico :
    print(k, un_dico[k])

'''
result :
1 a
3 a
5 123
7 123
'''

Autres modèle d’itérations :

modèle idiomatique

C’est un modèle d’itération présenté dans le guide de l’auto-stoppeur python

fruits = ['tomates', 'bananes', 'kiwis']

for index, fruit in enumerate(fruits) : (1)
    print(index, fruit)  (2)
1 utilise la fonction native enumerate (pour les listes, ou tuples)
2 affiche l’index en plus de la valeur de l’élément

Qui donnera :

0 tomates
1 bananes
2 kiwis
Modèle "classique"
fruits = ['tomates', 'bananes', 'kiwis']

i = 0 (1)
while i < len(fruits) : (2)
    print(i, fruits[i]) (3)
    i += 1 (4)

print(i) (5)
1 Les indices commencent à zéro
2 Tant que i est inférieur au nombre d’éléments
3 Affiche l’index et l’élément situé à cet index
4 Incrémente i
5 Affiche la valeur de i qui est len(fruits) (condition d’arrêt de l’itération)

Qui donnera :

0 tomates
1 bananes
2 kiwis
3
Modèle non idiomatique
fruits = ['tomates', 'bananes', 'kiwis']

for index in range(len(fruits)):
    print(index, fruits[index])

Bien qu’opérationnel, ce modèle appelle range, qui crée inutilement une séquence (immuable) de nombres en guise d’indices.

Cette remarque se vaut dans la mesure où l’on souhaite itérer sur la totalité des éléments de la liste.

Avec un itérateur (iterator)

En fait, toutes les collections sont itérables. Python dispose de 2 fonctions iter et next qui permettent de boucler sur les éléments d’une collection (mais pas seulement)

iterateur = iter(fruits)
print(next(iterateur))
print(next(iterateur))
print(next(iterateur))

Qui donnera :

tomates
bananes
kiwis

La fonction next() donne l’élément suivant (le premier appel à next donne le premier élément). Un appel à next() alors que le dernier élément a déjà été atteint déclenchera une exception de type StopIteration.

Ainsi, peut-on reproduire ce que fait en réalité la structure de boucle for:

iterateur = iter(fruits)
while(True) : (1)
  try:
    fruit = next(iterateur)
    print(fruit)
  except StopIteration :
    break (2)

# equivalent à : for fruit in fruits : print(fruit) (3)
1 Oh, boucle infinie !
2 Ouf, fort heureusement l’instruction break provoquera l’arrêt de la boucle lorsque que l’exception StopIteration sera déclenchée.
3 Syntaxe à préférer !

EXERCICE 43

L’objectif est de réécrire la fonction note (exercice 42) afin de lui passer en paramètre, non pas 4 valeurs, mais une collection de valeurs numériques (c’est plus souple).

Voici une nouvelle version de l’interface de la fonction note :

def note(scores : tuple) -> int (1)
1 Le paramètre est typé tuple plutôt que list car la fonction n’a pas vocation à modifier les éléments de la collection reçue en argument (voir Choisir la bonne structure de données)
Une autre approche consiste à placer une étoile (`*`) en *préfixe du paramètre* (qui dénote un tuple)  -- une double étoile (`**`) dénote un dictionnaire.

def note(*scores) → int

Dans ce cas, il faut omettre les parenthèses englobant les arguments. Exemple:

note(100, 100, 100, 1000, 100) # 325

Travail à faire
  1. Implémenter cette nouvelle fonction

  2. Proposer une nouvelle version de test_note() de l’exercice 42 afin de tester la nouvelle fonction

  3. Étendre les scénarios inclus dans test_note() qui passe à note un tuple de plus ou moins 4 éléments. Exemple.

    note((100, 100, 100, 1000, 100)) # 325 (5 éléments)
    note((100, 100)) # 100 (2 éléments)

    Proposer au moins 4 autres scénarios. Revenir sur votre implémentation de la nouvelle fonction note si nécessaire.

  1. Faire en sorte que la fonction ne puisse pas buguer lorsqu’on lui passe une liste de valeurs avec un nombre incorrect d’éléments (à identifier). (2 cas)

    Indice : Utiliser assert (ref cours sur le debugger), et mettre à jour la docstring de la fonction note.

EXERCICE 44

Voici une version d’une fonction qui prend 2 listes et retourne une liste des éléments communs.

def recherche_elements_communs(list1 : list, list2 : list) -> list :
    common_elements = []
    for item1 in list1:
        for item2 in list2:
            if item1 == item2:
                common_elements.append(item1) # ajoute cet élément
    return common_elements

Exemple

recherche_elements_communs(['a', 'b'], ['x', 'b', 'y', 'z']) # retourne ['b']
Travail à faire
  1. Déterminer le nombre de parcours de boucles de l’exemple ci-dessus. Utiliser le débogueur pour une exécution pas à pas de la fonction à fin de vérification. Inspecter les variables item1 et item2 à chaque itération de la boucle.

  2. Donner une formule générale qui donne le nombre d’itérations, quelque soit le nombre d’éléments des arguments

  3. Définir une fonction nommée test_recherche_elements_communs qui teste plusieurs scénarios (inspirez-vous de l’exemple donnée et de test_note())

  4. Proposez une amélioration de l’implémentation de la fonction test_recherche_elements_communs(). Il y a plusieurs possibilités, l’idée est de faire confiance aux structures existantes du langage. Attention, l’interface de la fonction ne doit pas être modifiée !

    def recherche_elements_communs(list1 : list, list2 : list) → list

  5. Tester la nouvelle version de votre fonction

EXERCICE 45

Concevoir et tester une fonction qui prend en argument une liste d’objets et qui retourne une liste des éléments en doublons. Voici son interface :

def doublons(liste : list) → list

Exemple d’utilisation :
doublons(['1','X','2','2','3','4','3','A','a','X']) # rend ['X', '2', '3']
Travail à faire
  1. Implémenter la fonction doublons

  2. Définir une fonction qui teste plusieurs scénarios d’utilisation de cette fonction.

  3. Définir une deuxième implémentation de la fonction doublons

  4. Concevoir une nouvelle fonction qui prend en argument une liste et qui détermine si cette liste détient des doublons ou non.

    1. Définir l’interface de cette fonction

    2. Implémenter cette fonction

    3. Définir une fonction de test pour cette fonction

Contrôler vos connaissances et contribuer aux QCMs

  1. Contrôler vos connaissances sur quizbe.org. (choisir PYTHON-1, scope p-2-collection)

  2. Proposer, pour le thème PYTHON-LDV, scope p-2-collection, 2 questions QCM originales et personnelles, sur des thèmes couverts pas cette séquence d’exercices.

Python-Projet 1

Big Data et Web Scraping

Le web regorge de données exposées, le plus souvent, au format HTML.

Ces données peuvent être extraites des sites web. On parle alors de web scraping

Le web scraping est une technique d’extraction des données de sites Web par l’utilisation d’un script ou d’un programme dans le but de les transformer et les réutiliser dans un autre contexte comme l’enrichissement de bases de données, le référencement ou l’exploration de données. (source : Web_scraping Wikipedia)

Les IA sont très gourmandes de ces données, mais pas que.

L’exploitation de données publiques ou privées est au coeur des systèmes décisionnels. C’est une branche à part entière de l’informatique.

développement de solutions Big Data, à l’aide des techniques les plus récentes de l’intelligence artificielle, de l’apprentissage automatique et de la fouille de données, et à l’aide d’architectures distribuées. Ils seront capables de comprendre et maîtriser le traitement des données structurées ou non, numériques et textuelles, pour dialoguer avec les experts métier, ainsi que les mathématiques appliquées nécessaires au domaine. La place de l’alternance est importante dans ce parcours. Attention, pour pouvoir entrer dans ce parcours en M2 il faut avoir obtenu l’option BD en M1.

BeautifulSoup

Beautiful Soup est un package Python pour parser (traiter) des documents HTML et XML (incluant des documents mal formés, i.e. balise non fermées par exemple, d’où le nom de tag soup). Une solution couramment utilisée pour du web scraping.

Beautiful Soup a été crée par Leonard Richardson, qui continue à contribuer à ce project.

TP préparatoire

À partir de l’exemple de code suivant, extrait de la document sur Wikipédia

#!/usr/bin/env python3
# Anchor extraction from HTML document
from bs4 import BeautifulSoup
from urllib.request import urlopen
with urlopen('https://en.wikipedia.org/wiki/Main_Page') as response:
    soup = BeautifulSoup(response, 'html.parser')
    for anchor in soup.find_all('a'):
        print(anchor.get('href', '/'))
Travail à faire
  • Concevoir un script qui extrait les liens en https de la ressource web suivante :

    sous la forme d’un document HTML bien formé.

    Attention : Pour faciliter la consultation du résultat de l’extraction des liens https, Le texte des liens devra être tronqué s’il dépasse une certaine longueur (75 caractères)

Exemple de lancement du script :

$ python3 tp-bs4-web-scraping.py > res.html

Exemple de résultat attendu web-scraping

REAMRQUE : Dans cet exemple, 2 textes de lien sont tronqués (et donc terminés par 3 points de suspension, comme la coutume l’impose afin d’en informer l’utilisateur).

Listing 19. Exemple du document res.html généré par votre programme :
<!DOCTYPE html>
<html>
<head>
 <meta name="viewport" content="width=500, initial-scale=1" />
 <style></style>
</head>
<body>
<h1>Exemple de web scraping</h1>
<ul><li><a href="https://github.com/kennethreitz/python-guide" target="_blank">https://github.com/kennethreitz/python-guide</a></li>
<li><a href="https://github.com/python-guide-fr/python-guide/issues" target="_blank">https://github.com/python-guide-fr/python-guide/issues</a></li>
<li><a href="https://www.transifex.com/python-guide-fr/python-guide-fr/" target="_blank">https://www.transifex.com/python-guide-fr/python-guide-fr/</a></li>
<li><a href="https://twitter.com/kennethreitz" target="_blank">https://twitter.com/kennethreitz</a></li>
<li><a href="https://www.amazon.com/Hitchhikers-Guide-Python-Practices-Development/dp/1491933178/ref=as_li_ss_il?ie=UTF8&linkCode=li2&tag=bookforkind-20&linkId=804806ebdacaf3b56567347f3afbdbca" target="_blank">https://www.amazon.com/Hitchhikers-Guide-Python-Practices-Development/dp/14...</a></li>
<li><a href="https://djangogirls.org" target="_blank">https://djangogirls.org</a></li>
<li><a href="https://github.com/kennethreitz/records" target="_blank">https://github.com/kennethreitz/records</a></li>
<li><a href="https://github.com/kennethreitz/python-guide/graphs/contributors" target="_blank">https://github.com/kennethreitz/python-guide/graphs/contributors</a></li>
<li><a href="https://media.readthedocs.org/pdf/python-guide-fr/latest/python-guide-fr.pdf" target="_blank">https://media.readthedocs.org/pdf/python-guide-fr/latest/python-guide-fr.pd...</a></li>
<li><a href="https://github.com/kennethreitz/python-guide" target="_blank">https://github.com/kennethreitz/python-guide</a></li>
</ul>
</body>
</html>

Conseil méthodologique.

Allez-y progressivement, par étapes (c’est la méthode clé pour programmer).

Exemple de décomposition :
  1. Extraire en premier tous les liens des balises a, en plaçant ces liens comme item d’un liste, exemple : <ul> <li> lien1 </li> <li> lien 2 </li> etc. </ul>. Prendre soin de placer cette liste dans le <body> d’une structure html bien formée.

  2. Puis ne retenir que les liens commençant par https

  3. Enfin tronquer le texte des liens en plaçant 3 points de suspension en fin de chaîne (voir le code de l’exemple ci-dessus)

Projet Web Scraping

À partir de l’expérience acquise au cours de ces moments passés à coder en Python, et en vous inspirant du travail réalisé à l’issu du dernier TP, proposez (analyser, coder, tester) une application Python (sous la forme d’un script) qui extrait et met en forme des données d’un ou plusieurs sites web de votre choix.

Vous placerez ce travail sous la forme d’un projet hébergé sur GitHub, en mode public, et rédigerez votre rapport sous la forme d’un README de votre projet (markdown ou asciidoc). Par conséquent ce rapport sera librement consultable par vos professeurs, et servira de base à l’évaluation de votre projet, et enrichira votre portfolio !

Date de rendu du projet : à déterminer

Glossaire

variable

symbole dans le code, dont le nom témoigne de son rôle, qui désigne selon son utilisation soit un contenant (une adresse mémoire) soit une valeur de contenu (interprétée selon son type)

type

Pour l’interpréteur, désigne comment utiliser et stocker la valeur d’une variable.
Pour l’utilisateur, limite l’exploitation de la variable aux opérations définies sur ce type.

Listing 20. La fonction 'type' retourne le type de son argument
>>> type(42)
<class 'int'>
>>> type(42) is int
True
>>> type(42) is str
False
>>> type("42") is str
True
>>>
valeur d’une variable

interprétation du contenu de (l’espace occupé par) la variable, selon son type.

Exemple : x = "42" ou y = 42;

La valeur de x est "42" est son type est string

La valeur de y est 42 est sont type est int

Ces deux variables pointent vers des espaces mémoire peu comparables en terme de structure binaire et de taille.

Constante littérale

Valeur inscrite dans le code source

Ces valeurs sont typées. Exemple : 'ANNEE' "ANNEE" """ANNEE""" sont des constantes littérales de type string

Convention de nommage

Les conventions usuelles

Table 4. Les conventions courantes
nom usage

UpperCamelCase

nom de classe

lowerCamelCase

propriété, méthode, variable, paramètre…​ en Java, JS, PHP…​

lower_snake_case

nom de variable et fonction en python

SCREAMING_SNAKE_CASE

constante

kebab-case

ressource web, identifiant, attribut html

return

Directive signifiant la fin de l’exécution du corps de la fonction où elle est présente. Peut être suivie d’une valeur qui sera retournée à l’appelant.

Appelant

Instruction (un bout de code) qui appelle une fonction en se fiant à son entête. Exemple : x = len(y) est une instruction appelant l’exécution de la fonction len.

Appelé

Un sous-programme identifié par un nom (nom d’une fonction par exemple) Exemple : x = len(y), len est la fonction appelée. Dans cet exemple, la valeur de retour de la fonction appelée est stockée, par l’appelant, dans une variable nommée x.

Entête d’une fonction

Nommée également interface, définit le nom de la fonction, ses paramètres éventuels et son type de retour (ou None). Exemple (première ligne) :

def bidon() -> None :
  pass
Corps d’une fonction

Nommée également implementation, définit le travail de la fonction, et sa valeur de retour, si attendue. La séquence d’instructions est placée dans le bloc indenté qui suit l’entête de la fonction.

Algorithme déterministe

(sensibilisation) Une fonction, pour une même donnée d’entrée, doit invariablement produire la même sortie.

Muable

Une structure de données muable signifie que les variables de ce type peuvent sont susceptibles d’avoir leur valeur évoluée dans le temps. Exemple list, contre exemple str (ne peut pas être modifiée après création)

Immuable

Une structure de données immuable signifie que les variables de ce type ont une valeur qui ne peut être modifiée après leur création. Exemple str ou tuple, ne permettent pas l’ajout d’éléments, et leurs éléments ne peuvent être ni modifiés ni supprimés. Contre exemple list (accepte l’ajout, et les éléments d’une instance de list peuvent être modifier, supprimer)


1. peut être aussi postmortem
2. Le développeur a la possibilité d’en définir d’autres, via la classe de base abstraite collections.abc https://docs.python.org/fr/3/library/collections.abc.html