IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez Python Discussion :

Vérification du type des arguments d'une fonction ou d'une méthode par décorateur


Sujet :

Contribuez Python

  1. #1
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut Vérification du type des arguments d'une fonction ou d'une méthode par décorateur
    Bonjour,

    Mon problème initial était de fournir à une calculatrice des fonctions de calcul, mais au cas où l'utilisateur fournissait de mauvaises données, par exemple une chaîne au lieu d'un nombre entier, je voulais que chaque fonction concernée renvoie un message d'erreur explicite pour permettre une correction facile. Il faut en effet absolument éviter en cas d'erreur de données:
    - un message d'erreur tellement général qu'on ne sait pas quoi en faire
    - pire: un crash du programme sans aucun message
    - encore pire: un résultat faux sans aucune mention de l'erreur de données

    Mais avant, je devais coder plein de tests "if isinstance(...) raise..." au début de chaque fonction, et ça me semblait pénible (une sorte de pollution du code...). J'ai donc cherché une solution plus pratique.

    J'ai donc fabriqué un décorateur sous forme d'une classe avec arguments, et dont les arguments sont les obligations de type portant sur les arguments de la fonction décorée.

    Par exemple, pour une fonction qui calcule les mensualités d'un crédit (le code complet est plus loin) avec C=capital emprunté, IA=intérêt annuel en %, N=nombre de mois de remboursement

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    @veriftypes(C=(int,), IA=(int, float), N=(int,))
    def menscredit(C, IA, N=12):
        ...
        ...
    Voilà le décorateur en version Python 3 (chez moi: v3.7). Je l'ai mis sous forme d'une classe "veriftypes" dans un fichier "veriftypes.py" à importer dans les programmes développés:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    # -*- coding: utf-8 -*-
    # Python 3.7
     
    # le décorateur "wraps" permettra à la fonction ou la méthode décorée de 
    # conserver son nom ("__name__") et son docstring ("__doc__"):
    from functools import wraps
     
    ##############################################################################
    class veriftypes:
        """Décorateur pour vérification des types des arguments d'une fonction ou 
           d'une méthode décorée
           (ne fonctionne pas avec des arguments comme *args ou **kwargs)
        """
     
        #=========================================================================
        def __init__(self, **deckwargs):
            """initialisation du décorateur
            """
            # dictionnaire des "variable:type(s)" pour les arguments à tester
            self.deckwargs = deckwargs
     
        #=========================================================================
        def __call__(self, fonc): 
     
            #---------------------------------------------------------------------
            @wraps(fonc)
            def appelfonc(*args, **kwargs):
                """méthode exécutée à chaque appel de la fonction décorée
                """
                # dico de toutes les "variable:valeur" passées à la fonction décorée
                dicvars = dict(zip(lvars[:len(args)],args))
                dicvars.update(kwargs)
                for v in dvars_def:
                    if v not in dicvars:
                        # ajout des variables par défaut non données avec leur valeur
                        dicvars[v] = dvars_def[v] 
     
                # vérif. des types pour chaque var. par défaut passée au décorateur
                for decvar in self.deckwargs:
     
                    # vérif. que l'argument du décorateur existe dans les arguments de la fonction décorée
                    if decvar not in lvars:
                        raise ValueError ('''Erreur veriftypes sur "{}": la variable "{}" n'existe pas'''.format(nomfonc, decvar))
     
                    # vérification de type(s) pour l'argument passé au décorateur 
                    if decvar in dicvars:
                        # valeur passée à la fonction décorée pour l'argument
                        val = dicvars[decvar]
                        # type(s) déclaré(s) au décorateur 
                        typ = self.deckwargs[decvar]
                        # vérification 
                        if not isinstance(val, typ):
                            raise TypeError ('Erreur appel "{}": mauvais type pour "{}"'.format(nomfonc, decvar))
     
                # appel de fonc avec tous ses arguments, et retour du résultat
                return fonc(*args, **kwargs)
     
            #---------------------------------------------------------------------
            # nom de la fonction décorée pour les messages suite à exception
            nomfonc = fonc.__name__
     
            # liste de toutes les variables d'appel de la fonction décorée
            lvars = list(fonc.__code__.co_varnames[:fonc.__code__.co_argcount])
     
            # liste des valeurs par defaut de la fonction décorée
            args_def = fonc.__defaults__
            if args_def == None:
                args_def = () # aucune variable par défaut n'est déclarée
            # liste des variables par défaut
            vars_def = lvars[len(lvars)-len(args_def):]
            # création du dictionnaire des "variable:valeur" par défaut
            dvars_def  = dict(zip(vars_def, args_def))
     
            # retourne l'adresse de la méthode à appeler à chaque appel de fonc
            return appelfonc
    En fait, la complexité vient du fait qu'il faut retrouver tous les arguments de la définition de la fonction, et leur affecter toutes les valeurs citées ou non dans l'appel. Et il y a des cas de figures complexes.

    Par exemple, j'ai la définition: "def menscredit(C, IA, N=12):" Je peux appeler:
    - menscredit(1000, 5.25) => N sera 12 par défaut
    - menscredit(1000, 5.25, 24) => N sera 24 mais ici, N=24 est cité comme un argument par position
    - menscredit(1000, IA=5.25, N=24) => IA sera 5.25, mais IA était un argument par position et il est appelé comme un argument par défaut
    - etc...

    Et, bien sûr, pour que la vérification du type d'un argument soit faite, il faut que le nom de cet argument soit cité dans la définition de la fonction! Cela exclut que ça puisse marcher avec une fonction comme "def test(*args, **kwargs):"

    Voilà un exemple de fonction décorée:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # -*- coding: utf-8 -*-
    # Python 3.7
     
    """
    Exemple de vérification de types d'arguments d'une fonction par décorateur
    """
     
    from veriftypes import veriftypes
     
    ###############################################################################
    @veriftypes(C=(int,), IA=(int, float), N=(int,))
    def menscredit(C, IA, N=12):
        """Retoune la mensualité M d'un prêt C à IA% d'intérêt par an, à 
           rembourser en N mois
        """
        # si le capital est nul, il n'y a rien à rembourser
        if C==0:
            return 0
        # si l'intérêt est nul, la mensualité ne dépend plus que de C et N
        if IA==0:
            return C/N
        # calcul de la mensualité dans les autres cas
        I = IA/1200
        return C*I*(1-1/(1-(1+I)**N))
    Et voilà ce qui se passe lors de l'appel de cette fonction:

    - menscredit(1000, 5.25, 24) => 43.98
    - menscredit(1000, 5.25, "24") => Erreur appel "menscredit": mauvais type pour "N"
    - menscredit(1000.0, 5.25, 24) => Erreur appel "menscredit": mauvais type pour "C"
    - etc...

    Voilà comment fonctionne ce décorateur:

    - lors de la 1ère exécution du code, le décorateur se configure pour la fonction qu'il décore. La méthode __init__ sauvegarde les arguments passés au décorateur, puis la méthode __call__ cherche toutes les caractéristiques des arguments passés dans la définition de la fonction (lignes 59 à 75 de veriftypes.py), et retourne l'adresse de la méthode appelfonc.

    - lors des exécutions suivantes, seule la méthode appelfonc sera appelée à chaque appel de la fonction décorée, ce qui permettra de traiter la correspondance entre les arguments appelés et les arguments prévus à la définition de la fonction.

    A noter que ça marche aussi pour décorer la méthode d'une classe.

    A noter aussi que ça continue à marcher pour une version "standalone" après traitement par pyinstaller ou cx_freeze. Et c'est normal, parce qu'il ne s'agit pas d'une compilation en code natif comme en C, mais d'une "encapsulation" de l'interpréteur + les bibliothèques et modules nécessaires.



    On peut faire encore mieux: ajouter des conditions sur la valeur des arguments passés! Le décorateur ci-dessus se complète par quelques lignes de code pour faire ça. Bien sûr, ce décorateur fait aussi bien que le précédent la vérification des types!

    Par exemple, toujours pour ma fonction de calcul de crédit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    @verifargs("C>=0", "IA>=0.0", "N>0", C=(int,), IA=(int, float), N=(int,))
    def menscredit(C, IA, N=12):
    Pour faire ça, il a seulement fallu:
    - ajouter des arguments par position au décorateur
    - vérifier par eval la satisfaction de chacune des conditions, avec la valeur transmise lors de l'appel de la fonction.

    Voilà ce nouveau décorateur complété. La classe est nommée maintenant "verifargs" et se trouve dans un fichier "verifargs.py qu'il faudra importer:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    # -*- coding: utf-8 -*-
    # Python 3.7
     
    # le décorateur "wraps" permettra à la fonction ou la méthode décorée de 
    # conserver son nom ("__name__") et son docstring ("__doc__"):
    from functools import wraps
     
    ##############################################################################
    class verifargs:
        """Décorateur pour vérification des types des arguments d'une fonction ou 
           d'une méthode décorée
           (ne fonctionne pas avec des arguments comme *args ou **kwargs)
        """
     
        #=========================================================================
        def __init__(self, *decargs, **deckwargs):
            """initialisation du décorateur
            """
            # arg. par position: liste des conditions à remplir
            self.decargs = decargs
            # dictionnaire des "variable:type(s)" pour les arguments à tester
            self.deckwargs = deckwargs
     
        #=========================================================================
        def __call__(self, fonc): 
     
            #---------------------------------------------------------------------
            @wraps(fonc)
            def appelfonc(*args, **kwargs):
                """méthode exécutée à chaque appel de la fonction décorée
                """
                # dico de toutes les "variable:valeur" passées à la fonction décorée
                dicvars = dict(zip(lvars[:len(args)],args))
                dicvars.update(kwargs)
                for v in dvars_def:
                    if v not in dicvars:
                        # ajout des variables par défaut non données avec leur valeur
                        dicvars[v] = dvars_def[v] 
     
                # vérif. des types pour chaque var. par défaut passée au décorateur
                for decvar in self.deckwargs:
     
                    # vérif. que l'argument du décorateur existe dans les arguments de la fonction décorée
                    if decvar not in lvars:
                        raise ValueError ('''Erreur veriftypes sur "{}": la variable "{}" n'existe pas'''.format(nomfonc, decvar))
     
                    # vérification de type(s) pour l'argument passé au décorateur 
                    if decvar in dicvars:
                        # valeur passée à la fonction décorée pour l'argument
                        val = dicvars[decvar]
                        # type(s) déclaré(s) au décorateur 
                        typ = self.deckwargs[decvar]
                        # vérification 
                        if not isinstance(val, typ):
                            raise TypeError ('Erreur appel "{}": mauvais type pour "{}"'.format(nomfonc, decvar))
     
                # vérif. des conditions à remplir sur les arguments par position du décorateur
                for decarg in self.decargs:
                    if not eval(decarg, dicvars):
                        raise ValueError ('Erreur appel "{}": échec condition "{}"'.format(nomfonc, decarg))
     
                # appel de fonc avec tous ses arguments, et retour du résultat
                return fonc(*args, **kwargs)
     
            #---------------------------------------------------------------------
            # nom de la fonction décorée pour les messages suite à exception
            nomfonc = fonc.__name__
     
            # liste de toutes les variables d'appel de la fonction décorée
            lvars = list(fonc.__code__.co_varnames[:fonc.__code__.co_argcount])
     
            # liste des valeurs par defaut de la fonction décorée
            args_def = fonc.__defaults__
            if args_def == None:
                args_def = () # aucune variable par défaut n'est déclarée
            # liste des variables par défaut
            vars_def = lvars[len(lvars)-len(args_def):]
            # création du dictionnaire des "variable:valeur" par défaut
            dvars_def  = dict(zip(vars_def, args_def))
     
            # retourne l'adresse de la méthode à appeler à chaque appel de fonc
            return appelfonc
    Maintenant, si on appelle "menscredit(1000, 5.25, -24)", donc avec un nombre de mois négatif, cela donne: "Erreur appel "menscredit": échec condition "N>0""

    A noter que, comme eval utilise le dictionnaire des arguments de la fonction, chacune des conditions peut être plus complexe, et concerner plusieurs arguments. Par exemple, une condition "C>=0 and C>N" est parfaitement valable (même si elle est absurde dans ce cas).

    A noter aussi que si "eval" est déconseillée en général pour des raisons de sécurité (et à juste titre), ce n'est pas un problème ici puisque les chaînes de caractères à évaluer sont uniquement dans le code du programme.

    Ces vérifications de type ne sont pas à utiliser tout le temps pour chaque fonction ou méthode, mais elles sont particulièrement utiles pendant le développement, ainsi que pendant l'utilisation, dès lors qu'on risque d'introduire de mauvaises données. En fait, on l'utilise dès qu'on trouverait nécessaire d'ajouter des tests "if...raise..." au début des fonctions concernées.

    N'hésitez pas à me signaler les éventuels problèmes que vous rencontrez dans l'utilisation!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  2. #2
    Membre confirmé

    Homme Profil pro
    Bidouilleur
    Inscrit en
    Avril 2016
    Messages
    721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Bidouilleur

    Informations forums :
    Inscription : Avril 2016
    Messages : 721
    Points : 503
    Points
    503
    Billets dans le blog
    1
    Par défaut
    Salut.

    Merci du partage.
    C'est &galement fonctionnel avec la 3.4.

    Mais c'est quand même vachement lent par rapport au temps d'exécution normal d'une fonction, de l'ordre du × 20, donc j'imagine que c'est pas le genre de décorateur à mettre sur toutes fonctions ou méthodes, comme par ex. une fonction de callback. Pour ma part je ne me vois pas utiliser ce genre de choses, peut-être qu'il faut réserver ce genre de décorateur sur des valeurs venant de données inconnues, saisies utilisateurs, données de fichier ?
    Par curiosité, dans quels cas utilises-tu ce décorateur ?


    Par contre je suis vraiment surpris des temps d'exécutions, j'avais jamais fait de comparatif, mais en python 3.4.2, c'est beaucoup plus rapide qu'en 3.4.8 et 3.6 environ 4 pour 5. En 3.7, je ne sais pas, je ne l'ai pas encore installé.
    Le temps ronge l'amour comme l'acide.

  3. #3
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour bistouille,

    Merci de tes infos!

    Citation Envoyé par bistouille Voir le message
    Par curiosité, dans quels cas utilises-tu ce décorateur ?
    C'est ce que j'ai mis à la fin de mon message précédent:

    Ces vérifications de type ne sont pas à utiliser tout le temps pour chaque fonction ou méthode, mais elles sont particulièrement utiles pendant le développement, ainsi que pendant l'utilisation, dès lors qu'on risque d'introduire de mauvaises données. En fait, on l'utilise dès qu'on trouverait nécessaire d'ajouter des tests "if...raise..." au début des fonctions concernées.
    Il s'agit donc d'une alternative plus pratique aux multiples "if condition... raise...".

    Citation Envoyé par bistouille Voir le message
    Par contre je suis vraiment surpris des temps d'exécutions, j'avais jamais fait de comparatif, mais en python 3.4.2, c'est beaucoup plus rapide qu'en 3.4.8 et 3.6 environ 4 pour 5. En 3.7, je ne sais pas, je ne l'ai pas encore installé.
    Je suis surpris, mais je vais regarder ça! Merci!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  4. #4
    Membre confirmé

    Homme Profil pro
    Bidouilleur
    Inscrit en
    Avril 2016
    Messages
    721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Bidouilleur

    Informations forums :
    Inscription : Avril 2016
    Messages : 721
    Points : 503
    Points
    503
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    C'est ce que j'ai mis à la fin de mon message précédent
    Ah oui, désolé, j'étais tellement parti à faire divers tests de ton script que j'en ai oublié de lire la fin de ton message

    Tiens, d'ailleurs ça fonctionne aussi en python 2.7 et c'est encore plus rapide

    Cela varie autour de.

    splash@scoubidou:~$ py2 verif_types.py
    3.76152396202
    splash@scoubidou:~$ py342 verif_types.py
    4.241486072540283
    splash@scoubidou:~$ py348 verif_types.py
    5.173945665359497
    splash@scoubidou:~$ py36 verif_types.py
    5.499176025390625
    Pour 1 million d'itérations sur un portable pas très performant.
    Le temps ronge l'amour comme l'acide.

  5. #5
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Question rapidité, je reconnais que la vérification des arguments par décorateur, en contrepartie de sa simplicité d'écriture, est moins rapide que la même vérification avec les "if condition raise..." dans la fonction.
    Cependant, on n'ajoute tout de même pas beaucoup de temps. Par exemple, avec l'exemple suivant, chaque appel de cette fonction n'ajoute que moins d'1/1000 seconde à cause du décorateur! Mais il est vrai que ça peut compter dans certains cas...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    @verifargs("a>0", "x>0", x=int, y=(int, float))
    def test(a, b, c, x=1, y=2):
        return None

    A part ça, j'ai revu le décorateur pour le rendre compatible avec les "*args" et *kwargs", et j'ai fait quelques améliorations de code.

    Comme exemple d'amélioration, le décorateur renvoie maintenant sous forme d'exception la liste de toutes les conditions non satisfaites qu'il a rencontrées, et pas seulement la première.

    Autre exemple d'amélioration: ajout d'une classe d'exception héritant de "Exception" et permettant de traiter à part les exceptions résultant de conditions non satisfaites. Par exemple comme suit:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    try:
        res = test(...)
    except VerifargsError as erreurs:
        for erreur in erreurs.args[0]:
            print(erreur)
    except Exception as erreur:
            print(erreur.args[0])
    Voilà la dernière version à mettre dans un fichier "verifargs.py" qui permettra une importation comme "from verifargs import verifargs":

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    # -*- coding: utf-8 -*-
    # Python 2.7 et 3.7
     
    """
    Décorateur pour vérifier les valeurs et les types des arguments d'une 
    fonction ou d'une méthode
     
    A importer comme: 
    from verifargs import verifargs
     
    Exemple:
    @verifargs("a>0", "x>0", x=int, y=(int, float))
    def test(a, b, c, x=1, y=2):
        ...
    Appel:
    test(5, 6, 7, y=9.5)
     
     
    Compatible avec *args (sauf Python 2) et **kwargs:
    Exemple:
    @verifargs("a>0", "x>0", x=int, y=(int, float), z=str)
    def test(a, b, c, *args, x=1, y=2, **kwargs):
        ...
    Appel (le 8 sera reçu dans args, et z dans kwargs):
    test(5, 6, 7, 8, y=9.5, z="toto")
     
    Le décorateur "wraps" permettra à la fonction ou la méthode décorée de 
    conserver son nom ("__name__") et son docstring ("__doc__"):
    """
     
    from functools import wraps
     
    ##############################################################################
    class VerifargsError(Exception):
        """classe pour les exceptions spécifiques aux vérifications de verifargs
        """
        pass
     
    ##############################################################################
    class verifargs:
        """Décorateur pour vérifier les valeurs et les types des arguments d'une 
           fonction ou d'une méthode
        """
     
        #=========================================================================
        def __init__(self, *decargs, **deckwargs):
            """initialise le décorateur
            """
            # stocke les conditions de valeurs sous forme d'arguments par position
            self.decargs = decargs
            # stocke les conditions de types sour forme d'arguments par défaut
            self.deckwargs = deckwargs
     
        #=========================================================================
        def __call__(self, fonc): 
     
            #---------------------------------------------------------------------
            @wraps(fonc)
            def appelfonc(*args, **kwargs):
                """méthode exécutée à chaque appel de la fonction décorée
                """
                # crée la liste qui portera les éventuelles erreurs rencontrées
                erreurs = []
     
                # crée le dico de tous les arguments passés à la fonction décorée
                dicvars = dict(zip(lvars_pos[:len(args)],args))# arg par position
                dicvars.update(kwargs) # ajout arg par défaut passés
                for v in dvars_def:
                    if v not in dicvars:
                        # ajout des arg par défaut non passés
                        dicvars[v] = dvars_def[v]
     
                # vérifie les conditions sur les types
                for decvar in self.deckwargs:
                    if decvar in dicvars:
                        if not isinstance(dicvars[decvar], self.deckwargs[decvar]):
                            # erreur de type
                            erreurs.append('Erreur appel "{}". Mauvais type pour "{}"'.format(nomfonc, decvar))
                    else:
                        # erreur: un argument passé au décorateur n'existe pas dans le fonction
                        erreurs.append('''Erreur verifargs sur "{}". La variable "{}" n'existe pas'''.format(nomfonc, decvar))
     
                # vérifie les conditions sur les valeurs
                for decarg in self.decargs:
                    if not eval(decarg, dicvars):
                        # erreur: une valeur passée à la fonction n'est pas correcte
                        erreurs.append('Erreur appel "{}". Echec condition "{}"'.format(nomfonc, decarg))
     
                # si erreurs, génère une exception avec la liste des conditions non satisfaites
                if erreurs:
                    raise VerifargsError(erreurs) 
     
                # appelle la fonction avec tous ses arguments, et retour du résultat
                return fonc(*args, **kwargs)
     
            #---------------------------------------------------------------------
            # nom de la fonction décorée pour les messages suite à exception
            nomfonc = fonc.__name__
     
            # liste des arguments par position de la fonction décorée
            lvars_pos = list(fonc.__code__.co_varnames[:fonc.__code__.co_argcount])
            # liste des arguments par defaut de la fonction décorée
            args_def = fonc.__defaults__
            if args_def is None:
                args_def = () # aucune variable par défaut n'est déclarée
            vars_def = lvars_pos[len(lvars_pos)-len(args_def):]
            # création du dictionnaire des arguments de la fonction décorée
            dvars_def  = dict(zip(vars_def, args_def))
     
            if dvars_def=={}:
                dvars_def  = fonc.__kwdefaults__ # il a été utilisé un "*args"
            if dvars_def is None:
                dvars_def = {} # cas de (*args, **kwargs)
     
            # retourne l'adresse de la méthode à appeler à chaque appel de fonc
            return appelfonc
    Pour la compatibilité avec *args et **kwargs, il y a des subtilités.

    Avec une définition de fonction comme:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    def test(a, b, c, *args, x=1, y=2, **kwargs):
    Sous Python 2, il y aura une "erreur de syntaxe" pour *args.

    Mais sous Python 3, pas de problème:

    - On peut passer une 4ème valeur avant le "x=1", et elle sera récupérée par args dans la fonction. Mais, bien sûr, comme cette valeur ne sera portée par aucun nom d'argument, il ne sera pas possible d'avoir une condition dans le décorateur.

    - Par contre, on peut passer une valeur avec un nom d'argument en dernière position (z="toto"). Elle sera reçue par kwargs dans la fonction, et comme la valeur a un nom d'argument, on peut avoir une condition dans le décorateur (z=str). Mais s'il y a cette condition dans le décorateur, il faut impérativement mentionner l'argument z dans l'appel de la fonction! Sinon, le décorateur signalera que le nom d'argument z qu'il a à vérifier n'existe pas dans la fonction (c'est logique).

    Par exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    @verifargs("a>0", "x>0", x=int, y=(int, float), z=str)
    def test(a, b, c, *args, x=1, y=2, **kwargs):
    Et un appel comme:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    test(5, 6, 7, 8, x=2, z="toto")
    Et un grand merci à Sve@r pour ses suggestions!
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

Discussions similaires

  1. Forcer le type des paramètres d'une fonction
    Par Teocali dans le forum Général Python
    Réponses: 9
    Dernier message: 14/08/2010, 20h17
  2. Réponses: 5
    Dernier message: 14/07/2010, 10h34
  3. Réponses: 9
    Dernier message: 10/05/2010, 21h35
  4. Nom des arguments d'une fonction
    Par jo_dalton dans le forum Général JavaScript
    Réponses: 9
    Dernier message: 26/09/2008, 13h25
  5. Definition des arguments d'une fonction
    Par Zimzimut dans le forum VB 6 et antérieur
    Réponses: 7
    Dernier message: 24/05/2007, 18h44

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo