lundi 7 décembre 2009

Décorateur en Python (solution au typage non stricte)

Introduction - Le typage non strict
Bien que Python supporte très bien le modèle orienté objet, le typage des paramètres (dans l'appel des fonctions) n'est pas du type "strict".

Il est donc possible de passer une chaîne de caractère ou un objet quelconque à une fonction/méthode là ou le développeur a prévu un entier.

Bien de prônant l'utilisation et le renforcement du typage (stricte), je dois reconnaître que l'approche du  typage "non stricte" des paramètres et variable en Python permet une plus grande souplesse dans les développements.
C'est quelque-chose qu'il faut tester dans ce langage et ses structures pour en entre-apercevoir la portée. Cette souplesse est certainement l'un des éléments à l'origine de la popularité de Python. Même l'environement .Net s'y est mis en introduisant le mot clé "var".

Cependant, malgré cet avantage le typage non strict des paramètres reste quand même problématique car il prône l'erreur.

Solutions
Il existe plusieurs solutions pour éviter ces problèmes de typage.
  1. D'une façon générale, et bien que cela ne soit pas suffisant, une documentation préçise permet déjà au développeur consencieux d'utiliser correctement la fonction.
    Au moins, ce dernier n'aura pas à déviner/décoder le type à passer à la fonction.
  2. Utiliser un notation (préfix) identifiant le type de paramètre.
    Bien qu'également inssufisant, cette pratique est largement répandue chez les bons développeurs.
    C'est ausci que l'on retrouve un s pour string, ex: sVilleName; i pour integer, ex: iPostalCode; f pour float, eg: fSalary; ii pour interface, eg: iiPostalCodeXmlNode; et ainsi de suite. 
  3. Vérification systématique des  paramètres en tête de fonction (à l'aide de la fonction isinstance qui permet de tester le type du paramètre). 
  4. Finalement, il reste l'utilisation des décorateurs (voir ci-dessous).

Qu'est-ce qu'un décorateur
Un décorateur, est une fonction particulière qui permet de "décorer" une autre fonction. Pour ce faire, Python permet de déclarer une fonction décoratrice et met en place une syntaxe permettant de décorer les fonctions et méthodes avec un ou plusieurs décorateurs (fonctions décoratrices).

Un décorateur et son principe peut être grossièrement décrit avec la métaphore suivante.
C'est un peu comme les décorations de Noel sur un sapin. Pour atteindre les branches d'un sapin décoré, il faut préalablement débrancher les guirlances électriques, passer le barrage des décorations et éviter de casser les boules de verres.
En programmation c'est un peu pareil, pour atteindre la fonction à éxécuter (et l'exécutée), il faudra passer le barrage du décorateur (la fonction décoratrice).
La fonction décoratrive sera exécutée par Pyhton avant l'appel de la fonction décorée.

Vérification stricte des paramètres avec un décorateur
L'un des usages communs des décorateurs est la vérification des paramètres.
dans ce cadre d'utilisation, la fonction décoratrice vérifiera les conditions d'exécution (ou transformera les paramètres). En cas d'erreur, la fonction décoratrice remplira son rôle en retournant une erreur (la boule de verre qui casse :-) )

Exemple de mise en place de décorateur
Comprendre le fonctionnement interne d'un décorateur demande un peu de gymnastique intellectuel.
Vous trouverez plus bas quelques notes pouvant éventuellement facilite cette compréhension.

# Définition du décorateur
#
def only_int(func):
    """decorator pour vérifier que l'argument
est seulement un entier"""
    def _only_int( arg ):
        if not isinstance( arg, int ):
            raise TypeError("'%s' doit etre un entier" % str(arg))
        return func(arg)
    return _only_int

# definition de la function absmod3 
#   décorée avec la fonction only_int
@only_int
def absmod3(a):
    return abs(a)%3

absmod3( 100 )
# resultat: 1


absmod3( 12.45 )
# produit l'erreur suivante
#
Traceback (most recent call last):
  File "", line 1, in 
    absmod3( 12.45 )
  File "", line 6, in _only_int
    raise TypeError("'%s' doit etre un entier" % str(arg))
TypeError: '12.45' doit etre un entier 


Notez que la fonction de décoration only_int reçoit en référence la fonction a décorer (func).  La fonction à décorée (func) peut être appelée avec son unique argument (ici func(arg) ).

La fonction de décoration only_int retourne une référence vers la fonction de vérification _only_int (_only_int étant une nested/inner function du décorateur).

Pour résumer, le décorateur only_int est une fonction qui met en place une fonction d'interception sur un argument qui dans le cas des décorateur est une référence de fonction.
C'est dans cette technique que s'exprime toute la puissance du typage non-strict dy Python.


Au démarrage du programme (parsing du code source), Python place donc les fonctions d'interception (décorateurs) sur les appels des fonctions décorées.


En gros, en appelant la fonction absmod3 décorée avec only_int:
La fonction only_int (paramètre func=absmod3) installera la fonction de vérification _only_int comme intermédiare aux appels de absmod3. Au run-time, l'interpréteur appelera _only_int qui lui même rapellera la function référencée par func (donc absmod3). Au passage, _only_int vérifiera le paramètre qui sera passé à absmod3.

Aucun commentaire: