Les exceptions avec Python 3

Comment gérer les exceptions et créer les siennes

a marqué ce sujet comme résolu.

Nope. La formulation n’est pas correcte.

Une source serait carrément appréciable.

L’infobulle est écrite sur le même ton que ce message. Il faut adapté la formulation.

+0 -0

Concernant la formulation, je ne saurais pas reformuler, je laisse le soin de le faire à quelqu’un d’autre. Quant à une source : https://stackoverflow.com/questions/3929837/python-if-vs-try-except ( la troisième réponse ) https://www.developpez.net/forums/d1581496/c-cpp/cpp/gestion-exceptions-performances/

Je viens de passer 10 minutes à chercher et j’ai pas trouver plus substantiel. La seconde concerne le Cpp, mais il y a des choses en commun avec les exceptions Python. La première lien est suffisamment clair mais pas assez complet. J’ajoute que en creusant un peu plus les recherches, il doit y avoir de meilleures sources, ce que je ne peux pas faire tout de suite désolé ':)

Le lien Stackoverflow est sympa, mais as-tu testé les codes proposés ?

from __future__ import print_function
  
def tryway():
    alist = list(range(1000))
    try:
        while True:
            alist.pop()
    except IndexError:
        pass

def ifway():
    alist = list(range(1000))
    while True:
        if alist == []:
            break
        else:
            alist.pop()
if __name__=='__main__':
    from timeit import Timer
    print("Testing Try")
    tr = Timer("tryway()","from __main__ import tryway")
    print(tr.timeit(10000))
    print("Testing If")
    ir = Timer("ifway()","from __main__ import ifway")
    print(ir.timeit(10000))

Chez moi :

$ python test.py
Testing Try
0.78213095665
Testing If
1.11239290237

$ python3 test.py
Testing Try
0.8470716489973711
Testing If
1.0310280029952992

En C++, le fait que les exceptions soient lentes est, pour autant que je sache, bien sourcé. En python, ce n’est pas le cas (tout est lent, dirons les mauvaise langues). En tout cas, rien d’évident.

+1 -0

Je n’avais pas du tout testé les codes proposés non ':). Etrange que tu aies un résultat différent à ce point du leur, mais je me permets de proposer une hypothèse. Tu as, peu importe le try ou le if, un résultat qui est l’équivalent à la moitié du leur. Je dis peut-être ( surement) une connerie car le hardware n’est pas mon domaine, mais la puissance de ton pc/la mémoire cache toussa toussa n’aurait pas une influence sur le résultat ? Je ne peux pas tester le code tout de suite, mais je testerais si je n’oublie pas.

J’ai un PC récent avec un bon processeur. Sinon, il y a une personne qui a noté le même comportmeent

Although your results show that the try is a little bit slower than while, my test with Python 3 gave me the following: Testing Try 1.0719404926951281 Testing while 1.2370897208711098 Testing for 0.5321000439965737

Après, je rappel que ton lien a 8 ans. Python peut être mieux optimisé (en général ou spécifiquement sur les exceptions) qu’à l’époque, les processeurs plus efficaces…

+0 -0

J’ai un PC bien pourri. Il tient avec 3 vis, du scotch, un mouchoir et un peu de colle. Processeur 1.5Ghz (quand même 💪 eheh !)

Voilà le résultat chez moi :

$ python test.py
Testing Try
2.916746840000087
Testing If
3.9226159980003104
Le résultat du code qu’a cité Gabbro

Voilà voilà \o

PS: Précision, je n’ai rien lancé en parallèle biensûr sinon, j’atteins les 7s. La seule constante c’est que le if est plus long.

PPS: Avec python 2, c’est pire.

$ python2 test.py
Testing Try
3.78916597366
Testing If
5.22718191147

PPS: Le résultat s’inverse si je lance systématiquement une exceptions.

+0 -0

Et bien visiblement mon information n’était plus d’actualités concernant Python. Bon à savoir ':). Je referais quelques tests avec des exceptions nombreuses à gérer et des if avec de grosses conditions pour voir ce que ça donne quand j’aurais le temps. Je dormirais moins con :)

Bonjour les agrumes !

La bêta a été mise à jour et décante sa pulpe à l’adresse suivante :

J’ai juste fait quelques petites corrections dans la dernière partie et la conclusion. Est-ce que vous pensez que le tuto est proche d’un état de validation ?

Merci d’avance pour vos commentaires.

+0 -0

J’ai tenté de faire un petit script d’exemple pour l’usage des exceptions. Est-ce que vous auriez des critiques à faire dessus ? (Je dois dire que j’ai du mal à trouver des exemples de codes simples et courts qui mettent certaines applications en pratique…)

import requests


class CustomException(Exception):
    """Une exception propre à ce script."""
    pass


BASE_URL = "https://geo.api.gouv.fr/communes"
PARAMS = {
    "codePostal": None,
    "fields": "nom,surface,codesPostaux,codeDepartement,departement,population",
    "format": "json",
    "geometry": "centre"
}


def main(raise_exception=False):
    postal_code = input("Saisissez un code postal : ")
    try:
        PARAMS["codePostal"] = int(postal_code)
    except ValueError as e:
        print("Erreur : Le code postal doit être un nombre.")
        if raise_exception:
            raise CustomException from e
        exit(1)
    
    try:
        r = requests.get(BASE_URL, params=PARAMS)
    except requests.RequestException as e:
        print("Erreur : Impossible d'accéder à l'API.")
        if raise_exception:
            raise CustomException from e
        exit(1)
    try:
        r.raise_for_status()
    except requests.HTTPError as e:
        print("Erreur : Erreur {} lors de la requête.".format(e.status_code))
        if raise_exception:
            raise CustomException from e
        exit(1)
    
    try:
        results = r.json()
    except ValueError as e:
        print("Erreur : La réponse de l'API n'est pas un JSON valide.")
        if raise_exception:
            raise CustomException from e
        exit(1)
    
    for result in results:
        try:
            name = result["nom"]
            postal_codes = result["codesPostaux"]
            population = result["population"]
            surface = result["surface"]
        except KeyError:
            print("Erreur : Données manquantes !")
            if raise_exception:
                raise CustomException from e
            exit(1)
        try:
            population_density = population / (surface / 1000)
        except DivisionByZero:
            print("Erreur : La commune a une surface de zéro mètres carrés.")
            if raise_exception:
                raise CustomException from e
            exit(1)
        print("Nom : {}".format(name))
        print("Codes postaux : {}".format(', '.join(postal_codes)))
        print("Densité : {} hab/km²".format(population_density))
    else:
        print("Aucun résultat !")


if __name__ == "__main__":
    main()
+0 -0

Pas énormément de choses à redire, juste quelques points :

  • Je trouve bizarre de modifier PARAMS depuis la fonction main, la convention veut plutôt que les noms en capitales soient des constantes.
  • Des f-strings (f'Nom: {name}') seraient plus claires que des format.
  • Dans un vrai script j’utiliserais plutôt des logger.error à la place des print, pour un exemple je dirais que ça passe.
  • Idem, l’exemple cherche plutôt à montrer les try/except, mais je les trouve bien encombrant. Dans un programme réel j’aurais tendance à chercher du côté des blocs with pour essayer de factoriser les raise_exception.

D’accord, merci bien !

Du coup, j’ai essayé de refactoriser un peu pour en faire quelque chose de plus "réutilisable", ça donne ça. :)

import requests


class CustomException(Exception):
    """Une exception propre à ce script."""
    pass


BASE_URL = "https://geo.api.gouv.fr/communes"


