CodeGym /Cours /Python SELF FR /Erreurs standard

Erreurs standard

Python SELF FR
Niveau 20 , Leçon 1
Disponible

7.1 Mauvaise utilisation des expressions comme valeurs par défaut pour les arguments de fonction

T'as déjà appris plein de choses, alors voyons les erreurs les plus communes des débutants - mieux vaut apprendre des erreurs des autres que des tiennes, n'est-ce pas ?

Python te permet de dire qu'une fonction peut avoir des arguments optionnels en fixant une valeur par défaut. C'est évidemment super pratique, mais ça peut entraîner des trucs pas sympas si le type de cette valeur est mutable. Par exemple, regarde cette définition de fonction :


def foo(bar=[]):  # bar est un argument optionnel 
# et par défaut il est égal à une liste vide.
    bar.append("baz")  # cette ligne peut causer des soucis...
    return bar

L'erreur courante ici, c'est de penser que la valeur de l'argument optionnel sera fixée à la valeur par défaut chaque fois que la fonction est appelée sans valeur pour cet argument.

Dans le code ci-dessus, par exemple, on pourrait supposer qu'en appelant la fonction foo() (sans indiquer de valeur pour l'argument bar), elle retournera toujours ["baz"], puisqu'on suppose que chaque fois que foo() est appelée (sans spécifier l'argument bar), bar est fixé à [] (soit une nouvelle liste vide).

Mais voyons ce qui se passe vraiment :


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

Pourquoi la fonction continue-t-elle d'ajouter la valeur baz à la liste existante chaque fois que foo() est appelée, au lieu de créer une nouvelle liste à chaque fois ?

La réponse à cette question réside dans une compréhension plus profonde de ce qui se passe "sous le capot" en Python. En fait, la valeur par défaut pour la fonction est initialisée une seule fois, au moment où la fonction est définie.

Donc, l'argument bar est initialisé par défaut (c'est-à-dire une liste vide) seulement quand foo() est défini pour la première fois, mais les appels suivants à foo() (sans spécifier l'argument bar) continueront d'utiliser la même liste qui a été créée pour l'argument bar lors de la première définition de fonction.

Pour info, une solution courante pour cette erreur est la définition suivante :


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 Mauvaise utilisation des variables de classe

Regardons cet exemple :


class A(object):
    x = 1
       
class B(A):
    pass
       
class C(A):
    pass
       
print(A.x, B.x, C.x) 
# 1 1 1

Tout semble en ordre.

Maintenant, attribuons une valeur au champ x de la classe B :


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

Tout comme prévu.

Et maintenant, on change la variable de classe A :


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

Étrange, on a seulement changé A.x. Pourquoi C.x a-t-il aussi changé ?

En Python, les variables de classe sont traitées comme des dictionnaires et suivent ce qu'on appelle souvent l'Ordre de Résolution des Méthodes (MRO). Donc, dans le code ci-dessus, puisque l'attribut x n'est pas trouvé dans la classe C, il sera cherché dans ses classes de base (seulement A dans l'exemple ci-dessus, bien que Python supporte l'héritage multiple).

En d'autres termes, C n'a pas sa propre propriété x, indépendante de A. Donc, les références à C.x sont en fait des références à A.x. Cela peut causer des problèmes si on ne prend pas bien en compte ces cas. En apprenant Python, fais particulièrement attention aux attributs de classe et à leur gestion.

7.3 Mauvaise spécification des paramètres pour le bloc d'exception

Imaginons que tu as le bout de code suivant :


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # Pour attraper les deux exceptions, non ?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

Le problème ici, c'est que l'expression except n'accepte pas une liste d'exceptions spécifiées de cette manière. En conséquence, dans le code ci-dessus, l'exception IndexError n'est pas capturée par l'expression except; au lieu de cela, l'exception se termine par un paramètre nommé IndexError.

Important ! Tu peux rencontrer ce type de code dans des exemples sur internet, car il était utilisé dans Python 2.x.

La manière correcte de capturer plusieurs exceptions en utilisant l'expression except est d'indiquer le premier paramètre sous forme de tuple, contenant toutes les exceptions à capturer. De plus, pour une meilleure lisibilité, tu peux utiliser le mot-clé as, par exemple :


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION