Quand vous débuggez, rappelez-vous que pdb est votre ami, et qu’il est souvent bon de supprimer tous les fichiers .pyc pour éviter la confusion. Mais parfois l’erreur semble n’avoir aucun sens. Bien que Python soit un langage dont l’une des grandes qualités soit la cohérence, voici une liste d’erreurs et leurs solutions qui ont tendance à énerver (les erreurs hein, pas les solutions).

NameError: name 'x' is not defined

Python plante en annonçant que la variable n’est pas définie. Vous allez à la ligne donnée, et elle est là. Vous vérifiez qu’il n’y a pas de faute de frappe (genre un zéro mélangé avec la lettre O), ni une majuscule ou une minuscule échangée quelque part (Python est sensible à la casse).

Et rien.

Tout est niquel.

Alors pourquoi ça plante bordel de merde ?

Et bien ce message qui n’aide absolument pas peut venir du fait que les closures sont en lecture seule en Python. En résumé, vous avez essayé de faire un truc comme ça:

chose = 'truc' def fonction ( ) : chose = 'machin' # ou chose += machin ou une variante chose = 'truc' def fonction(): chose = 'machin' # ou chose += machin ou une variante

La solution est simple: ne modifiez pas chose . Si vous avez besoin de modifier son contenu, utilisez une variable intermédiaire:

chose = 'truc' def fonction ( ) : bidule = chose bidule + = 'machin' # je sais c'est bidon, c'est pour l'exemple chose = 'truc' def fonction(): bidule = chose bidule += 'machin' # je sais c'est bidon, c'est pour l'exemple

En Python 3.0, vous pouvez utiliser le mot clé nonlocal pour y palier: vous modifierez alors la variable du scope du dessus.

chose = 'truc' def fonction ( ) : nonlocal chose chose + = 'machin' # je sais c'est bidon, c'est pour l'exemple chose = 'truc' def fonction(): nonlocal chose chose += 'machin' # je sais c'est bidon, c'est pour l'exemple

Évitez d’utiliser global , qui a un fort potentiel d’effet de bord.

ImportError: cannot import name bidule et ImportError: No module named truc

Une fois que vous avez vérifié qu’un module existe bien avec ce nom (regardez de près, parfois c’est subtile), voici 3 possibilités:

Pas de fichier __init__.py

Un dossier n’est pas un module importable si il ne contient pas de fichier __init__.py . Vérifiez qu’il y en a un, et dans le cas contraire, créez en un vide.

Erreur de Python Path

Quand vous faites import bidule , bidule ne peut être importé que si le dossier qui le contient est dans le Python Path. Le Python Path est une variable qui contient une liste de dossiers dans lesquels chercher les modules à importer.

Le dossier courrant, le dossier contenant la bibliothèque standard de Python et le dossier où sont installés les bibliotèques Python de votre système d’exploitation sont automatiquement présents dans le Python Path.

Première chose: assurez-vous d’être à la racine du projet que vous lancez (erreur typique quand on utilise la commande ./manage.py avec Django par exemple).

Deuxième chose: si vous utilisez une bibliothèque qui n’est pas dans le Python Path (ça arrive assez souvent avec les tests unitaires: on éxécute les tests depuis le dossier de test, et le projet est dans un dossier à côté, donc pas dans le Python Path), vous pouvez ajouter manuellement un chemin dans le Python Path.

Pour se faire, avant l’import qui va foirer:

import sys sys . path . append ( '/chemin/vers/le/dossier/parent/du/module/a/importer' ) import sys sys.path.append('/chemin/vers/le/dossier/parent/du/module/a/importer')

On peut tout à fait spécifier un dossier relativement au dossier courant. Il n’est pas rare d’ajouter le dossier parent du dossier courrant au Python Path:

import sys import os DOSSIER_COURRANT = os . path . dirname ( os . path . abspath ( __file__ ) ) DOSSIER_PARENT = os . path . dirname ( DOSSIER_COURRANT ) sys . path . append ( DOSSIER_PARENT ) import sys import os DOSSIER_COURRANT = os.path.dirname(os.path.abspath(__file__)) DOSSIER_PARENT = os.path.dirname(DOSSIER_COURRANT) sys.path.append(DOSSIER_PARENT)

Par exemple, souvent dans le dossier d’un projet Django je fais un sous-dossier ‘apps’, puis je rajoute ceci au fichier settings.py :

