Developpez.com - Rubrique Perl

Le Club des Développeurs et IT Pro

Comment utiliser des décorateurs en Perl ?

Un tutoriel de Laurent Rosenfeld

Le 2017-11-29 19:48:11, par Lolo78, Rédacteur/Modérateur
Bonsoir,

j'ai le plaisir d'annoncer la publication sur ce site d'un nouvel article: Comment utiliser des décorateurs en Perl - Un tutoriel pour changer le comportement d'une fonction sans en modifier le code source.

Un « décorateur » est une fonction qui permet de modifier le comportement d'une autre fonction sans toucher au code de cette autre fonction. Cela permet notamment d'ajouter des traces d'exécution (par exemple à des fins de débogage) ou d'accélérer le fonctionnement d'une fonction en ajoutant un cache.

Perl n'a pas de fonctionnalité spécifique pour utiliser des décorateurs, mais cet article montre qu'il est assez facile de créer cette fonctionnalité. Au passage, l'article illustre l'utilisation de certaines fonctions dites « avancées » de Perl, lesquelles sont en fait moins mystérieuses qu'il n'y paraît de prime abord.

Bonne lecture et bonne soirée à tous.
  Discussion forum
13 commentaires
  • djibril
    Responsable Perl et Outils
    Bonsoir,

    Encore un très bon tutoriel sur Perl qui nous apprend de nouvelles techniques de programmation avancées.

  • dca_marshall
    Nouveau membre du Club
    Bonjour,

    Excellent 👍

    Jusque la, j'utilisais un Wrapper pour se charger des compteurs d'appel, des code-retours et des compteurs internes à la fonction (avec PadWalker).

    Je vais donc revoir ma copie...

    Merci pour ce cours et sa modernité.
  • ptonnerre
    Membre habitué
    La lecture du début de ce tutoriel, notamment les premiers exemples d'utilisation de cache, m'a fait réfléchir à un de mes scripts.
    J'ai pu ainsi optimiser un traitement mensuel qui dure en moyenne 1h27 à 52 minutes.
    Pas mal non !

    Encore un grand merci à Laurent pour ses tutoriels
  • ptonnerre
    Membre habitué
    Un nouveau tuto que je vais lire dès que possible.
  • ptonnerre
    Membre habitué
    Bonjour,

    j'essaye de mettre en oeuvre les décorateurs avec ce cas d'application simple : nos programmes écrivent dans un fichier de log qui sera poussé vers un serveur de logs. Les lignes du fichier log ont un format spécifique et les écritures sont gérées par des fonctions d'un module.

    Les fonctions gérant les lignes de type INFO et WARN sont similaires, à l'exception du préfixe de la ligne (OXRES ou OXINC).

    Version sans décorateurs :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    sub oxa_msg_info {
        my ( $msg, $flag ) = @_;
    
        my $tms = ( $flag ) ? utils_timestamp() . ' - '  : '';
        say 'OXRES: ' . $tms . $msg;
    
        return;
    }
    
    sub oxa_msg_warn {
        my ( $msg, $flag ) = @_;
    
        my $tms = ( $flag ) ? utils_timestamp() . ' - '  : '';
        say "OXINC: " . $tms . "$msg";
    
        return;
    }
    Version avec décorateurs :

    Cette version crée en début de module deux coderefs via l'appel de la fonction decorateur en lui passant en argument la référence à la fonction générique info_or_warn qui ne fait qu'une écriture et le préfixe associé.
    Nous avons donc à disposition les deux fonctions oxa_msg_info et oxa_msg_warn utilisant la fonction générique info_or_warn redécorée en fonction du besoin .

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
        no warnings 'redefine';
        *{xxx_utils::oxa_msg_info} = decorateur(\&info_or_warn, 'OXRES');
        *{xxx_utils::oxa_msg_warn} = decorateur(\&info_or_warn, 'OXINC');
    }
    
    sub info_or_warn {
        my ( $msg ) = @_;
        say $msg;
    }
    
    sub decorateur {
        my ( $coderef, $type_msg) = @_;
        return sub {
            my ( $msg, $flag ) = @_;
            my $tms = ( $flag ) ? utils_timestamp() . ' - '  : '';
            $coderef->($type_msg . ': ' . $tms . $msg);
        }
    }
    Cela fonctionne, et je me pose maintenant la question de la pertinence de ce cas et si oui, de son écriture.
    Commentaires/suggestions bienvenus.
  • Lolo78
    Rédacteur/Modérateur
    Envoyé par ptonnerre

    c'est historique dans le service, les fonctions oxa doivent toujours avoir le même nom (oxa_xxx et non pas $oxa_xxx) quel que soit le langage utilisé
    OK, dans ce cas, tu peux sans doute créer ta fonction directement dans la table des symboles sans pour autant avoir besoin d'utiliser de décorateur:
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    use strict;
    use warnings;
    use feature "say";
    
    sub create_oxa_message {
        my $ox_what = shift;
        return sub {
            my $msg = shift;
            say "$ox_what: TMS $msg";
        }
    }
    
    *::oxa_msg_info = create_oxa_message ("OXRES: ");
    *::oxa_msg_warn = create_oxa_message ("OXINC: ");
    
    oxa_msg_info("Information");
    oxa_msg_warn("Avertissement");
    Ce qui affiche:
    Code :
    1
    2
    3
    OXRES: : TMS Information
    OXINC: : TMS Avertissement
    Voire même ceci en bouclant sur les clefs et valeurs d'un hachage de paramétrage:

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    use strict;
    use warnings;
    use feature "say";
    
    sub create_oxa_message {
        my $ox_what = shift;
        return sub {
            my $msg = shift;
            say "$ox_what: TMS $msg";
        }
    }
    
    my %hash = (info => "OXRES: ", warn => "OXINC: ");
    {
        no strict "refs";
        *{"::oxa_msg_" . $_} = create_oxa_message ($hash{$_}) for keys %hash;
    }
    
    oxa_msg_info("Information");
    oxa_msg_warn("Avertissement");
    ce qui affiche la même chose que précédemment.

    La syntaxe devient un peu plus complexe et moins claire (voire quelque peu inélégante), mais ça peut être intéressant si tu as beaucoup de types de messages (en plus de info et warn).


    Hormis cela, est-ce que le codage des décorateurs te paraît correct ?
    A priori, oui (mais je n'ai pas testé, n'ayant pas de données me permettant de le faire), mais si tu as testé et dis que ça marche, alors oui, certainement. Sauf, bien sûr, que comme je viens de le dire ci-dessus, tu peux en l'occurrence utiliser les techniques que j'ai décrites dans le tutoriel (écriture dans la table des symboles) sans avoir besoin d'un décorateur.
  • Lolo78
    Rédacteur/Modérateur
    En y réfléchissant, ce que j'ai proposé dans mon dernier post n'est en définitive pas très différent de ce que tu as fait à l'origine dans le tien, même si ce n'est pas fait exactement de la même façon (ma version est quand même un peu plus simple, me semble-t-il).

    J'ai été un peu trompé par le nom (decorateur) de ta fonction, mais ce que tu fais dans ton code n'est pas tout-à-fait un décorateur dans la mesure où tu ne remplaces pas une fonction existante par une autre (du moins dans le sens que j'ai donné su mot dans le tutoriel), mais te contentes en fait de créer de nouvelles fonctions en utilisant une fonction tout juste créée (note que, du coup, tu n'as semble-t-il pas besoin du pragma no warnings 'redefine';, puisque tu ne redéfinis pas une fonction existante (à toi de vérifier avec ton code, mais en tous cas, je n'en ai bas besoin avec le mien). Cela dit, ton code utilise bien le même principe que les décorateurs décrits dans le tuto.

    C'est un usage qui peut être utile et auquel je n'avais pas vraiment pensé quand j'ai rédigé le tuto. J'y réfléchirai, mais peut-être que j'ajouterai un paragraphe sur ce type d'utilisation des techniques employées pour créer mes décorateurs. Merci à toi de me donner une piste d'enrichissement du tutoriel.
  • ptonnerre
    Membre habitué
    Je confirme que le pragma no warnings 'redefine' n'est pas nécessaire.

    Ta version qui écrit directement dans la table des symboles est effectivement plus simple et je vais la retenir, tout en gardant la mienne pour mémo, version qui mélangeait allègrement, il me semble, décorateurs et usine à fonctions

    Quoi qu'il en soit, ce tuto ouvre de nombreuses voies de réflexion et j'espère qu'il sera utile à beaucoup d'entre nous.
  • Lolo78
    Rédacteur/Modérateur
    Bonjour,

    Envoyé par Lolo78

    C'est un usage qui peut être utile et auquel je n'avais pas vraiment pensé quand j'ai rédigé le tuto. J'y réfléchirai, mais peut-être que j'ajouterai un paragraphe sur ce type d'utilisation des techniques employées pour créer mes décorateurs. Merci à toi de me donner une piste d'enrichissement du tutoriel.
    suite à la discussion avec ptonnerre sur ces utilisations de la table des symboles (hors décorateurs), j'ai ajouté une nouvelle section (§ 4.3) dans le tutoriel.

    Bonne lecture à tous et merci à ptonnerre.

    Bonne soirée,
    Laurent.
  • Lolo78
    Rédacteur/Modérateur
    Merci du compliment.

    Envoyé par ptonnerre
    La lecture du début de ce tutoriel, notamment les premiers exemples d'utilisation de cache, m'a fait réfléchir à un de mes scripts.
    J'ai pu ainsi optimiser un traitement mensuel qui dure en moyenne 1h27 à 52 minutes.
    Si ce n'est pas confidentiel et si ce n'est pas trop compliqué, il serait peut-être intéressant que tu publies un post illustrant cette optimisation. Ça pourrait rendre service à d'autres.