Accueil > Perl-Ruby-Python > Quand les Pythons Attaquent

Quand les Pythons Attaquent

Erreurs fréquentes des Programmeurs Python

par Mark Lutz, coauteur de Learning Python, 2nd Edition, le 05/02/2004

traduit par Thierry, le 14/04/2004

Dans cet article, je vous rapporterai quelques unes des erreurs les plus courantes commises à la fois par Ies programmeurs Python débutants et par les vétérants, de manière à vous permettre de les éviter dans votre travail.

Tout d’abord, je me dois de vous expliquer que ces erreurs trouvent leur source dans une expérience dite de première main. Je gagne ma vie en enseignant Python. Dutant les sept dernières années, j’ai eu le privilège d’enseigner plus de 100 classes Python à plus de 1000 étudiants—et je les ai tous vus faire les mêmes erreurs. En fait, ce sont des choses que j’ai observées chez des programmeurs Python débutants des centaines de fois. En fait, certaines erreurs sont si courantes qu’elles sont inévitables lorsque vous démarrez.

“Qu’est ce à dire ?” dites vous. “Vous faites aussi plein d’erreurs en Python ?”. Et bien, oui. Python a beau être un des langages de programmation les plus simples et les plus flexibles, il reste toujours un langage de programmation. Il a toujours sa syntaxe, ses types de données et le coin noir occasionnel habité de sorciers nommés Tim.

La bonne nouvelle est qu’une fois que vous appris Python, plusieurs pièges sont naturellement évités, grace à la bonne conception du langage. Python comporte un ensemble minime d’interactions entre ses composants, ce qui aide à réduire le nombre de bogues. Il a aussi une syntaxe simple, ce qui veut dire qu’il y a moins de place pour les erreurs de premier rang. Et lorsque vous commetez une erreur, les mécanismes de détection et de rapport d’erreurs vous aident à réparer la faute rapidement.

Mais programmer en Python n’est toujours pas une tâche automatique, et un homme averti en vaut deux. Donc sans plus attendre, passons aux choses sérieuses. Les trois prochaines sections regroupent les erreurs en trois catégories : pragmatiques, syntaxiques et programmatique au sens large. Si vous souhaitez en lire d’avantage sur les erreurs fréquentes et sur la manière de les éviter, tout ceci et plus encore est expliqué plus en détail dans le nouveau livre sorti chez O’Reilly, Learning Python, 2nd Edition.

Erreurs Pragmatiques

Commençons par le début ; ces choses que les personnes qui en sont à apprendre comment programmer tendent à faire même avant de plonger dans la syntaxe. Si vous avez déjà fait un peu de programmation, la pluspart d’entre elles vous sembleront très simple ; si vous n’avez jamais tenté d’enseigner la programmation, elles ne le seront pas.

Tapez du Code Python en mode interactif

Vous ne pouvez taper que du code Python, et pas de commendes systèmes, lorsque vous êtes en mode interactif, en réponse au prompt >>>. Il n’est pas si rare de voir des gens taper emacs, ls, ou edit à ce prompt, mais ces commandes ne sont pas du code Python. Il existe des façons de lancer des commandes système à partir du code Python (par exemple, os.system et os.popen), mais ce n’est pas aussi direct que de taper simplement la commande elle-même. Si vous voulez lancer un fichier Python à partir du prompt interactif, utilisez import file, pas la commande système python file.py.

Les Instructions Print ne sont requises que dans les Fichiers

Compte tenu du fait que l’interpréteur interactif affiche le résultat des expressions, il n’est pas nécessaire de taper des instructions d’affichage en mode interactif. Cela représente une fonction intéressante mais rappelez vous que dans un fichier de code vous devrez utiliser des instructions “print” pour voir le résultat.

Attention aux Extensions Automatiques sous Windows

Si vous utilisez le programme Notepad sur Windows, assurez vous de sélectionner le type All Files au moment de sauvegarder votre fichier et donnez lui explicitement l’extension .py. Sinon, Notepad enregistre votre fichier avec une extension .txt, le rendant difficile à lancer dans certaines phases d’exécution. Pire, Word et WordPad ajoutent des caractères de formattage par défaut qui ne font pas partie de la syntaxe de Python. D’une façon générale, sélectionnez toujours All Files et sauvegardez au format texte simple sous Windows, ou utilisez des éditeurs de texte plus adpatés à la programmation tels que IDLE. Sous IDLE, n’oubliez pas de taper manuellement l’extension .py au moment de l’enregistrement.

Les Pièges du Clic sur les Fichiers Programme sous Windows

Sous Windows, vous pouvez lancer un programme Python en cliquant dessus, mais cela peut être source d’erreurs. D’abord, la fenêtre de sortie du programme disparaît dès que le programme se termine ; pour la maintenir, essayez d’ajouter un appel à raw_input() en fin de programme. Gardez aussi en mémoire que la fenêtre de sortie disparaît dès qu’une erreur survient ; pour voir les messages d’erreur, lancez votre programme de manières différentes—à partir d’une ligne de commande système, par des imports interactifs, avec les options de menu d’IDLE, et ainsi de suite.

Les Imports ne marchent que la Première Fois

Vous pouvez lancer un fichier en l’important à partir du prompt interactif, mais cela ne marche qu’une fois par session ; les imports suivants ne font que retourner le module déjà importé. Pour forcer Python à recharger et relancer le code d’un fichier, appelez la fonction reload(module) à la place. Et tant que vous y êtes, n’oubliez pas d’utiliser des parenthèses avec reload, mais pas avec import.

Les Lignes Blanches ont de l’Importance à partir du Prompt Interactif (seulement)

Les lignes blanches et les lignes de commentaire sont toujours ignorées dans les fichiers mais une ligne blanche termine une instruction composée lorsque vous tapez du code à partir du prompt interactif. En d’autres mots, une ligne blanche indique au prompt interactif que vous avez terminé une instruction composée ; ne tapez pas la touche Entrée tant que vous n’avez pas fini. A l’inverse, vous devrez vraiment taper une ligne blanche pour terminer une instruction composée à partir du prompt interactif, avant de commencer une nouvelle instruction—le prompt interactif n’exécute qu’une instruction à la fois.

Erreurs Syntaxiques

Une fois que vous commencez à taper sérieusement du code Python, cette catégorie d’erreurs commence à devenir plus dangereuse—ce sont des erreurs basiques de coding qui affectent les fonctions du langage et qui piègent souvent le programmeur non averti.

N’oubliez pas les Deux Points

C’est l’erreur la plus fréquente chez les programmeurs débutants : n’oubliez pas de taper un : à la fin des en-têtes d’instructions composées (la première ligne d’un if, while, for, etc.). Vous l’oublierez sûrement au début de toute façon, mais cela deviendra vite une habitude inconsciente. En général, 75% des étudiants en classe ont été grillés par ça en fin de journée.

Initialisez Vos Variables

En Python, vous ne pouvez pas utiliser un nom dans une expression tant qu’une valeur ne lui a pas été attribuée. Cela a un but : aider à éviter les erreurs communes de typo et à se poser la question ambiguë de savoir ce que serait une valeur par défaut (0, None, "", [], ?). Rappelez vous d’initialiser les compteurs à 0, les accumulateurs de type liste à [], et ainsi de suite.

Commencez en Colonne 1

Commencez toujours votre code de premier niveau à gauche en colonne 1. Cela s’applique aussi au code non indenté tapé dans les fichiers modules ainsi qu’au code non indenté tapé à partir du code interactif. Python utilise l’indentation pour délimiter les blocs de code imbriqués, ainsi un espace placé à gauche de votre code signifie que vous êtes dans un bloc imbriqué. Les espaces sont en général ignorés ailleurs à part pour l’indentation.

Indentez de Manière Logique

Evitez de mélanger les tabulations et les espaces dans l’indentation d’un bloc donné, à moins que vous ne sachiez ce que tous les systèmes qui toucheront votre code feront avec les tabulations. Sinon, ce que vous voyez dans votre éditeur pourra ne pas correspondre à ce que Python verra lorsqu’il transformera les tabulations en plusieurs espaces. Il est préférable de n’utiliser que des tabulations ou que des espaces pour chaque bloc ; le nombre étant de votre ressort.

Utilisez Toujours des Parentheses pour Appeler une Fonction

Vous devez ajouter des parenthèses après le nom d’une fonction afin de l’appeler, qu’elle prenne des arguments ou pas. Cela veut dire, utilisez function(), pas function. Les fonctions Python sont simplement des objets utilisés pour une opération spéciale, un appel, que vous déclenchez avec les parenthèses. Comme pour tout objet, elles peuvent être assignées à des variables et utilisées indirectement : x = function; x().

Lors des cours Python, cette erreur semble survenir plus souvent avec les fichiers. Il est fréquent de voir les débutants taper file.close pour fermer un fichier, plutôt que file.close(); parce qu’il est correct de référencer une fonction sans l’appeler, la première version sans parenthèses se passent correctement en silence, mais elle ne ferme pas le fichier !

N’Utilisez pas les Extensions ou les Chemins d’Accès dans les Imports

Utilisez les chemins d’accès et les extensions de fichiers dans les lignes de commande système (par exemple, python dir/mod.py), mais pas dans les instructions d’import. Cela veut dire, tapez import mod, pas import mod.py ni import dir/mod.py. En pratique, c’est probablement la seconde erreur la plus fréquente chez les débutants. Parce que les modules peuvent avoir d’autres suffixes (.pyc par exemple), coder en dur le suffixe n’est pas seulement une erreur de syntaxe, cela n’a pas de sens.

La syntaxe des chemins d’accès spécifique à chaque plate-forme est tirée des réglages de votre chemin de recherche des modules et pas de l’instruction import elle-même. Vous pouvez utiliser des points dans les noms de fichier pour faire référence à des sous-répertoires de package (par exemple : import dir1.dir2.mod), mais le répertoire le plus à gauche doit toujours être trouvé grace au chemin de recherche des modules et aucune autre syntaxe de chemin d’accès ne doit apparaître dans les imports. L’instruction incorrecte import mod.py est supposée être un import de package par Python—il importe le module mod, puis essaie de trouver un module appelé py dans un répertoire appelé mod, et finit en général par balancer un message d’erreur potentiellement confus.

Ne Codez pas du C en Python

Quelques rappels pour les programeurs C/C++ qui débutent avec Python :

  • Vous n’avez pas à taper de parenthèses dans les en-têtes de if et de while (par ex, if (X==1):). Vous le pouvez, si vous le souhaitez, puisque toute expression peut être enfermée par des parenthèses, mais elles sont complètement superflues dans ce contexte.
  • Ne terminez pas toutes vos instructions par un point-virgule. Techniquement, c’est correct en Python, mais c’est totalement inutile à moins que vous ne placiez plusieurs instructions sur la même ligne (ex : x=1; y=2; z=3).
  • N’imbriquez pas d’instruction d’assignation dans les tests de boucle while (ex : while ((x=next() != NULL)). Avec Python, une instrcution ne peut apparaître là où une expression est attendue, et l’assignation n’est pas une expression.

Erreurs de Programmation

Enfin, voici quelques-uns des problèmes que vous pourrez rencontrer lorsque vous commencerez à travailler avec des fonctions plus importantes du langage Python—types de données, fonctions, modules, classes et similaires. L’espace étant restreint, cette section a été abrégée, surtout par respect des concepts avancés de programmation ; pour le reste de l’histoire, reportez-vous aux sections astuces et “je t’ai bien eu” de Learning Python, 2nd Edition.

Les Appels à File-Open n’utilisent pas le Chemin de Recherche des Modules

Lorsque vous utilisez l’appel open() en Python pour accéder à un fichier externe, Python n’utilise pas le chemin de recherche des modules pour localiser le fichier cible. Il utilise un chemin absolu que vous lui communiquez ou suppose que le nom du fichier est relatif au répertoire courant. Le chemin de recherche des modules n’est consulté que pour les imports de module.

Les Méthodes sont Spécifiques à un Type de Donnée

Vous ne pouvez utiliser de méthodes liste sur des chaînes et vice versa. En général, les appels de méthodes sont spécifiques à un type de donnée mais les fonctions intégrées peuvent marcher sur plusieurs types. Par exemple, la méthode reverse ne marche que sur des listes mais la fonction len marche sur tout objet disposant d’une longueur.

Les Types de Données Non-modifiables ne peuvent être changés sur Place

Souvenez-vous que vous ne pouvez pas changer un object immutable (ex : n-tuplets, chaînes) sur place :

T = (1, 2, 3)
T[2] = 4          # Erreur

Construisez un nouvel objet avec découpage, concaténation et autres, et assignez le à la variable originale si besoin est. Du fait que Python récupère la mémoire inutilisée, le gaspillage est moins évident qu’il n’y paraît :

T = T[:2] + (4,)  # OK: T devient (1, 2, 4)

Utilisez des Boucles for Simples au lieu de while ou range

Lorsque vous avez besoin de parcourir tous les éléments d’un object séquencé de la gauche vers la droite, une simple boucle for (ex :for x in seq:) est plus simple à coder, et en général plus rapide à exécuter, qu’une boucle while- ou range basée sur un compteur. Ne succombez pas à la tentation d’utiliser range dans un for tant que vous n’en êtes pas vraiment forcé ; laissez Python gérer l’index pour vous. Ces trois boucles fonctionnent mais la première est habituellement meilleure ; avec Python, le plus simple est le meilleur.

S = "lumberjack"
for c in S: print c                   # plus simple
for i in range(len(S)): print S[i]    # trop
i = 0                                 # trop
while i < len(S): print S[i]; i += 1

N’espérez pas de Résultats de Fonctions qui altèrent les Objets

Les opérations de changements sur place comme les méthodes list.append( ) et list.sort( ) modifient un objet mais ne retourne pas cet objet (elles renvoient None) ; elles n’assignent pas le résultat. Il n’est pas si rare de voir des débutants taper :

mylist = mylist.append(X)

pour essayer de récupérer le résultat d’un append ; à la place, cela assigne None à mylist, au lieu de la liste modifiée. Un exemple encore plus trompeur de cette erreur survient lorsque l’on essaie de parcourir un dictionnaire en se basant sur une clé triée :

D = {...}
for k in D.keys().sort(): print D[k]

Cela marche presque—la méthode keys construit une liste keys, et la méthode sort l’ordonne—mais puisque la méthode sort retourne None, la boucle échoue car elle parcourt None (une nonséquence). Pour coder cela correctement, couper les appels à la méthode en instructions :

Ks = D.keys()
Ks.sort()
for k in Ks: print D[k]

Les Conversions ne marchent que sur les Types Numériques

En Python, une expression comme 123 + 3.145 fonctionne—elle convertit automatiquement l’entier en virgule flottante et utilise la mathématique appliquée aux virgules flottantes. D’un autre côté, ce qui suit échoue :

S = "42"
I = 1
X = S + I        # Une erreur de type

Cela fait aussi partie du sujet, parce que c’est ambigu : est-ce que les chaînes devraient être converties en nombres (pour l’addition) ou les nombres en chaînes (pour concaténation) ? En Python, nous disons que ce qui est explicite est meilleur que ce qui est implicite (autrement dit : EIBTI, Explicit Is Better Than Implicit), vous devez donc convertir menuellement :

X = int(S) + I   # Addition effectuée : 43
X = S + str(I)   # Concaténation effectuée : "421" 

Les Structures de Données Cycliques peuvent générer des Boucles

Bien qu’assez rare en pratique, si une collection d’objets contient une référence à elle-même, elle est appelée objet cyclique. Python affiche un [...] quand il détecte un cycle dans l’objet au lieu de rester scotché dans une boucle infinie :

>>> L = ['grail']  # Ajoute une référence à L
>>> L.append(L)    # Génére un cycle dans l'object
>>> L
['grail', [...]]

Outre le fait que les trois points représentent un cycle dans l’objet, il est important de connaître ce cas car les structures cycliques peuvent faire en sorte que votre code tombe dans une boucle inattendue si vous ne l’anticipez pas. Si besoin est, maintenez une liste ou un dictionnaire des éléments déjà visités et testez la (ou le) pour savoir si vous avez mis le doigt sur un cycle.

Les Assignations créent des Références, pas des Copies

Ceci est un concept fondamental de Python qui peut causer des problèmes lorsque son comportement n’est pas prévu. Dans l’exemple suivant, l’objet liste assigné au nom L est référencé à la fois par L et à l’intérieur de la liste assignée au nom M. Le fait de changer L sur place change ce que M référence, aussi, parce qu’il y a deux références au même objet :

>>> L = [1, 2, 3]        # Un objet liste partagé
>>> M = ['X', L, 'Y']    # Incorpore une référence à L
>>> M
['X', [1, 2, 3], 'Y']

>>> L[1] = 0             # Change aussi H
>>> M
['X', [1, 0, 3], 'Y']

Cet effet ne devient important que dans les plus grands programmes et les références partagées sont normalement exactement ce que vous souhaitez. Si elles ne le sont pas, vous pouvez éviter de partager des objets en les copiant explicitement ; pour les listes, vous pouvez faire une copie de premier niveau en utilisant une rangée sans limite :

>>> L = [1, 2, 3]
>>> M = ['X', L[:], 'Y']   # Incorpore une copie de L

>>> L[1] = 0               # Ne change que L, pas M
>>> L
[1, 0, 3]
>>> M
['X', [1, 2, 3], 'Y']

Les limites de rangée vont par défaut de 0 jusqu’à la longueur de la séquence qui a été coupée. Si le début et la fin sont omis, la rangée porte sur tous les éléments de la séquence et une copie de premier niveau est effectuée (un nouvel objet non partagé). Pour les dictionaires, utilisez la méthode dict.copy().

Les Noms Locaux sont détectés de Manière Statique

Python classifie les noms assignés dans une fonction comme étant locaux par défaut ; ils résident dans le périmètre de la fonction et n’existent qu’au moment de l’exécution de la fonction. Techniquement parlant, Python détecte les locaux de manière statique, lorsqu’il compile le code def, plutôt que de notifier les assignations au fur et à mesure qu’elles surviennent lors de l’exécution. Cela peut aussi conduire à des confusions si ce n’est pas compris. Par exemple, observez ce qui arrive si vous ajoutez une assignation à une variable après une référence :

>>> X = 99
>>> def func():
...     print X      # N'existe pas
...     X = 88       # Rend X local dans le def entier
...
>>> func( )          # Erreur!

Vous obtenez une erreur de nom non défini mais la raison est subtile. Au moment de compiler, Python voit l’assignation à X et décides que X sera un nom local partout dans la fonction. Mais plus tard, lorsque la fonction est réellement exécutée, l’assignation n’a pas encore eu lieu au moment du print, donc Python fait ressortir une erreur de nom indéfini.

En réalité, l’exemple précédent est ambigu : vouliez vous afficher le X global puis créer un X local, ou est ce une authentique erreur de programmation ? Si vous souhaitez réellement afficher le X global, vous devez le déclarer dans une instruction de type global, ou le référencer dans le nom du module.

Les Valeurs par Défaut et les Objets Modifiables

Les valeurs par défaut des arguments sont évaluées et enregistrées une fois, lorsque l’instruction def est exécutée, et pas à chaque fois que la fonction est appelée. C’est en général ce que vous souhaitez, mais compte tenu que les valeurs par défaut gardent en mémoire le même objet entre chaque appel, vous devez être prudent au moment de changer les ces valeurs. Par exemple, la fonction suivante utilise une liste vide comme valeur par défaut puis la change sur place chaque fois que la fonction est appelée :

>>> def saver(x=[]):   # Sauvegarde une liste d'objets
...     x.append(1)    # et la modifie à chaque appel
...     print x
...
>>> saver([2])         # Defaut non utilisé
[2, 1]
>>> saver()            # Defaut utilisé
[1]
>>> saver()            # Grandit à chaque appel !
[1, 1]
>>> saver()
[1, 1, 1]

Certains voient ce comportement comme une fonctionnalité—parce que les arguments par défaut modifiables maintiennent leur état entre les appels de fonction, ils peuvent avoir certains des rôles tenus par les variables statiques locales de fonctions du langage C. Cependant, cela peut sembler bizarre la première fois que vous êtes confonté à ce comportement, et il y a des façons plus simples de maintenir un état entre différents appels en Python (avec les classes par exemple).

Afin d’éviter ce comportement, faites une copie de vos valeurs par défaut au début de la fonction en vous servant de rangée ou de méthodes, ou déplacer l’expression appliquant les valeurs par défaut dans le corps de la fonction ; tant que la valeur réside dans le code exécuté chaque fois que la fonction est appelée, vous obtiendrez chaque fois un nouvel objet :

>>> def saver(x=None):
...     if x is None: x = []   # Aucun argument passé ?
...     x.append(1)            # Change la nouvelle liste
...     print x
...
>>> saver([2])                 # Defaut non utilisé
[2, 1]
>>> saver()                    # Ne grandit plus maintenant
[1]
>>> saver()
[1]

Autres Pièges Fréquents

Voici un rapide survol d’autres erreurs que nous ne pouvons couvrir faute
de place :

  • L’ordre des instructions importe au plus haut niveau d’un fichier : parce que l’exécution ou l’importation d’un fichier exécute ses instructions, assurez-vous de ne pas placer d’appels aux fonctions ou classes avant la définition de la fonction ou de la classe.
  • reload n’impacte pas les noms copiés avec from: reload fonctionne mieux avec l’instruction import. Si vous utilisez des instructions from, rappelez vous de relancer le from après le reload, sinon vous aurez toujours les anciens noms.
  • L’ordre de mélange importe dans les héritages multiples : parce que les superclasses sont recherchées de gauche à droite, selon l’ordre dans la ligne d’en-tête de la classe, la classe la plus à gauche l’emporte si le même nom apparaît dans plusieurs superclasses.
  • Des clauses except vides dans des instructions try peuvent attraper plus que ce que vous espériez. Une clause except dans un try qui ne nomme aucune exception attrape toutes les exceptions—même des choses telles que des erreurs authentiques de programmation, ainsi que l’appel sys.exit().
  • Les lapins peuvent être plus dangereux qu’ils ne paraissent (NdT : ???).

Textes originaux en anglais sur O’Reilly : When Pythons Attack par Mark Lutz

Thierry Perl-Ruby-Python ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
S'abonner aux commentaires de cet article