import sys import os PROJECT_DIR = os . path . dirname ( os . path . abspath ( __file__ ) ) sys . path . append ( os . path . join ( PROJECT_DIR , 'apps' ) ) import sys import os PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(os.path.join(PROJECT_DIR, 'apps'))

Il y a deux avantages à cela:

Mes applications sont regroupées dans un dossier et pas en vrac à la racine du projet, mais je peux quand même les importer en faisant import nom et pas import apps.nom .

et pas . J’ai maintenant une variable PROJECT_DIR que je peux utiliser partout, notamment pour définir où sont certains dossiers comme le dossiers des fichiers statiques:

STATIC = os . path . join ( PROJECT_DIR , 'static' ) STATIC = os.path.join(PROJECT_DIR, 'static')

Imports circulaires

Si vous importez poisson.rouge dans force.py , et force.bleu dans poisson.py , vous aurez aussi ce message d’erreur (qui n’aide pas beaucoup, on est d’accord).

Il n’y a pas vraiment de façon élégante de s’en sortir, c’est une des plus grosses couillasses en Python.

Solution 1: vous refactorez votre code pour avoir bleu et rouge dans un fichier couleur.py , lequel est importé dans poisson.py et force.py . C’est propre, mais parfois ça n’a aucun sens, et parfois ce n’est juste pas possible.

Solution 2: vous mettez l’import dans une fonctions ou une méthode dans un des deux modules (n’importe lequel):

def make_bouillabaisse ( ) : from poisson import rouge def make_bouillabaisse(): from poisson import rouge

C’est moche, mais c’est facile. Et je le répète, je n’ai jamais vu quelqu’un en 10 ans de Python proposer une solution élégante à ce problème. C’est un What The Fuck d’or.

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)

Arf. L’erreur à la con. Parce que généralement elle vient du fait que l’on ne comprend pas vraiment ce qu’on fait. Or difficile de résoudre un problème quand on ne comprend pas de quoi il est question. Ne vous sentez pas mal, on s’est tous retrouvé comme un demeuré devant un problème d’encodage.

A noter que ce n’est pas une erreur spécifique à Python, mais si vous venez d’un langage comme PHP qui passe silencieusement ce genre d’erreur et affiche en prod des texts illisibles, voire une grosse erreur à l’écran peut surprendre.

Voici des causes très fréquentes:

Encodage du fichier.py

Comme il peut y avoir 1 million de possibilités, forcez vous à:

– TOUJOURS avoir votre éditeur de texte réglé pour utiliser UTF-8. Surtout sur Windows. Si votre chef vous l’interdit parce que “ça pose des problèmes d’encodage” (sic), quittez votre job (meilleur choix) ou faites vous former pour comprendre comment marchent les encodages et travailler dans cet environnement hostile.

– TOUJOURS avoir votre encodage (UTF-8 j’ai dis !) déclaré en haut du fichier.py : # -*- coding: utf-8 -*-

Vérifiez que les textes en entrée sont dans l’encodage prévu

Le contenu des bases de données ne sont parfois pas dans l’encodage déclaré de la table ou de la base. Le contenu d’une page HTML n’est parfois pas encodé dans l’encodage déclaré dans le HEAD. Le contenu d’un fichier n’est parfois pas encodé dans l’encodage par défaut de votre OS.

Il n’y a pas de secret. Pas de moyen infaillible de détection automatique. Il faut vérifier.

Vous confondez encodage et décodage (Python 2.7 et moins)

En Python, on DECODE pour passer d’un texte en encodé (UTF8, ISO-8859, CP1552, etc) et donc de type ‘str’ c’est à dire un flux de bits, à un texte unicode, une représentation interne, un objet non imprimable. Il est recommandé de décoder tout texte venant d’une source extérieur à votre programme, pour tout uniformiser.

A l’inverse, on ENCODE pour passer du type ‘unicode’ à un type ‘str’. Il obligatoire d’encoder un texte pour le communiquer au monde extérieur. Si vous ne le faites pas manuellement, Python le fera automatiquement, en essayant de deviner. Il n’est pas excellent à deviner.

En résumé:

In [ 7 ] : texte = open ( '/etc/fstab' ) . read ( ) # ou un téléchargement, ou une requete SQL... In [ 8 ] : type ( texte ) Out [ 8 ] : str In [ 9 ] : texte = texte. decode ( 'UTF8' ) In [ 10 ] : type ( texte ) Out [ 10 ] : unicode In [ 11 ] : print texte # encode automatiquement le texte car votre terminal ne comprend qu'un text encodé # /etc/fstab: static file system information. # [ ............. ] In [ 12 ] : type ( texte. encode ( 'UTF8' ) ) # à faire avant de faire un write Out [ 12 ] : str In [7]: texte = open('/etc/fstab').read() # ou un téléchargement, ou une requete SQL... In [8]: type(texte) Out[8]: str In [9]: texte = texte.decode('UTF8') In [10]: type(texte) Out[10]: unicode In [11]: print texte # encode automatiquement le texte car votre terminal ne comprend qu'un text encodé # /etc/fstab: static file system information. # [.............] In [12]: type(texte.encode('UTF8')) # à faire avant de faire un write Out[12]: str

Si ça continue de foirer, prenez tous les fichiers de votre application un par un: changez toutes les strings en unicode (les précéder d’un “u”), assurez vous que tout ce qui entre est converti en unicode (unicode() après urllib, open, etc) et tout ce qui sort est converti dans un encodage adapté (souvent UTF8) (encode(‘UTF-8’) avant send(), write() ou print).

Si ça ne marche toujours pas, embauchez un mec comme moi qui est payé cher pour se taper la tête contre les murs à la place des autres.

TypeError: ‘int’ object has no attribute ‘__getitem__’ et autres erreurs sur les tuples

Tuples d’un seul élément

CECI N’EST PAS UN TUPLE: (1)

Ceci est un tuple: (1,)

