Critique de «effets de la séparation en classes»

Parmi les nouveaux livres de première S, Odyssée présente quelques programmes en Python. C’est très positif bien sûr, mais malheureusement certains ne sont pas très «pythoniques». En voici un trouvé à la page 60 du livret du professeur :

N=input("Nombre de donnees ? ")
L=N*[0]
for i in range(N):
    L[i]=input("Entrer une valeur ")
C=input("Nombre de classes ? ")

## Calcul de l'etendue de chaque classe
Max=max(L) ; Min=min(L) ; etendue=float(Max-Min)/C

## initialisation de la liste qui contiendra les effectifs
EFF=C*[0]

## Recherche de l'effectif de chaque classe
for k in range(C-1):
    EFF[k]=0
    for i in range (N):
    if L[i]>=(Min+k*etendue) and L[i]<(Min+(k+1)*etendue):
        EFF[k]= EFF[k]+1
    print "classe n ",k+1,Min+k*etendue,Min+(k+1)*etendue
    print "effectif : ",EFF[k]

## Recherche de l'effectif de la derniere classe
EFF[C-1]=0
for i in range (N):
    if L[i]>=(Min+(C-1)*etendue)and L[i]<=Max:
    EFF[C-1]= EFF[C-1]+1
print "classe n ",C,Min+(C-1)*etendue,Max
print "effectif : ",EFF[C-1]

Il faut tout d’abord évoquer quelques légers problèmes de forme :

  • quelques espaces manquent pour améliorer la lisibilité du code (voir le PEP 8),
  • il manque une espace devant un and et il y en a en trop derrière des range (le programme fonctionne tout de même),
  • un seul # suffit pour commenter,
  • il manque des points finaux dans les commentaires et une majuscule à celui qui commence par «initialisation…»,
  • les deux derniers print n’ont pas la même couleur (forcément ici, on ne peut pas le voir).

Passons maintenant à des choses plus sérieuses.

Il faut nommer ses variables par des mots plutôt que par des lettres isolées (c’est un héritage de langages antiques dont il faut se débarrasser). L’incohérence du choix de la casse peut aussi être déconcertante. Quelques idées pour un code plus lisible :

  • N n’est finalement rien d’autre que l’effectif total, et pourrait donc s’appeler effectif_total.
  • L est la liste des valeurs à étudier → valeurs ou serie.
  • i est une variable temporaire d’itération → rang_courant (facultatif, i est très couramment utilisé comme cela).
  • C est le nombre de classes → nbre_classes.
  • Pour Min et Max, les noms sont bien choisis (même si la majuscule n’est pas courante pour les simples variables en Python). Mais l’idée ici est que l’on pourrait utiliser directement min(serie) et max(serie). La perte en lisibilité est minime et peut même devenir un gain de lisibilité si on a plusieurs listes dont on veut connaître les extrema. Bien sûr on les fait recalculer plusieurs fois (nbre_classes + 3 dans le programme final, ce qui est peu et on peut descendre à 4 en rusant), mais le programme initial n’est pas taillé pour la course et n’a pas besoin de l’être (voir tout de même quelques idées d’optimisation au point suivant et à la fin du billet).
  • L’ajout des variables borne_sup et borne_inf est en revanche clairement un gain de lisibilité ainsi qu’une économie de calculs. borne_sup et borne_inf sont en effet calculées avant de parcourir la liste de valeurs et non plus pour chaque valeur.
  • EFF est la liste des effectifs → effectifs,
  • k est une variable temporaire d’itération → classe_courante (facultatif, « k » est proche de « classe » phonétiquement).

Pas besoin d’initialiser la liste L car l’utilisateur va tour à tour donner une valeur à chaque élément. Supprimons L = N*[0].

J’ai mis du temps à comprendre le -1 dans for k in range(C-1):. Comme tout bon élève qui se respecte, j’aurais du lire l’énoncé en entier et j’aurais vu que la dernière classe était traitée à part. Un commentaire aurait été bienvenu pour guider le lecteur : ## Recherche de l’effectif de chaque classe, sauf de la dernière, traitée à part.

EFF a besoin d’être initialisée, mais pas deux fois. Supprimons EFF[k]=0.

L’expression x >= min and x < max est encore un héritage de langages anciens. Python autorise la notation  min <= x < max (notez que peu de langages le font), et c’est très appréciable pour les matheux que nous sommes. Exemple :


>>> for x in range(10):
...     if 2 <= 2*x < 5:
...         print(x)
...
1
2

Cette syntaxe permet à la fois de rendre le code plus lisible, d’en taper moins, et de ne pas répéter d’éventuels calculs sans utiliser de variable intermédiaire. Sans elle, on aurait été obligé de taper :


>>> for x in range(10):
...     if 2 <= 2*x and 2*x < 5:  # répétition du calcul
...         print(x)
...
1
2

ou


>>> for x in range(10):
...     double = 2*x  # variable intermédiaire pour
                      # effectuer le calcul une seule fois
...     if 2 <= double and double < 5:
...         print(x)
...
1
2

Utiliser for i in range(L): pour parcourir une liste est encore un héritage dont on peut se débarrasser aujourd’hui grâce à Python. for valeur in L: est bien plus élégant et concis (on dira parfois «pythonique»). On remplacera L[i] par valeur. Ainsi, la liste ne sera plus parcourue en incrémentant un nombre entier et en allant chercher l’élément correspondant, Python nous donnera directement les éléments un à un. Comparons :

>>> liste = ["ceci", "est", "un", "test"]
>>> for i in range(len(liste)):
...     print(liste[i])
...
ceci
est
un
test
>>> liste = ["ceci", "est", "un", "test"]
>>> for mot in liste:
...     print(mot)
...
ceci
est
un
test

Il existe en Python une syntaxe pour ne pas avoir à répéter la variable dans l’expression : x = x + 1. C’est x += 1. Ce raccourci pour incrémenter une variable est présent dans quasiment tous les langages et est précieux vers la fin de notre programme, surtout que nos nouveaux noms de variables sont longs.

Voici la mise en place des différentes modifications :

effectif_total = input("Nombre de donnees ? ")
serie = []
for i in range(effectif_total):
    serie.append(input("Entrer une valeur "))
nbre_classes = input("Nombre de classes ? ")

# Calcul de l'etendue de chaque classe.
etendue = float(max(serie) - min(serie)) / nbre_classes

# Initialisation de la liste qui contiendra les effectifs.
effectifs = nbre_classes*[0]

# Recherche de l'effectif de chaque classe,
# sauf de la derniere, traitee a part.
for k in range(nbre_classes - 1):
    borne_inf = min(serie) + k * etendue
    borne_sup = borne_inf + etendue
    for valeur in serie:
        if borne_inf <= valeur < borne_sup:
            effectifs[k] += 1
    print "classe n ", k+1, borne_inf, borne_sup
    print "effectif : ", effectifs[k]

# Recherche de l'effectif de la derniere classe.
effectifs[nbre_classes-1] = 0
borne_inf = min(serie) + (nbre_classes - 1) * etendue
borne_sup = max(serie)
for valeur in serie:
    if borne_inf <= valeur <= borne_sup:
        effectifs[nbre_classes-1] += 1
print "classe n ", nbre_classes, borne_inf, borne_sup
print "effectif : ", effectifs[nbre_classes-1]

Notez bien que nous n’avons traité que les problèmes de lisibilité et de «pythonicité» (utilisation des améliorations que propose le langage par rapport à d’autres langages moins modernes) de ce programme, dont nous n’avons quasiment pas modifié la structure. D’autres commentaires sur l’optimisation du temps d’exécution ou sur la mise en place du programme vis à vis de son utilisation peuvent être faits.

Par exemple, l’ajout de l’instruction break après l’incrémentation de l’effectif aurait permis d’optimiser le déroulement du programme. Cette instruction casse la boucle (for, while…) en cours. En effet, nous n’avons pas besoin de vérifier si une valeur tombe aussi dans une classe suivante à partir du moment où on a trouvé la bonne classe.

D’autre part, la mise en place d’une fonction pour traiter une liste de valeurs dans l’interpréteur peut être préférable. En effet, plutôt que d’écrire un programme qui demande les valeurs (et les redemandera lors d’une autre exécution), on peut faire confiance à l’utilisateur qui saura taper lui-même sa liste : serie = [12.1, 5.7, 10]. Ensuite on peut créer une fonction (appellons-la effet_regroupement) qui prend en paramètre ce genre de liste (appellée L ou serie dans notre programme) mais aussi le nombre de classes (C ou nbre_classes) . Il tapera donc quelque chose du genre : effet_regroupement(serie, 6), puis effet_regroupement(serie, 13) sans avoir à ressaisir la série.

Nous proposerons une autre façon de construire et d’utiliser ce genre d’algorithme dans un autre billet.

About M.Gragnic

Enseignant au lycée Carcouët à Nantes.
This entry was posted in Chroniques de livres scolaires and tagged , , , . Bookmark the permalink.

2 Responses to Critique de «effets de la séparation en classes»

  1. Denis Pinsard says:

    Votre programme ne fonctionne pas.
    Dans la première boucle la variable serie doit être initialisée.