Aller au contenu

Bonnes pratiques Logicielles

Programme

Notions Compétences Remarques
Mise au point des programmes.
Gestion des bugs.
Dans la pratique de la programmation, savoir répondre aux causes typiques de bugs : problèmes liés au typage, effets de bord non désirés, débordements dans les tableaux, instruction conditionnelle non exhaustive, choix des inégalités, comparaisons et calculs entre flottants, mauvais nommage des variables, etc. On prolonge le travail entrepris en classe de première sur l’utilisation de la spécification, des assertions, de la documentation des programmes et de la construction de jeux de tests.
Les élèves apprennent progressivement à anticiper leurs erreurs.

Introduction

Le respect de certaines règles plus ou moins explicites permettent à tout programmeur de lire, comprendre et modifier le programme écrit par un autre. Nous allons voir ici quelques unes de ces règles.

La syntaxe en Python

Règles explicites du PEP8

Les règles de syntaxe de Python sont définies dans le document PEP8 (Python Enhancement Proposal) rédigé entre autres par Guido van Rossum lui-même.

A consulter sur https://www.python.org/dev/peps/pep-0008/

  • Le nom des variables, fonctions et méthodes s'écrit toujours en snake_case (ex : vitesse_voiture)
  • Le nom des classes (voir séquence 2) s'écrit toujours en CamelCase (ex : VoitureSport)
  • Le nom des constantes s'écrit toujours en lettres capitales (ex : VLUMIERE)
  • Les opérateurs sont censés être entourés d'un espace (sauf pour le passage de paramètres à une fonction ou pour des questions de lisibilité de grandes expressions mathématiques)
  • Pas d'espace à l'intérieur de (), [] ou {}
  • Pas d'espace avant : et , mais on en met après.
x = 2
a = x*2 - 1
b = x*x + y*y
c = (a+b) * (a-b)

def puissance(x, puis=3):
    if x == 5:
        return x**puis

print(puissance(5, 2))

La longueur des lignes est limitée en principe à 79 caractères. on peut raccourcir une longue ligne grâce à :

  • L'indentation
  • Des parenthèses
dico {
    0: 'zero',
    1: 'un',
    2: 'deux'
}

from module import (package1, package2, package3,
                    package4, package5, package6)

4 espaces (ou une tabulation)

  • 2 lignes entre les imports et les fonctions, puis entre chaque définition de fonction
  • Mais une seule ligne entre chaque méthode d'une classe (voir Chapitre 2)
import math


def f1():
    pass


def f2():
    pass

Linter

Un linter (flake8 ou pylint) est un analyseur de code vérifiant la conformité de la syntaxe. Le site pep8online.com par exemple est très simple d'utilisation. On copie/colle le code à évaluer puis on clique sur le bouton Check code.

Règles implicites

  • Choisir judicieusement le nom de ses variables : for x in y: ou for lettre in chaine: ?

  • Documenter chaque fonction avec un prototype (docstring). Ne pas hésiter à inclure un exemple simple et concret pour les fonctions compliquées

def calcul_moyenne(x, y):
  """
  Calcul de la moyenne de x et y

  param x: première valeur (int ou float)
  param y: seconde valeur (int ou float)
  return: moyenne de x et y

  Exemple:

  >>> calcul_moyenne(4, 7)
  5.5
  """
  • Anticiper et gérer les bugs en levant des exceptions et en incluant des test les plus complets possibles de manière à avoir la meilleur couverture de code (pourcentage de code couvert par les tests) possible
  • Chercher à optimiser le temps d'exécution du programme
  • Assurer la mise à jour de son programme
The zen of Python

Le poème "The zen of Python" écrit par Timp Peters résume la philosopie d'un bon programmeur en Python et est accessible par un simple import (surnommé oeuf de Pâques)

>>> import this

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Et la version française pour les non-anglophones :

https://fr.wikipedia.org/wiki/Zen_de_Python

Modularité

Définition

La modularité est le fait qu'un programme est découpé en modules logiciels afin de favoriser leur maintenance ou leur réutilisation dans d'autres contextes.

  • Un module est un ensemble de fonctions prédéfinies
  • Une bibliothèque correspond à un ensemble plus complet puisqu'elle comprend des fonctions, des constantes ET des modules

Encore faut-il savoir utiliser le module désiré... D'où la nécessité de lire la documentation de ce module en utilisant l'instruction help() !

Instruction help()

L'instruction help(nom_de_la_fonction) ou help(module) premet d'obtenir la liste des fonctions contenues dans ce module.

Testez dans la cellule ci-dessous, les lignes suivantes et analysez-les:

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

help(len)
import math

help(math)
Instruction dir()

Cette instruction permet de lister les attributs d'un objet (liste, tuple, dictionnaire, classe...).

number1 = [2, 3, 1]

print(dir(number1))

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Ici, une longue liste d'attributs apparaît.

On s'aperçoit ainsi que l'on peut, entre autres :

  • Ajouter un terme à la liste avec la méthode append
  • Trier une liste avec la méthode sort

Vérifiez tout cela avec :

number1.append(4)
print(number1)
number1.sort()
print(number1)

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

help() et dir() en résumé

  • L'instruction help() permet de se documenter sur des instructions, fonctions, modules...
  • L'instruction dir() permet de lister les attributs et méthodes d'un objet.
Activité 1 - Module de gestion de listes
  1. Créez un module personnalisé nommé algos_classiques_listes.py dans lequel vous mettrez toutes les fonctions concernant les listes. A savoir :

    • Recherche de la présence d'un élément \(e\) dans la liste \(L\): dans_liste(e, L)
    • Recherche de la valeur maximale (et/ou minimale) des élément d'une liste \(L\): max_liste(L)
    • Calcul de la moyenne des éléments d'une liste \(L\): moy_liste(L)
    • Recherche de l'indice de l'élément minimum: indice_min_liste(L)
    • Recherche du nb d'occurrences d'une valeur: card_val_liste(e, L)
    • Suppression de la première occurrence d'un élément: supp_liste(e, L)
    • Vous veillerez à respecter dans la mesure du possible les recommandations de PEP8.
  2. Créez un nouveau Notebook.

Dans des nouvelles cellules, copiez/coller le code ci-dessous et testez-le :

Notebook Jupyter

Notebook

import algos_classiques_listes as al

help(al.dans_liste)

print(al.dans_liste(2, [1, 2, 3]))

print(al.max_liste([1, 2, 3]))

print(al.moy_liste([1, 2, 3]))

print(al.indice_min_liste([1, 2, 3]))

print(al.card_val_liste(2, [2, 2, 2]))

print(al.supp_liste(1, [2, 1, 4, 1, 3]))

Corrigé

Gestion des erreurs

L'erreur étant humaine, même les meilleurs programmeurs en commettent. Ce qui peut avoir de grosses conséquences, tant au niveau humain (crash de navette spatiale...), économique (retour de produits défectueux...) ou encore en terme d'image et/ou de crédibilité.

Pour valider un code, il existe deux moments stratégiques :

  • à l'écriture : il faut documenter proprement son code
  • à l'exécution

C'est ce second point que nous allons développer.

Erreurs classiques

Typage dynamique

Python est un langage à typage dynamique: il n'impose pas que le type des variables soit défini. Le type est alors déterminé à la volée au moment de l'interprétation du code. On peut donc à loisir changer le type de données d'une variable. Cela peut poser problème si on n'est pas prudent. D'autres langages, tels le C, Java ou Pascal sont à typage statique, donc nettement moins permissifs que Python.

Testez et analysez ce programme :

a = "Hello World"
print(type(a))
a = 12
print(type(a))
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Activité 2 - Typage dynamique

Python est un langage à typage dynamique : il n'impose pas que le type des variables soit défini. Le type est alors déterminé à la volée au moment de l'interprétation du code. On peut donc à loisir changer le type de donnée d'une variable. Cela peut poser problème si on n'est pas prudent.

a = 'Hello world'
print(type(a))
a = 12
print(type(a))

D'autres langages, tels le C, Java ou Pascal sont à typage statique, donc nettement moins permissifs que Python.

Corriger le programme ci-dessous :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Corrigé
from random import randint


def fonc(n, liste_notes):
    somme = 0
    for elt in liste_notes :
        somme = somme + elt
    return somme/n

