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 |
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 |
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 |
É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') |
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) |
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')) |
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 pasimport apps.nom
. - 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') |
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 |
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 |
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__' |
Et il y a plus vicieux:
>>> 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' |
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' |
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 |
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) |
ou
toto = open('histoire.txt') |
Et ensuite:
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)) |
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) |
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 ! |
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 ! |
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' |
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) |
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) |
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) |
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] |
Tous les résultats sont alors stockés en mémoire vive.
Ah merci je reconnais 1 ou 2 trucs que j’ai pas aimer en
python.
PS: j’aime bien l’expression “what the fuck d’or”
très classe, simple concise on comprend tout de suite de quoi on parle.
Python c’est un langage burné sévère après tout …
Pingback: Mes trouvailles du jour : 25 June 2012 | DotMana
Pour les imports circulaires, je crois que ça marche en n’utilisant pas “from machin import truc” mais plutôt “import machin as m” et en utilisant dans le code “m.truc”.
Quand on demande d’importer quelque chose en particulier d’un module, il exécute le module à ce moment là et a donc besoin de pouvoir tout inclure, d’où le coincage avec les import circulaires. Au contraire si on importe le module lui-même, il peut le faire plus tard.
Cool, merci pour tout ça. Un peu de grammar-nazisme pour garder la forme :
vous avez essayer (é)
dans un des deux module (+s)
Parceque (+espace)
1 millions (-s)
comment marche(+nt) les encodages
J’ai emprunté l’expression “WTF d’or” au joueur du grenier:
Cet épisode est particulièrement marrant.
Je viens de faire un combo d’erreur “concaténation de chaîne” + “encodage”
# -*- coding: utf-8 -*-
a = u"vive lé gros" "nénés"
Ça renvoie l’erreur bien connue : UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc3 in position 1: ordinal not in range(128)
Sans préciser le numéro de ligne, parce que sinon c’est trop facile. L’indication de position à 1 correspond au premier “é” de “nénés”. Mais si on le sait pas, ça avance pas à grand chose.
# -*- coding: utf-8 -*-
a = u"vive lé gros" u"nénés"
Et là ça marche. Je m’étais dit qu’avec la première déclaration de chaîne unicode, l’interpréteur allait en déduire que le reste le serait aussi. Eh bien non. Faut faire gaffe.
Le SDZ a aussi un article sur la question, fort sympathique:
http://www.siteduzero.com/forum-83-562507-p1-les-erreurs-courantes-en-python.html
Et bien je me suis fait eu par un mutable en paramètre par défaut.
J’instancie un classe avec un dictionnaire, puis j’instancie avec un autre et, quoi, ??…: je retrouve le premier dict dans la seconde instance… Au début je me suis dit: “y’a un fantôme dans ma machine!!, c’est pas possible”.
J’ai bien soupçonné le mutable, mais rien n’y fit jusqu’à ce que je me rappelle ce post qui m’a finalement permis de débloquer la situation.
Une méthode prenait un dictionnaire vide comme argument par défaut:
Je l’ai finalement remplacé par:
… et je passe un dictionnaire vide lors de l’appel :
Merci!
PS:
En fouillant pour résoudre le problème, je me suis rendu compte que la méthode en question a toujours la même adresse mémoire (c’est vrais pour toutes les méthodes j’imagine), quelque soit l’instance (qui ont chaque fois une nouvelle adresse).
.. le lien vers le SDZ est cassé…
… C’est là que je vois qu’ils ont changé les meubles …
… jamais aimé l’organisation des tutos là-bas. Trop de sous-titre sur lesquels cliquer, trop de “page suivante”…
Ca a pas toujours été comme ça. A une époque c’était simple et pas chargé.
Le mieux est l’ennemi du bien.
un petit WTF : j’ai fait un import circulaire dans un moment d’inattention, c’est mal. Mais le pire, c’est que les tests unitaires sont passés tranquilillou, il n’y a qu’en utilisant mes modules de manière normale que ça m’a pété à la gueule ! Comment ça se fait que ni nosetests ni py.test n’ait bloqué là-dessus ? Ils importent bien les fichiers pourtant !
Je précise que j’utilise exclusivement des doctests dans les docstring de mes classes et méthodes.
Aucune idée. Peut être que les doctests n’importent pas les modules qui contiennent les doctests quand on utilise nosetests et py.test et que dans les doctests, il n’y a aucun import qui entraine un import circulaire.
>>> 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"
… )
hahaha, énorme, le clin d'oeil à Ghostbuster m'a beaucoup faire rire. J'ai encore la réplique en RAM. :D
Merci pour se pense-bête qui me dépanne au beau milieu de la nuit.
On a aussi une tag line avec cette référence, mais elle apparait aléatoirement :-)
Bonjour,
il me semble que juste avant la ligne :
il faut lire encode et pas decode dans la phrase :
Si j’ai bien compris la partie sur les encodages :-)