>>> type ( ( 1 ) ) < type 'int' > >>> type ( ( 1 , ) ) < type 'tuple' > >>> t = ( 1 , ) >>> t [ 0 ] 1 >>> t = ( 1 ) >>> t [ 0 ] Traceback ( most recent call last ) : File "<stdin>" , line 1 , in < module > TypeError : 'int' object has no attribute '__getitem__' >>> type((1)) <type 'int'> >>> type((1,)) <type 'tuple'> >>> t = (1,) >>> t[0] 1 >>> t = (1) >>> t[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object has no attribute '__getitem__'

Et il y a plus vicieux:

>>> a = ( "12345" ) >>> b = ( "12345" , ) >>> a [ 0 ] '1' >>> b [ 0 ] '12345' >>> a = ("12345") >>> b = ("12345",) >>> a[0] '1' >>> b[0] '12345'

C’est très dur à débugguer car on dans les deux cas il n’y a pas d’erreur étant donné que c’est une opération tout à fait légitime.

Concaténation automatique

Python vient avec une fonctionnalité qui concatène automatiquement les descriptions littérales de chaînes de caractères:

>>> "Ceci est un" " test" 'Ceci est un test' >>> "Ceci est un" " test" 'Ceci est un test'

C’est très pratique pour les chaînes longues:

>>> print ( "Ceci est une chaîne longue " ... "et je peux la diviser sur plusieurs lignes" ... " sans me fouler" ) 'Ceci est une chaîne longue et je peux la diviser sur plusieurs lignes sans me fouler' >>> print ("Ceci est une chaîne longue " ... "et je peux la diviser sur plusieurs lignes" ... " sans me fouler") 'Ceci est une chaîne longue et je peux la diviser sur plusieurs lignes sans me fouler'

Mais si vous oubliez une virgule dans un tuple (par exemple dans INSTALLED_APPS dans le fichier de settings.py de Django):

>>> REGLES = ( ... "Ne jamais parler du fight club" , ... "Ne jamais croiser les effluves" , ... "Ne jamais appuyer sur le petit bouton rouge" # <===== virgule oubliée ! ... "Ne jamais goûter" ... ) >>> print REGLES [ 3 ] Traceback ( most recent call last ) : File "<stdin>" , line 1 , in < module > IndexError : tuple index out of range >>> print REGLES [ - 1 ] Ne jamais appuyer sur le petit bouton rougeNe jamais goûter >>> REGLES = ( ... "Ne jamais parler du fight club", ... "Ne jamais croiser les effluves", ... "Ne jamais appuyer sur le petit bouton rouge" # <===== virgule oubliée ! ... "Ne jamais goûter" ... ) >>> print REGLES[3] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: tuple index out of range >>> print REGLES[-1] Ne jamais appuyer sur le petit bouton rougeNe jamais goûter

Le fichier/la liste est vide

On ne peut lire qu’une seule fois les générateurs en Python.

Si vous faites:

toto = ( blague. title ( ) for blague in histoire ) toto = (blague.title() for blague in histoire)

ou

toto = open ( 'histoire.txt' ) toto = open('histoire.txt')

Et ensuite:

for blague in toto: print toto len ( list ( toto ) ) for blague in toto: print toto len(list(toto))

La dernière ligne ne marchera pas. Toto aura été vidé par la première boucle for. Si vous souhaitez utiliser plusieurs fois le résultat de votre générateur, il faut le transformer en liste:

toto = list ( toto ) for blague in toto: print toto len ( list ( toto ) ) toto = list(toto) for blague in toto: print toto len(list(toto))

Attention, car vous avez maintenant l’intégralité des données chargées en RAM.

TypeError: ma_function() takes exactly x argument (y given)

Cette erreur est très explicite, et la plupart du temps ne pose aucun problème: vérifiez que vous passez le bon nombre d’arguments à la fonction. Faites particulièrement attention si vous utilisez l’opérateur splat.

Il existe néanmoins un cas particulier un peu taquin:

>>> class Americaine ( object ) : ... def dernier_mot ( mot ) : ... print mot ... >>> homme_le_plus_classe_du_monde = Americaine ( ) >>> homme_le_plus_classe_du_monde. dernier_mot ( "Monde de merde !" ) Traceback ( most recent call last ) : File "<stdin>" , line 1 , in < module > TypeError : dernier_mot ( ) takes exactly 1 argument ( 2 given ) >>> class Americaine(object): ... def dernier_mot(mot): ... print mot ... >>> homme_le_plus_classe_du_monde = Americaine() >>> homme_le_plus_classe_du_monde.dernier_mot("Monde de merde !") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: dernier_mot() takes exactly 1 argument (2 given)

On définie une seul argument ( mot ) et on en passe un seul ( "Monde de merdes !" ), alors pourquoi Python n’est pas d’accord ?

C’est parce que l’on déclare une méthode sans self dans la signature. Or Python va passer automatiquement (et de manière invisible) la référence à l’objet courrant en premier argument, du coup la méthode reçoit deux arguments: la référence à homme_le_plus_classe_du_monde et "Monde de merde !" . Ca ne marche pas puisque la méthode est déclarée pour n’accepter qu’un seul argument.

Il y a deux solutions. La plus simple, ajoutez self :

>>> class Americaine ( object ) : ... def dernier_mot ( self , mot ) : ... print mot ... >>> homme_le_plus_classe_du_monde = Americaine ( ) >>> homme_le_plus_classe_du_monde. dernier_mot ( "Monde de merde !" ) Monde de merde ! >>> class Americaine(object): ... def dernier_mot(self, mot): ... print mot ... >>> homme_le_plus_classe_du_monde = Americaine() >>> homme_le_plus_classe_du_monde.dernier_mot("Monde de merde !") Monde de merde !

Une seconde solution consiste à déclarer une méthode statique. Du coup on a plus besoin d’instance:

>>> class Americaine ( object ) : ... @ staticmethod ... def dernier_mot ( mot ) : ... print mot ... >>> Americaine. dernier_mot ( "Monde de merde !" ) Monde de merde ! >>> class Americaine(object): ... @staticmethod ... def dernier_mot(mot): ... print mot ... >>> Americaine.dernier_mot("Monde de merde !") Monde de merde !

Ma structure de données par défaut n’est pas la bonne

Piège classique en Python, qu’il est important de répéter encore et encore tant il est la source de frustration chez les personnes qui ne le connaissent pas.

>>> from random import choice >>> def bioman ( forces = [ 'rouge' , 'bleu' , 'vert' , 'rose' , 'jaune devant, marron derriere' ] , invite = None ) : ... if invite is not None : ... forces . append ( invite ) ... return choice ( forces ) ... >>> bioman ( ) 'rose' >>> bioman ( ) 'rouge' >>> bioman ( invite = 'magenta a pois gris' ) 'vert' >>> bioman ( ) 'jaune devant, marron derriere' >>> bioman ( ) # WTF ?????????? 'magenta a pois gris' >>> from random import choice >>> def bioman(forces=['rouge', 'bleu', 'vert', 'rose', 'jaune devant, marron derriere'], invite=None): ... if invite is not None: ... forces.append(invite) ... return choice(forces) ... >>> bioman() 'rose' >>> bioman() 'rouge' >>> bioman(invite='magenta a pois gris') 'vert' >>> bioman() 'jaune devant, marron derriere' >>> bioman() # WTF ?????????? 'magenta a pois gris'

Dans le dernier appel ‘magenta a pois gris’ est tiré au sort alors qu’on ne l’a pas passé en paramètre. Comment cela est-il possible ?

Cela vient du fait que les paramètres par défaut sont initialisés une seule fois pour tout le programme: dès que le module est chargé.

Si vous utilisez un objet mutable (liste, set, dico) et que vous le modifiez (ici avec append ), le prochain appel de la fonction utilisera toujours la référence de cet objet, et donc de sa versio modifiée.

La solution est soit de ne pas utiliser d’objet mutable (tuple, strings, int, etc), soit de ne pas modifier l’objet:

>>> def bioman ( forces = ( 'rouge' , 'bleu' , 'vert' , 'rose' , 'jaune devant, marron derriere' ) , invite = None ) : ... if invite is not None : ... forces + = ( invite , ) # ne modifie pas l'ancien objet ... return choice ( forces ) >>> def bioman(forces=('rouge', 'bleu', 'vert', 'rose', 'jaune devant, marron derriere'), invite=None): ... if invite is not None: ... forces += (invite,) # ne modifie pas l'ancien objet ... return choice(forces)

Ou alors (et ceci est souvent utilisé même si c’est moche):

>>> def bioman ( forces = None , invite = None ) : ... if forces is None : ... forces = [ 'rouge' , 'bleu' , 'vert' , 'rose' , 'jaune devant, marron derriere' ] ... if invite is not None : ... forces . append ( invite ) ... return choice ( forces ) >>> def bioman(forces=None, invite=None): ... if forces is None: ... forces = ['rouge', 'bleu', 'vert', 'rose', 'jaune devant, marron derriere'] ... if invite is not None: ... forces.append(invite) ... return choice(forces)

Toutes les parties qui sont éxécutées à l’inialisation du code (en opposition à celles qui le sont à l’appel du code) sont concernées par ce problème: les paramètres par défaut, les variables à la racine des modules, les attributs de classe déclarés en dehors d’une méthode, etc.

ItZ naute a beuhgue, Itse fitiure

Néanmoins cela a aussi son utilité. On peut en effet l’utiliser pour partager des états:

class Model ( object ) : _pool = { 'mysql' : MySQL ( ) . connect ( 'test' ) , 'sqlite' : Sqlite. open ( 'test.db' ) } default_connection = 'mysql' def query ( self , connection = default_connection , *params ) : connection. super_query ( *params ) class Model(object): _pool = { 'mysql': MySQL().connect('test'), 'sqlite': Sqlite.open('test.db') } default_connection = 'mysql' def query(self, connection=default_connection, *params): connection.super_query(*params)

Et vous avez maintenant une classe de modèle qui gère plusieurs connections. Si vous l’étendez, les enfants de la classe et toutes les instances partageront le même objet connection, mais tout le reste sera unique à chacun d’eux. Cela évite un effet de bord du singleton qui oblige à partager un état et une identité. Ici on ne partage que la partie de l’état que l’on souhaite, et pas l’identité.

On gagne sur les deux tableaux: si on update la connection MySQL (par exemple parcequ’on a détecté qu’elle était stale), toutes les instances ont accès à l’objet modifé. Mais si on veut overrider la connection pour une seule classe, on peut le faire sans affecter les autres simplement en remplaçant l’objet à la déclaration de la classe.

On peut aussi utiliser cette fonctionnalité pour créer un cache. On appelle ça “mémoiser”:

def fonction_lente ( param1 , param2 , _cache = { } ) : # les tuples peuvent être des clés de dico \o/ key = ( param1 , param2 ) if key not in _cache: _cache [ key ] = process_lent ( param1 , param2 ) return _cache [ key ] def fonction_lente(param1, param2, _cache={}): # les tuples peuvent être des clés de dico \o/ key = (param1, param2) if key not in _cache: _cache[key] = process_lent(param1, param2) return _cache[key]

Tous les résultats sont alors stockés en mémoire vive.