nombre = 10
x="Anatole"
liste_notes = []
liste_notes = [randint(0, 20) for i in range(nombre)]
print(liste_notes, "\t", fonc(nombre, liste_notes))

Effet de bord

On appelle effet de bord la modification de valeurs référencées par des variables. Cela peut poser problème dans le cas de variables mutables, notamment lors de la copie d'une variable par affectation. En effet, lors d'une affectation du type b = a, ce n'est pas la valeur de \(a\) qui est recopiée, mais son identifiant!

  • Cas des variables non mutables
a = 1961
b = a
print(a, "\t", b)
a = 1935
print(a, "\t", b)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Comme les identifiants de a et b sont identiques, ils définissent la même plage mémoire pour les valeurs de ces deux variables. Autrement dit a et b ont la même valeur.

Cependant, toute modification de la variable a via une affectation créera une autre plage mémoire avec un nouvel identifiant : la variable b ne sera donc pas modifiée !

  • Cas des variables mutables

Les variables mutables peuvent être modifiées par d'autres opérations qu'une affectation. or cela conserve l'identifiant ! Par conséquent, pour une variable de type mutable, l'identifiant ainsi que la plage mémoire allouée à la valeur de cette variable peuvent rester inchangés, même lorsque l'on change la valeur de cette variable.

L1 = [1, 9, 6, 1]
L2 = L1
print(L1, "\t", L2)
L1[2: 4] = [3, 5]
print(L1, "\t", L2)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Cette manière de procéder permet un gain de temps (copie rapide) et de mémoire(une seule plage mémoire pour deux variables). Mais elle pose un problème technique : si le contenu de la plage mémoire de la valeur de L1 est modifié, cela entraîne automatiquement la modification de la valeur de L2 puisqu'ils sont référencés par le même identifiant!

Pour le programmeur Python non averti, cet effet de bord peut provoquer une erreur de programmation difficile à élucider.

On peut néanmoins copier une liste sans provoquer d'effet de bord en utilisant une deepcopy :

from copy import deepcopy


L1 = [1, 9, 6, 1]
L2 = deepcopy(L1)
print(L1, "\t", L2)
L1[2: 4] = [3, 5]
print(L1, "\t", L2)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Un programme écrit selon le paradigme fonctionnel utilise des fonctions dites fonctionnelles (!) et se caractérise essentiellement par une chose : l'absence d'effets de bord. Le code ne dépend pas de données se trouvant à l'extérieur de la fonction courante et il ne modifie pas des données à l'extérieur de cette fonction. Il évite donc l'utilisation de variables globales.

Comparez les deux exemples ci-dessous :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Rappel

La portée d'une variable correspond aux parties du programme pour lesquelles la variable est définie. - Une variable définie à l'intérieur d'une fonction est une variable locale. Elle ne sera pas reconnue au sein d'une autre fonction ni au sein du programme principal. - Une variable définie au niveau du programme principal est une variable globale. Elle est reconnue partour dans le programme, même au sein des fonctions. On peut faire en sorte qu'une variable déclarée au sein d'une fonction soit malgré tout une variable globale en utilisant l'instruction global. Mais c'est à éviter pour cause de confusion.

En résumé

On appelle effet de bord la modification de valeurs référencées par des variables. Pour les éviter, le code intérieur d'une fonction ne doit pas modifier le code extérieur. La copie de variables immutables ne pose pas de souci. Mais la copie de variables mutables (listes, dictionnaires...) provoque un effet de bord. Il faut alors utiliser une copie "profonde".

Activité 3 - Effets de bord

On appelle effet de bord la modification de valeurs référencées par des variables. Pour les éviter, le code intérieur d’une fonction ne doit pas modifier le code extérieur.

La copie de variables immutables ne pose pas de souci ! Mais la copie de variables mutables (listes, dictionnaires..) provoque un effet de bord. Il faut alors utiliser une copie « profonde »

Ex 1:

a = 1961
b = a
print(a, "\t", b)
a = 1935
print(a, "\t", b)

Ex 2:

L1 = [1, 9, 6, 1]
L2 = L1
print(L1, "\t", L2)
L1[2: 4] = [3, 5]
print(L1, "\t", L2)

Ex 3:

from copy import deepcopy


L1 = [1, 9, 6, 1]
L2 = deepcopy(L1)
print(L1, "\t", L2)
L1[2: 4] = [3, 5]
print(L1, "\t", L2)

Ex 3:

a = 0

def increment_non_fonctionnel():
global a
a = a + 1
return a

print(increment_non_fonctionnel())
print(increment_non_fonctionnel())
et

a = 0

def increment_fonctionnel(a):
return a + 1

print(increment_fonctionnel(a))
print(increment_fonctionnel(a))

Modifiez le programme ci-dessous pour que la fonction ne provoque pas d'effets de bord :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5
.128013rAt=7Cflb)D1 {2e/vyg.:,-]B8swE9nuph5aS(o3Fk}i46P;[cdm0050!0q0d0L0T0i0C0n0Z0i0L0C0C0e010d0T0I010406050C0H0#0#0L0b0t040M0O0i0H0`0O0G050r111315170 0I04051n1g1q0r1n0 0!0T0s0/0;0?0^0J0T0u0J0i1E0J0d0}050*0j0i0q1z0=0@011D1F1H1F0d1N1P1L0d0b1o0d0J0/1a0C0I0L0G0^0p011R1B010h0,0q0G0L0#0q1L1.1:1^1T1{1P1~200}0a0n0W0b0O0I0O0C0T1d0G0n0(1,0b0b0q0Z2l1g230G1o0r1*2y1%1)1(1M0!250^1H0G1}2i1L1w1y0:1S2I0T2K0G0O2O1L0I2r1o2w2y2$101/2m2Q1_2V0b140i0}0n0m2v2*0~2)242,1T2.2:2=0p2^1:2`2w2H012 0L2;040n0P332x0 362}0^393b0n0U3f352*373l2=0K3p3h3r3j380O2/3a2=0V3w2{2+1A2~3B303c0f3G3i3J3k3L3D3c0B3P3y3R3A3C3m0F3X2|3Z3t040m0$3(3I2R3!3M0m2@1h2_3x3)3;3+0m323_341r2!1g2O2B0!1)2G3z0Z2W211o461p442(412x054c0(2#3Y3;0R0G0}0h2f0#3p0n3H3s4t044c0I0t4y4A3z4s0}0T0#2h0b0d4H3Q3}0}0(0q0I4E4G4k0~3{3:1_0R4U0q0h4R4q2-0h0}0I0;0G0Z0J0q0b3p4I3Z0|040N4|4S2-4U0T2t0T1e1~0T2r524.1T4 0k0w4y060n5l4z532~5557590+2r402$5n5e0^0O0}0e4-3|54044V4X2g4Z2(5o0^4 514!4}4T5G562l5a5c5Q5M015g3w5m5x5E1T4)040h3B5D4%5p4D1P5d5)5N0}0x5/3s0}0s3a0q0H4{4!5(5:5z0D4L1f645R5F1w5r0G5W0q5@66015A040v6j5}042k0q0#0C6p3z5O5h5$5%5m6c5*4L4,6b5Z4C5 1P625|3z6m020i0d0X6N3Z0#0T0}3.5Y5y5!0}5i4!5k6B6+6D3k5q5V5t0q5v2_6-6$040Y6w3*0}0:6i6!5^6_0z6U3;6m5C6H6#6W6Y6A5l6^5+2r0d626a5w6^4C6e6:5b6=3w4$6q1}1%0q0q6v716k77751_4 0o6|3;0C0m0}02030P0F0X0c7M7O6T7A374 6(7l5Z7b3,6?425Z4 5{79727J7L7N7P0A7S7P7H7E6%7D1T7#0p3^5L6#7*7{0^7.047?0X0g877^5f7`7,7B0}0y8b0^7}8i6_7+7Z6#85870l8a7V6x8d8o727#0P3O8u4~5`83018q7:0X0E8t80727X8F6m8h8C3;7#0F3%8S7_048n2_65378H7T0Q8L6@7)8w8#6^7#3F8X8c040S3/3s0j4;0b2T4Q8?5_508l4C7v2r7y8l5#6)7m8|042Z8 990}5P8M6k4C4=1:4^4`9h9391380}967x7z9k7W0}0k0k3G0r4n0q2y2Z9H451x472B2E2z0L1O9K0r460 9U0)0+0-04.

Débordement

Il existe deux sortes de débordement:

  • On peut constater un débordement négatif sur une liste si on appelle la méthode .pop() sans s'assurer au préalable que la liste contient au moins un élément.

  • On sera confronté à un débordement positif lorsqu'on parcourt une liste au-delà de son dernier élément.

En général, la notion de débordement est associée à la notion de pile (voir chapitre 3).

Une pile est un conteneur dans lequel il est possible d'ajouter (empiler) et de retirer (dépiler) des éléments.

Activité 4 - Débordements

Modifiez les programmes ci-dessous pour empêcher tout débordement. Programme 1 :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013rt=flb)1 2e/vyg.:,]swnuph5aS(o3Lki4P[cdm050N0l0c0B0I0f0u0j0M0f0B0u0u0d010c0I0y010406050u0x0O0O0B0b0o040C0E0f0x0)0E0w050m0:0=0@0_0.0y040519121c0m190.0N0I0n0X0Z0#0%0z0I0p0z0f1q0z0c0,050S0g0f0l1l0!0$011p1r1t1r0c1z1B1x0c0b1a0c0z0X0|0u0y0B0w0%0k011D1n010e0U0l0w0B0O0l1x1W1Y1%1F1*1B1-1/0,0a0j0K0b0E0y0E0u0I0 0w0j0Q1U0b0b0l0M27121=0w1a0m1S2k1P1R1Q1y0N1@0%1t0w1,241x1i1k0Y1E2u0I2w0w0E2A1x0y2d1a2i2k2O0/1X282C1(2H0b0?0f0,0i2h2S0-2R1?2U1F2W2Y0,0k2$1Y2(2i2t012-0B2Z040F2;2j0.2@2+0%2`2|0J2 2?2S2^350,0A381d2M122A2n0N1R2s33010M2I1:1a3j1b3h2Q132%053q0Q2N3a3o0w0,0G2#3y2=0j2)2T1m1F0E0,0d383M323P0%0+040L3f3W2D010O0I2/3$3E3X013Z0s3U3N2^3*0,2~3K2j3@3o3;3?3%1(3_04373|3D2*3/3Z0t38313.3(0w0g0,2L2F0c3-493(3Z0D4n3O4g3H3J2Q421F3Z0h4d3~3/0H0,0e0E0b414f2V0,0I4K4o1(0E0v4N11473V4L2,4i040b1Y0p0l4s2^4q4*3o443{4x4Y3Y0,0h0r4d0j4{4X4Q2,4v4-3/3R040q514u04230y561(4q4B474e4~344!4k0w4m474D4p0,4r5n4y34505s4=3:[email protected].

Programme 2 :

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013rt=flb)1 2e/vyg.:,]sw9nuph5aS(o3Lki46P;[cdm050Q0l0c0C0J0f0u0j0P0f0C0u0u0d010c0J0z010406050u0y0R0R0C0b0o040D0F0f0y0,0F0x050m0?0^0`0|0;0z04051c151f0m1c0;0Q0J0n0!0$0(0*0A0J0p0A0f1t0A0c0/050V0g0f0l1o0%0)011s1u1w1u0c1C1E1A0c0b1d0c0A0!0 0u0z0C0x0*0k011G1q010e0X0l0x0C0R0l1A1Z1#1*1I1-1E1:1=0/0a0j0M0b0F0z0F0u0J120x0j0T1X0b0b0l0P2a151^0x1d0m1V2n1S1U1T1B0Q1`0*1w0x1/271A1l1n0#1H2x0J2z0x0F2D1A0z2g1d2l2n2R0=1!2b2F1+2K0b0_0f0/0i2k2V0:2U1_2X1I2Z2#0/0k2)1#2+2l2w012:0C2$040G2@2m0;2`2.0*2}2 0K322_2V2{380/0B3b1g2P152D2q0Q1U2v36010P2L1?1d3m1e3k2T162*053t0T2Q3d3r0x0/0H2(3B2^0j2,2W1p1I0F0/0d3b3P353S0*0.040O3i3Z2G010R0J2=3)3H3!013$0s3X3Q2{3-0/313N2m3`3r3@3_3*1+3|043a3 3G2-3=434a3Y3;3+473h4a414d0/3^4f4m4i3.040L3:4c3+3$0t3b344h2Y3K2?4q453T3V444D1I3$0O4A4a4C4x1+0I0/0e0F0b4L4T2/0/0J4!3R3+0F0v4%144H4M370g0/0b1#0p0l4w4*1+3$0E4|3{4t4k2T4I3#0/0h0r4B0j5c4g4#374F513r3U040q5i3=3J040C0z0z1/0Q5n4y0/504l562|3K3M554;3?0/3(5A5G5p4(5K5f5H040t0h5b5d4r2Y4?042O2I0c5w4~5y5%4$043L5*573%5.5C045N5F5P4z4p2R5e4}5+5u5;5k3W5O5~0*0u0i0/02030G0w0N0j0s0j6a6c0N5;3$5T4R153E0l2n2O6r3l1m3n2q2t2o0C1D6u0m3m0;6E0U0W0Y04.
Activité 5 - Instruction conditionnelle non exhaustive ou contenant un mauvais choix des inégalités

Une séquence de if, elif et else peut être incomplète, complète mais construite avec un ordre incorrect des conditions, ou encore contenir un traitement conditionnel d'égalité conduisant à des erreurs.

Dans les différents programmes ci-dessous, traquez l'erreur et corrigez-là !

ensuite, vous attribuerez à chacun de ces programmes le type d'erreur adéquat parmi : - séquence incomplète - séquence complète mais construite avec un ordre incorrect des conditions - séquence contenant un traitement conditionnel d’égalité conduisant à des erreurs

Notebook

Corrigé

Comparaisons

Activité 6 - Comparaisons et calculs entre flottants

De nombreux algorithmes nécessitent de comparer des valeurs entre elles. Il est important de prévoir tous les cas de figure de manière exhaustive et sans commettre d'erreur dans l'ordre des évaluations pour les inégalités. Si l'ordre n'a pas d'importance pour les égalités simples, la notion même d'égalité doit être nuancée.

Difficile comparaison des réels

La comparaison d'entier (type int) ne pose pas de problèmes particuliers. Il n'en va pas de même avec les réels (type float).

En effet, certaines valeurs sont seulement des valeurs approchées. Exemple pour la valeur 0.3 affichée avec un très grand nombre de décimales:

print("{:.80f}".format(0.3))

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Les tests d'égalité stricte sont donc à éviter entre réels. Il faudra privilégier des comparaisons de valeurs arrondies (instruction round) ou tolérer un faible écart. Certaines valeurs réelles sont seulement des valeurs approchées.

Exemple pour la valeur 0.3 affichée avec un très grand nombre de décimales :

print("{:.80f}".format(0.3))
donne : 0.29999999999999998889776975374843459576368331909179687500000000000000000000000000

Les tests d’égalité stricte sont donc à éviter entre réels. Il faudra privilégier des comparaisons de valeurs arrondies (instruction round) ou tolérer un faible écart.

  • Afficher les deux nombres x et y avec une cinquantaine de décimales puis transformer le test d'égalité stricte en un test de comparaison d'arrondis en utilisant la fonction round (help() est votre amie...).
  • Ensuite, vous écrirez un test de comparaison tolérant un faible écart (ex : ecart_tolere = 0.01)
x = 0.1 + 0.1 + 0.1
y = 0.3

print(x == y)

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Corrigé
x = 0.1 + 0.1 + 0.1
y = 0.3

print("{:.50f}".format(x))

print("{:.50f}".format(y))

print(round(x, 10) == round(y, 10))

ecart_tolere = 0.01

print(abs(x-y) <= ecart_tolere)
Activité 7 - Comparaison physique entre variables

Toute variable peut être considérée comme un objet, défini par différentes caractéristiques telle que leur adresse en mémoire. Des copies simple peuvent alors poser problèmes pour des variables mutables.

Exécutez le programme suivant puis expliquer les résultats obtenus.

Remarque : la fonction id() renvoie l'identifiant d'une variable.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Corrigé

En affectant l1 à l2, on octroie la même adresse mémoire aux deux listes, donc le == ou le is pointe vers le même endroit.

Par contre, en créant l3 "manuellement", on le crée à une nouvelle adresse. Donc les valeurs de l1 et l3 sont les mêmes, mais les listes ne pointent pas à la même adresse.

Les exceptions

Même si un code est syntaxiquement correct, il peut générer des erreurs. Quand Python rencontre une erreur lors de l'exécution d'un code, on dit qu'il lève une exception.

Cela génère l'affichage d'un message explicite et l'arrêt du programme si ces exceptions ne sont pas gérées.

Gérer les exceptions consiste à anticiper les erreurs (hors de contrôle du concepteur) de manière à ce que le programme ne s'arrête pas. Contrairement aux assertions, les exceptions permettent ainsi de ne pas planter le programme en proposant une alternative de traitement des cas problématiques avec les instructions try/except/else/finally. Elles doivent demeurer dans le code final.

En effet, la plupart des programmes ont vocation à être utilisés par d'autres utilisateurs que leur concepteur, et ceux-ci peuvent entrer des valeurs de variables provoquant des erreurs lors de l'exécution, même si le code du concepteur est initialement correct. Le concepteur doit anticiper ces erreurs dues à l'utilisateur.

Activité 8 - Liste non exhaustive d'exceptions

Il existe un certain nombre de catégories d'exceptions clairement identifiées et repérées par un nom adéquat.

Dans les lignes ci-dessous, un certain nombre de codes erronés sont écrits. Identifier l'exception correspondante parmi celles proposées ci-dessous et associées les au code.

a) SyntaxError: invalid syntax
b) ZeroDivisionError: division by zero
c) FileNotFoundError: [Errno 2] No such file or directory: 'mon_fichier'
d) IndexError: list index out of range
e) ValueError: invalid literal for int() with base 10: 'seize'
f) TypeError: unsupported operand type(s) for +: 'int' and 'str'
g) NameError: name 'z' is not defined

Cette liste est non exhaustive...

  1. somme = 0
    L = [2, 5, '6', 9]
    for element in L:
        somme = somme + element
    
  2. def div(x, y):
        return x/y
    
    for i in range(5):
        print(div(5, i))
    
  3. x = 2
    y = x + z
    print(y)
    
  4. l = [1, 2, 3]
    for i in range(5):
        print(l[i])
    
  5. age = "seize"
    print(int(age))
    
  6. for i in range(5)
        print(i)
    
  7. open('mon_fichier')
    
Correction
  • a → 6
  • b → 2
  • c → 7
  • d → 4
  • e → 5
  • f → 1
  • g → 3
Stack trace

Au-dessus du type d'exception (ValueError, IndexError, etc...) se trouve une description de ce qui a causé l'erreur. C'est ce qu'on appelle une stack trace, et ça représente la pile d'appels qui ont amené à cette erreur. Pour comprendre votre erreur, il faut lire ce texte à l'envers, de bas en haut.

Testez et analysez le programme suivant :

def une_fonction():
    return 1/0


def une_autre_fonction():
    une_fonction()


une_autre_fonction()

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Gestion des exceptions

On utilise les instructions try/except et éventuellement else et finally.

  • Le bloc try permet de tester un bloc de code et ne l'exécute que s'il ne contient aucune erreur. Dans le cas contraire, le programme ignore la totalité du code dans ce bloc et passe au bloc suivant except
  • Le bloc except sera exécuté en cas d'erreur
  • Le bloc else permet d'exécuter une action si aucune erreur ne survient dans le bloc try
  • Le bloc finally vous permet d'exécuter du code, quel que soit le résultat des blocs try et except.

Exemple : structure complète possible

try:
    resultat = numerateur/denominateur
except NameError:
    print("La variable numerateur ou denominateur n'a pas été définie.")
except TypeError:
    print("La variable numerateur ou denomniateur possède un type incompatible avec la division")
except ZeroDivisionError:
    print("La variable denominateur est égale à 0.")
else:
    print("Le résultat est : ", resultat)
finally:
    print("Vous venez de calculer une division")

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Jeux de tests

Afin de vérifier le bon fonctionnement d'un algorithme, il faut aussi tester son programme avec un jeu de tests judicieusement choisi.

Pour cela, on peut:

  • Utiliser des assertions dans le programme principal
  • Insérer des tests dans le prototype même de la fonction. Cela présente de gros avantages : la fonction peut être transférée à un autre programme, ou données à un autre programmeur : celui-ci aura tous les renseignements nécessaires.

Voyons un exemple de test inséré dans le prototype d'une fonction :

"""
...

Exemples :
>>> indice_maximum_liste([2, 0, 8, -3, 9, 6])
4
"""
Pour que ce test tapé dans le prototype de la fonction soit reconnu, exécuté et comparé au résultat souhaité, il faudra toutefois ajouter les trois lignes de code suivantes dans votre prorgamme principal :

if __name__ == '__main__':
  import doctest


  doctest.testmod()
Activité 9 - Docstring et Doctests

Faire la docstring complète de ce module (général et pour chaque fonction), en incluant des exemples à tester avec le module Doctest. Ne pas oublier de respecter le PEP8

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Corrigé
# module sur les listes

"""
Ce module contient les différentes fonctions sur les listes suivantes:

    - dans_liste: Recherche de la présence d'un élément dans la liste
    - max_liste: Recherche de la valeur maximale (et/ou minimale) des élément d'une liste
    - moy_liste: Calcul de la moyenne des élément d'une liste
    - indice_min_liste: Recherche de l'indice de l'élément minimum 
    - card_val_liste: Recherche du nb d'occurrences d'une valeur 
    - supp_liste: Suppression de la première occurrence d'un élément 
"""


def dans_liste(x,L):
    """
    Cette fonction permet de rechercher si un élément est dans une liste ou non.

    param x : Elément recherché (float)
    param L : Liste pour la recherche (list)
    return : présence ou non (bool)

    Exemple :

    >>> dans_liste(2, [1, 2, 3])
    True
    >>> dans_liste(4, [1, 2, 3])
    False
    """
    msg = False

    for e in L:
        if e == x:
            msg = True

    return msg


def max_liste(L):
    """
    Cette fonction permet de rechercher le plus grand élément d'une liste.

    param L : Liste (list)
    return : valeur max (float)

    Exemple :

    >>> max_liste([1, 2, 3])
    3
    >>> max_liste([4, 1, 2])
    4
    """

    maxi = L[0]
    for i in range(1, len(L)):
        if L[i] > maxi:
            maxi = L[i]

    return maxi


def moy_liste(L):
    """
    Cette fonction permet de calculer la moyenne des valeurs d'une liste.

    param L : Liste dont on cherche la moyenne (list)
    return : valeur moyenne (float)

    Exemple :

    >>> moy_liste([1, 2, 3])
    2
    """ 

    Somme = 0
    for e in L:
        Somme = Somme + e

    moyenne = Somme/len(L)

    return moyenne


def indice_min_liste(L):
    """
    Cette fonction permet d'avoir l'indice de la valeur minimum d'une liste.

    param L : Liste dont on cherche l'indice du min (list)
    return : indice du min (int)

    Exemple :

    >>> indice_min_liste([1, 2, 3])
    0
    >>> indice_min_liste([4, 2, 1])
    2
    """ 

    i = 0
    indice = 0
    mini = L[0]
    for e in L:
        if L[i] < mini:
            L[i] = mini
            indice = i
        i = i + 1

    return indice


def card_val_liste(x, L):
    """
    Cette fonction permet d'avoir le nombre de fois où apparaît une occurence dans une liste.

    param x : occurence à compter (float ou str)
    param L : Liste où on compte (list)
    return : nombre d'occurence (int)

    Exemple :

    >>> card_val_liste(2, [1, 2, 3])
    1
    >>> card_val_liste(2, [2, 2, 2])
    3
    """

    nb = 0
    for e in L:
        if e == x:
            nb = nb + 1

    return nb


def supp_liste(x, L):
    """
    Cette fonction permet de supprimer la première occurence d'un élément d'une liste.

    param x : élément à supprimer
    param L : Liste de départ(list)
    return : Liste sans l'occurence de l'élément (list)

    Exemple :

    >>> supp_liste(1, [2, 1, 4, 1, 3])
    [2, 4, 1, 3]
    """
    arret = False
    for e in L:
        if e == x and arret == False:
            indice=L.index(e)
            del L[indice]
            arret = True
    return L

if __name__ == '__main__':
    import doctest


    doctest.testmod()