Site original : Sam & Max: Python, Django, Git et du cul
Utiliser les outils de Twisted de base pour faire les requêtes est assez chiant, et quand on est habitué à requests, c’est le retour au moyen age.
La lib treq tente de corriger ça mais n’utilise pas l’API de requests et ne propose pas certaine de ses fonctionnalités.
Du coup, après l’article d’hier, j’ai regardé le code source de requests-futures pour voir si je pouvais pas faire la même chose pour Twisted.
Et on peut. J’ai fais un petit (bon, ok minuscule) wrapper qui permet de faire ça :
from requests_twisted import TwistedRequestsSession session = TwistedRequestsSession() defer = session.get('http://github.com/sametmax/') def print_status(response): print(response.url, response.status_code) defer.addCallback(print_status) |
Ça utilise l’objet Session de requests et donc on peut faire session.get|post|touslestrucsderequests
et toute l’API est disponible.
Donc si vous en avez besoin :
pip install requests-twisted |
Le truc fait 3 lignes et 2 tests unittaires, en fait c’est juste un deferToThreads
derrière. C’est certain que c’est moins performant que l’approche de treq qui utilise directement l’Agent non bloquant de Twisted, mais pour la plupart des cas c’est juste plus pratique, plus familier, et surtout, plus facile à maintenir :)
En attendant le dossier sur la programmation non bloquante, voici une petite lib qui résout un cas d’école : faire une requête HTTP sans bloquer avec une jolie API, en pur Python.
Pour ça, on dégaine pip et installe requests-futures, un plugin pour la célèbre lib requests qui fonctionne avec Python 2 et 3 :
pip install requests-futures |
requests-futures va créer pour vous une pool de workers (2 par défaut) et quand vous faites une requête, la lib vous retourne un objet future qui vous permet d’attacher un callback.
Fiou, le nombre de liens référant à d’autres articles du blog est en train d’exploser.
Exemple :
import time from requests_futures.sessions import FuturesSession # Cette session est notre point d'entrée, c'est elle # qui gère nos workers. Faites help(FuturesSession) # pour voir ses paramètres. session = FuturesSession() # Les URLs sur lesquelles on va faire # nos requêtes URLs = [ "http://sametmax.com", "http://sebsauvage.net", "http://indexerror.net", "http://afpy.org", "http://0bin.net" ] # Notre callback qui sera appelé quand une # des requêtes sera terminée. Il reçoit # l'objet future pour seul paramètre def faire_un_truc_avec_le_resultat(future): # On est juste intéressé par le résutlat, qui # est un objet response typique de la lib # request response = future.result() print(response.url, response.status_code) # On traite chaque URL. Comme on a 2 workers, # on pourra traiter au mieux 2 URLs en parallèle, # mais toujours sans bloquer le programme # principal for url in URLs: # On fait notre requête GET future = session.get(url) # On rajoute le callback à appeler quand # le résultat de la requête arrive. # La flemme de faire la gestion des erreurs. future.add_done_callback(faire_un_truc_avec_le_resultat) # Juste pour montrer que c'est bien non bloquant for x in range(10): print(x) time.sleep(1) |
Output :
0 1 (u'http://sebsauvage.net/', 200) (u'http://sametmax.com/', 200) 2 (u'http://indexerror.net/', 200) (u'http://0bin.net/', 200) (u'http://www.afpy.org/', 200) 3 4 5 6 7 8 9
On remerciera turgon37 pour sa question sur IndexError qui m’a amené à écrire cet article.
Plus on code, plus on oublie ce que c’était quand on a débuté. Même moi, et pourtant je fais un gros effort pour essayer de me replonger dans cet état d’esprit.
Dernièrement j’ai eu plusieurs interactions qui m’ont mis un petit taquet derrière la tête :
- “Mec, les exceptions, y a plein de personnes qui pigent pas.”
– “Nannnn, mais quand même, les exceptions…”
– “Si, les exceptions.”
– “Nan, vraiment ?”
– “Oui, vraiment.”
Donc, back to black, et petit tour de la gestion d’erreur en Python.
Et de la musique.
Une exception est un mécanisme d’interruption du programme utilisé pour signaler que quelque chose d’anormal est en train de se produire.
On les rencontre dans de nombreux cas, mais souvent, c’est dans le cadre d’erreurs. Par exemple, Python va lever une exception dans les cas suivant :
>>> 1 / 0 # division par zéro Traceback (most recent call last): File "<ipython-input-2-b710d87c980c>", line 1, in <module> 1 / 0 ZeroDivisionError: integer division or modulo by zero >>> l = [1, 2, 3] >>> l[100] # dépassement d'un tableau Traceback (most recent call last): File "<ipython-input-4-3e7ce3111e95>", line 1, in <module> l[100] IndexError: list index out of range >>> d = {'cle': 'valeur'} >>> d['nope'] # clé de dico inconnue Traceback (most recent call last): File "<ipython-input-6-9d965227e4b0>", line 1, in <module> d['nope'] KeyError: 'nope' >>> 1 + "banane" # opération entre types incompatibles Traceback (most recent call last): File "<ipython-input-7-5147635e41b3>", line 1, in <module> 1 + "banane" TypeError: unsupported operand type(s) for +: 'int' and 'str' >>> import nawak # ce module n'existe pas Traceback (most recent call last): File "<ipython-input-12-c948b8798146>", line 1, in <module> import nawak ImportError: No module named nawak >>> open('nawak') # le fichier n'existe pas Traceback (most recent call last): File "<ipython-input-13-4d15cfd2ffd1>", line 1, in <module> open('nawak') IOError: [Errno 2] No such file or directory: 'nawak' >>> print(nawak) # la variable n'existe pas Traceback (most recent call last): File "<ipython-input-15-f47a27cedc12>", line 1, in <module> print(nawak) NameError: name 'nawak' is not defined >>> n a w a k # erreur de syntaxe File "<ipython-input-16-532bf025442e>", line 1 n a w a k # erreur de syntaxe ^ SyntaxError: invalid syntax >>> int('a') # la valeur passée n'a aucun sens Traceback (most recent call last): File "<ipython-input-17-b3c3f4515dd4>", line 1, in <module> int('a') ValueError: invalid literal for int() with base 10: 'a' |
De base, il y a un paquet d’exceptions en Python, et celles fournies par défaut sont listées ici.
Malgré l’abondance de ces exceptions, vous pouvez remarquer un motif récurrent quand Python crash et affiche l’erreur :
Gros pâté de texte. Gros pâté de texte. Gros pâté de texte. NomDeLExceptionError: description de l'erreur.
En effet, non seulement une exception interrompt le programme, mais elle collecte des informations sur la source de l’erreur afin qu’au moment où ça crash, le développeur ait de quoi déboguer.
Une exception va donc normalement déclencher un affichage à la fin avec toutes ces infos, et sur la dernière ligne, le type de l’exception (ValueError, IndexError, etc) ainsi qu’une description de ce qui a causé l’erreur.
Néanmoins, le plus intéressant est ce qu’il y a au dessus : le gros pâté de texte. C’est ce qu’on appelle une stack trace, et ça représente la pile d’appels qui ont amené à cette erreur. Chaque ligne de la stack trace va vous donner le chemin d’un fichier de code, et une ligne.
Une erreur se lit donc à l’envers, de bas en haut.
Vous lisez d’abord le nom de l’erreur et sa cause, puis, vous remontez la stack trace ligne à ligne, pour essayer de trouver quelle ligne de quel fichier de code vous devez déboguer.
Par exemple, si vous avez un fichier wololo.py
:
def une_fonction(): return 1 / 0 def une_autre_fonction(): une_fonction() une_autre_fonction() |
Lancer ce script va vous pondre :
Traceback (most recent call last): File "wololo.py", line 7, inune_autre_fonction() File "wololo.py", line 5, in une_autre_fonction une_fonction() File "wololo.py", line 2, in une_fonction return 1 / 0 ZeroDivisionError: integer division or modulo by zero
On le lit de bas en haut :
ZeroDivisionError: integer division or modulo by zero
Ok, donc c’est une erreur de division par 0.
File "wololo.py", line 2, in une_fonction return 1 / 0
Elle a lieu dans mon fichier wololo.py
à la ligne 2.
Mais visiblement, ce qui déclenche tout le bordel, c’est que la fonction contenant cette ligne est appelé à la ligne 5 :
File "wololo.py", line 5, in une_autre_fonction une_fonction()
Qui est elle même appelée à la ligne 7 :
File "wololo.py", line 7, inune_autre_fonction()
Avec ces infos, on peut prendre une décision : aller ligne 2 pour corriger un bug, ou aller ligne 5, ou 7 pour retirer l’appel qui cause le déclenchement du code fautif.
Dans ce cas, vous pourriez vous dire qu’il faut retirer la division par 0, et que les premières ligne de la stack trace ne servent à rien.
Mais imaginez que je change mon code :
def une_fonction(diviseur): return 1 / diviseur def une_autre_fonction(): une_fonction(diviseur=0) une_autre_fonction() |
A ce moment là, mon stack trace sera :
Traceback (most recent call last): File "wololo.py", line 7, inune_autre_fonction() File "wololo.py", line 5, in une_autre_fonction une_fonction(diviseur=0) File "wololo.py", line 2, in une_fonction return 1 / diviseur ZeroDivisionError: integer division or modulo by zero
Et là, on ne veut pas changer la ligne 2, on veut changer la ligne 5, car c’est ce paramètre qui fait qu’à la ligne 2 on a une division par 0.
La stack trace permet donc de remonter la chaîne de causalité de vos erreurs.
Quand une exception est activée, on dit qu’elle est “levée”.
Python va lever des exceptions de lui-même, par exemple en cas d’erreurs, mais on peut aussi lever soi-même des exceptions en faisant raise NomDeLException("Message")
.
Par exemple, vous avez une fonction qui n’accepte en paramètre que comme valeur 1, 2 ou 3. Vous savez que si un utilisateur rentre quelque chose d’autre, ça va tout faire merder.
Vous pouvez faire ceci :
def votre_super_fonction(param): if param not in (1, 2, 3): raise ValueError("'param' can only be either 1, 2 or 3") # reste du code |
Ainsi, si quelqu’un passe un mauvais paramètre, il est immédiatement alerté :
>>> votre_super_fonction(4) Traceback (most recent call last): File "<ipython-input-3-bcd6e8653c83>", line 1, in <module> votre_super_fonction(4) File "<ipython-input-2-46fc7cd18c42>", line 3, in votre_super_fonction raise ValueError("'param' can only be either 1, 2 or 3") ValueError: 'param' can only be either 1, 2 or 3 |
Attention : le message ne peut pas contenir de caractères non-ASCII. Oubliez les accents.
Les exceptions ne sont pas un simple mécanisme de debuggage. Elles servent d’abord et avant tout à gérer les cas exceptionnels, et on peut donc les détecter, et réagir quand elles surviennent, à l’aide de l’instruction try/except
.
Cela s’utilise en enrobant les lignes qui peuvent lever une exception :
try: # lignes qui peuvent # lever une exception except NomException: # faire un truc si l'exception se déclenche |
Quand vous attrapez une exception, le programme ne plante pas. A la place, le bloc except
correspondant au nom de l’exception est appelée.
Bien entendu, si une exception qui ne porte pas ce nom est levée, le programme plante.
Par exemple :
# i est définit plus haut personnages = ['iron', 'super', 'bat', 'clepto'] try: resultat = personnages[i] # i est plus grand que la taille du tableau except IndexError: resultat = None |
Si i
est plus grand que la taille de la liste, Python va lever une IndexError
, qui sera attrapée. Le programme ne plantera pas, et resultat
sera égal à None
.
Si i
est de type string, Python va lever une TypeError
, et le programme va planter.
Cela suppose de savoir le nom de l’erreur qu’on veut gérer. Je recommande donc de tester le cas foireux dans le shell auparavant.
On peut gérer plusieurs exceptions avec un seul bloc :
try: return personnages[100 / i] # est exécuté pour ces deux exceptions except IndexError, ZeroDivisionError: return None # est exécuté pour cette exception except KeyError: print("Qui est le bâtard qui a remplacé ma " "liste par un dico dans mon dos ?") |
Les exceptions sont des classes. Et si vous avez lu notre dossier sur la POO, vous savez que les classes peuvent hériter d’autres classes.
C’est le cas des exceptions. Celles intégrées à Python suivent la hiérarchie suivante :
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EnvironmentError | +-- IOError | +-- OSError | +-- WindowsError (Windows) | +-- VMSError (VMS) +-- EOFError +-- ImportError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning
Par exemple, IndexError
et KeyError
héritent toutes les deux de LookupError
.
Donc si vous faites :
try: return personnages[i] except LookupError: return None |
Vous attrapez à la fois les exceptions de type IndexError
et KeyError
.
LookupError
hérite de Exception
, dont hérite la plupart des exceptions en Python. Donc si vous faites :
try: return personnages[100 / i] except Exception: return None |
Vous allez attraper LookupError
, IndexError
, ZeroDivisionError
et probablement tout un tas de trucs auxquels vous n’avez pas pensé ce qui peut potentiellement un jour masquer un bug dans votre programme.
Donc gérez toujours l’erreur au plus proche de la logique de votre programme.
Il est d’ailleurs possible de dire à Python “tu m’attrapes tout” en omettant le nom de l’exception :
try: return personnages[100 / i] except: return None |
C’est généralement une mauvaise idée car si il arrive une erreur que vous n’avez pas prévue, la close except
s’activera, masquant l’erreur, et ne vous donnera jamais la chance de découvrir le problème.
Sachez également que vous n’êtes pas limité à la hiérarchie offerte par Python. Vous pouvez créer vos propres exceptions en héritant d’une des exceptions existantes de Python :
class MonProjetError(Exception) pass class MonTraitementError(MonProjetError) pass |
C’est généralement une bonne idée quand vous créez une lib et que vous voulez permettre aux utilisateurs de votre bibliothèque de pouvoir filtrer les exceptions levées par votre code uniquement.
Néanmoins, dans ce cas, choisissez intelligemment les parents de vos exceptions personnalisées. Chaque exception de Python est porteuse de sens. TypeError
est levée pour une erreur de type, IOError
pour une erreur d’entrée/sortie, etc. Il est logique qu’un développeur s’attende à pouvoir les attraper selon ce sens.
Heureusement, on peut hériter de plusieurs exceptions :
class MonProjetError(Exception) pass class MonTraitementError(MonProjetError, TypeError) pass |
Du coup, si quelqu’un essaye d’attraper MonTraitementError
ou TypeError
ou MonProjetError
selon ce qu’il désire exprimer, ça marchera.
En effet, les exceptions sont une forme d’expression :
Vous communiquez en utilisant des exceptions entre développeurs et utilisateurs du code.
try/except
peut être complété par deux autres mots clés : finally
et else
.
else
est le bloc exécuté si aucune exception n’est levée :
try: return personnages[100 / i] except IndexError, ZeroDivisionError: return None except KeyError: print("Qui est le bâtard qui a remplacé ma " "liste par un dico dans mon dos ?") else: print('Bon en fait tout va bien') |
finally
est un bloc qui est exécuté après que tous les autres blocs aient été exécutés, peu importe qu’il y ait eu une exception ou non, et même si le programme crash.
try: return personnages[100 / i] except IndexError, ZeroDivisionError: return None except KeyError: print("Qui est le bâtard qui a remplacé ma " "liste par un dico dans mon dos ?") finally: print('Rosebud !') |
Dans notre cas, peu importe ce qui se passe dans le bloc try/except
, Rosebud !
est toujours affiché.
finally
n’est pas à l’épreuve des balles, si on crash l’interpréteur violemment (ou que vous débranchez la prise du serveur :)), il ne vous sauvera pas. Mais il permet de gérer la plupart des cas extrêmes.
Ah, oui, au fait, si vous utilisez finally
, except
n’est pas obligatoire.
On utilisera try/except
d’abord et avant tout pour gérer les erreurs sur laquelle on a pas de controle.
Par exemple, vous téléchargez une document depuis le Web :
>>> import urllib.request >>> fichier_recu, headers = urllib.request.urlretrieve('http://www.sudinfo.be/sites/default/files/imagecache/pagallery_450x300/1365338424_B9765144Z.1_20121219124926_000_GOJDROM6.1-0.png') |
Ca va marcher la plupart du temps. Et puis un jour, le réseau va sauter, et ça va plus marcher :
>>> fichier_recu, headers = urllib.request.urlretrieve('http://www.sudinfo.be/sites/default/files/imagecache/pagallery_450x300/1365338424_B9765144Z.1_20121219124926_000_GOJDROM6.1-0.png') Traceback (most recent call last): File "<ipython-input-10-0f41d15585f0>", line 1, in <module> fichier_recu, headers = urllib.request.urlretrieve('http://www.sudinfo.be/sites/default/files/imagecache/pagallery_450x300/1365338424_B9765144Z.1_20121219124926_000_GOJDROM6.1-0.png') File "/usr/lib/python3.4/urllib/request.py", line 178, in urlretrieve with contextlib.closing(urlopen(url, data)) as fp: File "/usr/lib/python3.4/urllib/request.py", line 153, in urlopen return opener.open(url, data, timeout) File "/usr/lib/python3.4/urllib/request.py", line 455, in open response = self._open(req, data) File "/usr/lib/python3.4/urllib/request.py", line 473, in _open '_open', req) File "/usr/lib/python3.4/urllib/request.py", line 433, in _call_chain result = func(*args) File "/usr/lib/python3.4/urllib/request.py", line 1258, in http_open return self.do_open(http.client.HTTPConnection, req) File "/usr/lib/python3.4/urllib/request.py", line 1235, in do_open raise URLError(err) URLError: <urlopen error [Errno -2] Name or service not known> |
On va donc gérer l’exception (et beaucoup d’autres, le réseau ça foire tout le temps) :
try: fichier_recu, headers = urllib.request.urlretrieve('http://www.sudinfo.be/sites/default/files/imagecache/pagallery_450x300/1365338424_B9765144Z.1_20121219124926_000_GOJDROM6.1-0.png') except urllib.URLError: # faire un truc |
Parmi les choses à faire : réessayer plus tard, tenter de redémarrer la carte réseau, changer d’URL…
Un autre raison d’utiliser try/except
est pour enregistrer une trace des erreurs.
try: url = 'http://www.sudinfo.be/sites/default/files/imagecache/pagallery_450x300/1365338424_B9765144Z.1_20121219124926_000_GOJDROM6.1-0.png' fichier_recu, headers = urllib.request.urlretrieve(url) except urllib.URLError: with open('/tmp/errors.log', 'a') as f: f.write("L'URL '%s' est inacessible" % url) |
On peut le faire avec le module logging
, ou envoyer un mail ou un SMS d’alerte…
Proposer un comportement par défaut est aussi très courant.
Par exemple, vous voulez récupérer le premier élément d’une liste, mais si la liste est vide, récupérer None
:
try: resultat = liste[0] except IndexError: resultat = None |
Ce cas d’usage est typique de Python. Dans d’autres langage, on regarderait si le tableau est vide :
if liste: resultat = liste[0] else: resultat = None |
C’est typiquement la philosophie Java : “Watch before you leap” (WBYL). Soit “Regardez où vous mettez les pieds”.
En Python, la philosophie est “It’s easier to ask for forgiveness than permission” (EAFP). Soit “il est plus facile de demander pardon que la permission”.
La raison est que les exceptions sont très rapides en Python, et permettent de gérer plein de cas d’un coup.
Par exemple, dans le cas :
if conteneur: resultat = conteneur[0] else: resultat = None |
Que se passe-t-il si conteneur peut être un dictionnaire ou une liste ? C’est possible en Python du fait du duck typing.
Il faut tester deux cas :
if conteneur and 0 in conteneur: resultat = conteneur[0] else: resultat = None |
Avec une exception, on teste un seul cas :
try: resultat = conteneur[0] except LookupError: resultat = None |
Là c’est un cas simple, mais sur des cas complexes comme les fichiers, c’est très utile :
try: fichier = open('/tmp/fichier', 'w') except IOError, OSError: # gérer l'erreur |
Si je devais faire ça avec des if
, il faudrait :
C’est relou. Mais en prime, entre chaque vérification du if
s’écoule quelques nanosecondes durant lequelles l’état de mon fichier a pu changer. Un autre programme a pu changer les permissions, créer un fichier avec le même nom, etc. Arrivé à la fin du if
, on n’a pas la garantie que les vérifications du début du if
sont toujours d’actualité.
try/except
évite ce problème qu’on appelle des “race conditions” : on essaye d’abord, et ensuite si ça foire, on analyse pourquoi. Comme en drague.
Enfin on va utiliser finally
pour faire du nettoyage : fermer un fichier, fermer une connexion à une base de données, une socket, supprimer un fichier temporaire. Bref, tout ce qu’on veut qui arrive, même si notre code se vautre.
try: fichier = open('/tmp/fichier', 'w') except IOError, OSError: # gérer l'erreur else: # faire un truc avec le fichier # on essaye toujours de fermer notre fichier finally: try: fichier.close() # le fichier n'a jamais été ouvert et # la variable n'existe pas except NameError: pass |
Comme vous avez pu le constater, gérer proprement les erreurs, ça peut devenir vite chiant. Pour cette raison, beaucoup de context managers servent à gérer les cas les plus courants.
Typiquement :
try: fichier = open('/tmp/fichier', 'w') except IOError, OSError: # gérer l'erreur else: # faire un truc avec le fichier finally: try: fichier.close() except NameError: pass |
Peut être remplacé par :
try: with open('/tmp/fichier', 'w') as fichier: # faire un truc avec le fichier except IOError, OSError: # gérer l'erreur |
Ce qui est quand même vachement plus court. Le context manager va s’occuper de la fermeture du fichier pour nous proprement. Cet article est déjà long, donc je ne vais pas rentrer en détail sur with
, mais sachez que beaucoup d’objets qui demandent une fermeture peuvent être traités comme ça.
Une chose qui échappe généralement aux débutants quand ils commencent à utiliser les exceptions, c’est qu’elles bubblent, c’est à dire qu’elles remontent comme une bulle, de bloc en bloc, jusqu’à la racine du programme.
Si j’ai :
if truc: if machin: for bidule in chose: raise MaisAieuuuuuError("Ca fait mal t'es con") |
Mon programme ne va PAS planter à la ligne raise MaisAieuuuuuError("Ca fait mal t'es con")
.
A la place, l’erreur va remonter en dehors de la boucle. Si elle n’est pas attrapée, elle va remonter en dehors du premier if
. Si elle n’est pas attrapée, elle va remonter en dehors du second if
, et enfin, si elle vraiment, mais alors vraiment pas attrapée, Python va crasher, et montrer une belle stack trace.
Ça veut dire que vous pouvez arrêter l’exception à plusieurs endroits dans le programme :
if truc: if machin: for bidule in chose: try: raise MaisAieuuuuuError("Ca fait mal t'es con") except MaisAieuuuuuError: # faire un truc |
Dans ce cas, la boucle va continuer à tourner après la première erreur, et faire un try/except
à chaque tour de boucle.
Mais si vous faites :
if truc: if machin: try for bidule in chose: raise MaisAieuuuuuError("Ca fait mal t'es con") except MaisAieuuuuuError: # faire un truc |
A la première erreur, la boucle est interrompue définitivement. Par contre la condition if machin
va continuer sa route.
Mais si vous faites :
if truc: try: if machin: for bidule in chose: raise MaisAieuuuuuError("Ca fait mal t'es con") except MaisAieuuuuuError: # faire un truc |
La condition if machin
est définitivement interrompue.
Et si vous faites :
try: if truc: if machin: for bidule in chose: raise MaisAieuuuuuError("Ca fait mal t'es con") except MaisAieuuuuuError: # faire un truc |
Là, tout le programme est interrompu. Ca ne plantera pas, mais ça ne fera plus rien.
Il faut donc choisir soigneusement où attraper l’exception selon la partie du programme qu’on souhaite interrompre.
Les exceptions sont des objets complets. On peut les récupérer avec le mot clés as
et analyser leur contenu.
Exemple :
try: f = open("nawak") except IOError as e: print("args: ", e.args) print("errno: ", e.errno) print("filename: ", e.filename) print("strerror: ", e.strerror) ## args: (2, 'No such file or directory') ## errno: 2 ## filename: nawak ## strerror: No such file or directory |
Il est même possible de les lever à nouveau après :
try: f = open("nawak") except IOError as e: print("args: ", e.args) print("errno: ", e.errno) print("filename: ", e.filename) print("strerror: ", e.strerror) raise e |
raise
appelé sans argument lève de toute façon automatiquement l’exception en cours. Mais il est possible de lever une autre exception :
try: f = open("nawak") except IOError as e: print("args: ", e.args) print("errno: ", e.errno) print("filename: ", e.filename) print("strerror: ", e.strerror) raise MonError("T'as encore oublie ton classeur Man") |
Il est possible d’attraper n’importe quelle exception non gérée juste avant qu’elle fasse crasher le programme, sans avoir à mettre tout son code dans un gros try/except
.
Il faut définir une fonction qui accepte 3 arguments que sont la classe de l’exception, l’instance de l’exception et l’objet traceback :
def attrapez_les_tous(type, value, traceback): print("Pokemon !") |
Ensuite il faut l’attacher au module sys
avec le bon nom :
import sys sys.excepthook = attrapez_les_tous |
Et hop :
1 / 0 Pokemon ! |
Alors, c’est certain que si vous faites ça, vous avez intérêt à savoir ce que vous branlez car vous pouver tuer le debuggage. Ou alors créer un super moyen de logger toute erreur sur un process séparé qui vous le présente dans une belle interface Web. Au choix.
Les exceptions sont la pour gérer des cas exceptionnels, et pas forcément une erreur. Par exemple, la boucle for
utilise les exceptions pour savoir quand s’arrêter. En effet elle attend que l’itérateur qu’elle utilise lève StopIteration
:
>>> iterateur = iter(range(3)) >>> next(iterateur) 0 >>> next(iterateur) 1 >>> next(iterateur) 2 >>> next(iterateur) --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-11-d90e1e04a685> in <module>() ----> 1 next(iterateur) StopIteration: |
Donc quand on fait ça :
for x in range(3): print(x) |
En fait, la boucle fait un truc comme ça :
iterateur = iter(range(3)) while True: try: print(next(iterateur)) except StopIteration: break |
Les exceptions en Python, c’est une grande histoire d’amour.
Régulièrement, je donne à des organismes. Je ne pense pas qu’il y ait un combat plus important que d’autres. Si une personne veut supporter les yorkshires dépressifs anorexiques, une autre les goths hémophiles sidaïques et une dernière les ti nenfants qui meurent de la faim mortelle dans le monde mondial, très bien.
Personnellement, j’ai tendance à donner à des entités qui ont produit un travail concret dont j’ai pu profiter des fruits dans ma vie. Je suis un égoïste. Typiquement : la quadrature du Net, VLC, Ubuntu, Libre Office, Debian, la Python et la Free Software Fondation, Wikipedia, etc.
Mais comment motiver les gens à donner ?
Ça c’est plus compliqué. Tous les bloggers le savent, nos liens en bas qui demandent la charité rapportent peau de balle. En deux ans, on a récolté 200 euros avec flatr et 0.x bitcoins.
Du coup je me suis dit que peut être en donnant l’exemple on allait créer des vocations. Voilà donc l’idée : à chaque fois que je vais donner des sous, je vais écrire un article pour dire à qui, combien, et pourquoi.
Le but est multiple :
Mozilla, la boîte qui a fait Firefox, est vraiment, mais alors vraiment formidable.
Ce sont les premiers à avoir fournit un logiciel libre de qualité pour surfer le Web. Souvenez-vous : d’abord il s’appelait Pheonix, puis Firebird, avant de devenir Firefox, mais il est là depuis 13 ans ! S’inspirant du très beau Opera, il a proposé des fonctionnalités qui paraissent évidentes aujourd’hui comme les onglets, le blocage de pop up ou la complétion des champs de formulaires. Et surtout, il a popularisé le principe des extensions.
Firefox, et donc Mozilla, est la raison pour laquelle vous pouvez profiter de pages sans pub grâce à Adblock aujourd’hui.
Firefox est la seule raison pour laquelle on s’est pas bouffé de l’IE pendant toutes ses années, Firefox est celui qui a montré la voie à l’équipe de Google pour qu’ils lancent Chrome, et la bataille sur les perfs d’affichage et d’exécution JS dont on bénéficie aujourd’hui.
Mais Mozilla, ce n’est pas que Firefox.
C’est une association à but non lucratif qui passe son temps à défendre le Web ouvert. En se battant sur les standards, en documentant et en continuant de prendre des risques dans une industrie de plus en plus conservatrice.
En effet, malgré des revenus très inférieurs à la plupart des acteurs concurrents, Mozilla s’est lancé dans :
Leurs projets libres sont nombreux, et il y en a plein en Python.
Mozilla est aussi le seul acteur à ne pas censurer son store pour répondre à ses besoins mercantiles.
Et les gars sont très approchables. Vous pouvez littéralement envoyer un tweet à Tarek Ziadé ou Tristan Nitot et avoir une réponse.
Il se trouve que Mozilla accepte les bitcoins sur sa page de don \o/. Ils utilisent Coinbase comme plateforme, ce qui leur permet de hooker un mail de confirmation et d’avoir un retour sur paiement très rapide puisque Coinbase est initialement un service d’analyse de la block chain et qu’ils la surveillent en permanence.
J’ai donc fait un envoi de 0.25 BTC, ce qui équivalait à environ $50. Encore une fois, la somme est directement liée à ce que j’ai pu me permettre ce jour là. Si vous avez juste un euro à donner une fois dans l’année, c’est parfait. On s’en branle. Il y a des mois où je ne donne rien car j’ai pas un rond.
Juste pour signaler rapidement qu’on a changé la logique d’attribution de points sur indexerror.net.
Le réglage par défaut du site était de multiplier tous les gains de points par 10, ce qui incite les premiers utilisateurs car on a l’impression de progresser très vite.
Mais ça implique aussi que les points n’ont vite plus de sens : en 2 semaines j’avais cumulé plusieurs milliers de points.
Donc on a changé ça, et les points ont été recalculé pour que chacun ait un total plus raisonnable.
C’est donc tout à fait normal si vous les voyez chuter d’un coup. Max a envoyé un mail à tous les inscrits normalement, mais je mets la justification ici just in case.