Voyez vous, quand on écrit un script bash, il exécute les commandes ligne à ligne. Si il y en a une qui retourne une erreur, il continue vaillamment.
Sur un script où chaque ligne dépend de la précédente (comme un script de déploiement), c’est moyen.
Pour y pallier, il suffit de mettre tout en haut du script :
set -e
Et voilà. Si une commande retourne une erreur, le script s’arrête à ce niveau. 10 ans de Linux et je découvre ça maintenant. Moi content.
]]>include *.conf
dans son nginx.conf et que cela inclut nginx.conf lui même.
Il faut donc être spécifique sur les fichiers à inclure include site.conf
ou déplacer nginx.conf dans un autre dossier que celui des sous fichiers de configuration.
Par exemple:
class Truc(threading.Condition):
pass
class Machine(tempfile.NamedTemporaryFile):
pass
Lèveront l’exception :
TypeError: Error when calling the metaclass bases function() argument 1 must be code, not str
Car:
>>> import threading, tempfile
>>> type(threading.Condition)
>>> type(tempfile.NamedTemporaryFile)
Malgré leurs noms en majuscule.
Le message d’erreur est lui-même complètement obscure. Bref, le genre de truc qui est 100% lié à des erreurs d’autres personnes que vous aller payer par une après-midi de debug si on ne vous donne pas la solution.
Mais bon, la queue de celui qui n’a jamais pourri l’après-midi d’un autre codeur avec son travail merdique jette le premier parpaing.
]]>Aujourd’hui pourtant, ce système si merveilleux nous a bien fait chier pendant une demi-heure.
Cas simple: Max me file un snippet bien racheux dans 0bin. Je clic sur “copy to clipboard”, j’élague la fonction des loggers et des try/catch qui attrapent tout, même un rhûme, et je lance des tests.
File "truc.py", line 35
^
SyntaxError: invalid syntax
Quid ?
Je cherche, je recherche, je creuse, je retourne, je m’enfonce.
Passage en mode fourmis.
Je retire des blocs. Des lignes une a une. Des combinaisons des deux. Des combinaisons arbitraires, aléatoires de blocs transposés dans un autre fichier après conversion en utf-8 et des tabs en espaces.
File "truc.py", line racine de 12
^
SyntaxError: invalid syntax
Je prends Max a témoin.
Nous cherchons. Nous recherchons. Nous creusons. Je m’énerve.
Ce putain de code DOIT marcher. Il n’y a AUCUNE putain d’erreur de syntax là dedans.
Puis eureka, je copie et je colle le texte comme string dans le shell Python.
Lumière:
>>>"""def download(url, dest_path, progress_callback=lambda x, y: 0, proxy=None, block_sz=8192):
...
...
... if proxy is not None:
... # build a new opener that uses a proxy requiring authorization
... proxy_support = urllib2.ProxyHandler({"http" : proxy})
... opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
...
... # install it
... urllib2.install_opener(opener)
...
... u = urllib2.urlopen(url, timeout=30)
... f = open(dest_path, 'w')
...
... meta = u.info()
...
... file_size = int(meta.getheaders("Content-Length")[0])
...
... block_sz = file_size_dl = 8192
... buffer = u.read(block_sz)
... previous_status = ()
...
... while buffer:
...
... file_size_dl += block_sz
... f.write(buffer)
... status = (file_size_dl, file_size_dl * 100. / file_size)
... if status != previous_status:
... """
...
'\ndef download(url, dest_path, progress_callback=lambda x, y: 0, proxy=None, block_sz=8192):\n \n \xc2\xa0\n if proxy is not None:\n # build a new opener that uses a proxy requiring authorization\n proxy_support = urllib2.ProxyHandler({"http" : proxy})\n opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)\n \xc2\xa0\n # install it\n urllib2.install_opener(opener)\n \xc2\xa0\n u = urllib2.urlopen(url, timeout=30)\n f = open(dest_path, \'w\')\n \xc2\xa0\n meta = u.info()\n \n file_size = int(meta.getheaders("Content-Length")[0])\n \xc2\xa0\n block_sz = file_size_dl = 8192\n buffer = u.read(block_sz)\n previous_status = ()\n \xc2\xa0\n while buffer:\n \xc2\xa0\n file_size_dl += block_sz\n f.write(buffer)\n status = (file_size_dl, file_size_dl * 100. / file_size)\n if status != previous_status:\n previous_status = status\n progress_callback(*status)\n \n buffer = u.read(block_sz)\n \xc2\xa0\n \xc2\xa0\n f.close()\n \xc2\xa0\n '
Mais quel est ce petit salopard de “\xc2\xa0” ?
In [2]: print "a\xc2\xa0b"
a b
Caractère utf8 pour “espace insécable”.
Pour Sublime Text et 0bin, se sont des espaces comme les autres. Pour Python, c’est une syntax error.
Fuck.
La coloration syntaxique de 0bin doit sans doute insérer ce truc à chaque saut de ligne. Du coup on a patché tout ça, les sources sont à jour sur github et j’en ai profité pour updater le paquet sur pypi qui est maintenant la dernière en date avec tous les goodies: détection de code source, send by mail, compteur, etc.
]]>========= Commencez la copie APRES cette ligne ================
Quel est mon objectif ? (Donnez un context général, mais précisez quel point en particulier vous pose problème)
Qu’est-ce que j’ai essayé de faire ? (Expliquez pas à pas. Postez aussi les bouts de code. Si c’est trop long, utilisez 0bin.net)
Quels résultats ai-je obtenus ? (Ne pas oublier les messages d’erreur).
Qu’est-ce que je pense que j’aurais dû obtenir au lieu de cela ? (Pour qu’on comprenne ce que vous comprenez)
Quelle est ma situation ? (Version, langue, marchine, système, etc. pour chaque logiciel et library)
========= Arrêtez la copie AVANT cette ligne ================
Inutile de préciser que c’est urgent ou que vous êtes un débutant. Écrire une bonne question prend du temps, comme écrire une bonne réponse. Vous êtes pressé ? Nous aussi. Il n’y a pas de raccourci.
J’aime beaucoup l’article “Comment poser une question intelligement” mais il fait 3 km de long et une personne qui ne prend pas le temps de formuler correctement sa demande a peu de chance de prendre le temps de le lire. L’approche de Google code est beaucoup plus efficace.
Bloggers et admins du monde (francophone) entier, ne vous embêtez plus à demander des précisions. Postez juste un lien vers cet article pour chaque personne qui ne donne pas toutes les infos.
]]>.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:
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.
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:
import nom
et pas import apps.nom
.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')
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:
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 -*-
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.
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.
CECI N’EST PAS UN TUPLE: (1)
Ceci est un tuple: (1,)
>>> type((1))
>>> type((1,))
>>> t = (1,)
>>> t[0]
1
>>> t = (1)
>>> t[0]
Traceback (most recent call last):
File "", line 1, in
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.
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 "", line 1, in
IndexError: tuple index out of range
>>> print REGLES[-1]
Ne jamais appuyer sur le petit bouton rougeNe jamais goûter
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.
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 "", line 1, in
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 !
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.
]]>