def main(postal_code):
    if (
        not isinstance(postal_code, str) or
        not postal_code.isdigit() or
        not len(postal_code) == 5
    ):
        # On pourrait aussi faire "raise CustomException("...") from ValueError.
        raise CustomException("Le code postal doit être une string de 5 chiffres.")
    
    params = {
        "codePostal": postal_code,
        "fields": "nom,surface,codesPostaux,population",
        "format": "json",
        "geometry": "centre"
    }
    
    try:
        r = requests.get(BASE_URL, params=params)
        r.raise_for_status()
    except requests.RequestException as e:
        raise CustomException("L'API n'est pas accessible.") from e
    except requests.HTTPError as e:
        # Affiche "Erreur 500 lors de la requête." en cas d'erreur 500 par exemple.
        raise CustomException(f"Erreur {e.status_code} lors de la requête.") from e
    
    try:
        results = r.json()
    except ValueError as e:
        raise CustomException("La réponse de l'API n'est pas un JSON valide.") from e
    
    for result in results:
        try:
            name = result["nom"]
            postal_codes = result["codesPostaux"]
            population = result["population"]
            surface = result["surface"]
        except KeyError as e:
            raise CustomException("Données manquantes dans l'API !") from e
        try:
            population_density = population / (surface / 1000)
        except DivisionByZero:
            raise CustomException(
                "La commune a une surface de zéro mètres carrés."
            ) from e
        print(f"Nom : {name}")
        print("Codes postaux : {}".format(', '.join(postal_codes)))
        # Arrondis la densité à deux chiffres après la virgule.
        print(f"Densité : {population_density:.2f} hab/km²")
    else:
        print("Aucun résultat !")


if __name__ == "__main__":
    postal_code = input("Code postal : ")
    main(postal_code)
+0 -0

J’aime beaucoup ton nouveau code !

En regardant un peu plus attentivement ton code et en l’exécutant.

    except requests.HTTPError as e:
        print("Erreur : Erreur {} lors de la requête.".format(e.response.status_code)) 
        if raise_exception:
            raise CustomException from e
        exit(1)

C’est l’élément e.response qui à le champs status_code.

        except ZeroDivisionError:
            print("Erreur : La commune a une surface de zéro mètres carrés.")
            if raise_exception:
                raise CustomException from e
            exit(1)

DivisonByZero vs ZeroDivisionError

La clause else du for affiche systématiquement Pas de résultat même quand il y en a eu un.

+0 -0

Oups, c’est corrigé. Merci ! :)

import requests


class CustomException(Exception):
    """Une exception propre à ce script."""
    pass


BASE_URL = "https://geo.api.gouv.fr/communes"


def main(postal_code):
    if (
        not isinstance(postal_code, str) or
        not postal_code.isdigit() or
        not len(postal_code) == 5
    ):
        # On pourrait aussi faire "raise CustomException("...") from ValueError.
        raise CustomException("Le code postal doit être une string de 5 chiffres.")
    
    params = {
        "codePostal": postal_code,
        "fields": "nom,surface,codesPostaux,population",
        "format": "json",
        "geometry": "centre"
    }
    
    try:
        r = requests.get(BASE_URL, params=params)
        r.raise_for_status()
    except requests.RequestException as e:
        raise CustomException("L'API n'est pas accessible.") from e
    except requests.HTTPError as e:
        # Affiche "Erreur 500 lors de la requête." en cas d'erreur 500 par exemple.
        raise CustomException(f"Erreur {e.response.status_code} lors de la requête.") from e
    
    try:
        results = r.json()
    except ValueError as e:
        raise CustomException("La réponse de l'API n'est pas un JSON valide.") from e
    
    for result in results:
        try:
            name = result["nom"]
            postal_codes = result["codesPostaux"]
            population = result["population"]
            surface = result["surface"]
        except KeyError as e:
            raise CustomException("Données manquantes dans l'API !") from e
        try:
            population_density = population / (surface / 1000)
        except ZeroDivisionError:
            raise CustomException(
                "La commune a une surface de zéro mètres carrés."
            ) from e
        print(f"Nom : {name}")
        print("Codes postaux : {}".format(', '.join(postal_codes)))
        # Arrondis la densité à deux chiffres après la virgule.
        print(f"Densité : {population_density:.2f} hab/km²")
    if not results:
        print("Aucun résultat !")


if __name__ == "__main__":
    postal_code = input("Code postal : ")
    main(postal_code)
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte