Faire marcher un class decorator Python avec self

Pour un projet Python, je dois benchmarker plusieurs éléments et une de ces mesures est de chronométrer le temps mis pour avoir une réponse. Dans mon code, cela se traduit par chronométrer combien de temps se passe entre deux appels de deux méthodes différentes d’un même objet. Apprenant le Python en même temps, je me suis dit qu’une façon élégante de le faire serait de faire un class decorator.

Je ne sais pas si vous savez ce qu’est et comment fonctionne un class decorator, si non je vous conseille le chapitre 38.4 « Coding Class Decorators » du livre Learning Python par Mark Lutz et si vous ne savez même pas ce qu’est un decorator, honte à vous è_é! Ahah, non, aucune honte bien sur, dans ce cas lisez simplement le chaptire 38.2.

En bref, un decorator permet de remplacer une fonction (ou une classe) par une autre en écrivant simplement l’annotation @nomDuDecorator avant cette fonction/classe. Dans ma situation, en ajoutant l’annotation @TwoMethodsTimer(« requestMedia », « startPlayback ») juste avant la déclaration de ma classe, je vais récolter des statistiques sur le temps que met un média à commencer sa lecture après avoir été demandé. Voila une première version du decorator faite après avoir lu le chapitre 38.4 en plus de quelques blogs et fils sur Stack Overflow:

Pour l’utiliser sur n’importe quelle classe, voila un exemple:

Dans cet exemple, on mesure combien de temps s’écoule entre l’appel de methodA e methodB, ce qui sera environ 2 secondes. Ça marche, on est content, jusqu’à essayer quelque chose de nouveau:

On peut observer ici un appel interne à methodB (self.methodB()) et que cet appel ne déclenche pas stopTimer! Après enquête, il s’avère que l’appel de méthodes sur self, donc à l’intérieur d’un objet, ne fait pas appel à __getattr__ pour récupérer la méthode. J’ai donc essayé d’intercepter l’appel dans __getattribute__ mais cela s’avère difficile et quand je ne finis pas par des appels récursifs infinis, impossible de voir passer la méthode qui m’intéresse et donc de déclencher mon timer.

Pour résoudre ce problème, à la place de déterminer quelle méthode a été appelée en interceptant __getattr__, je modifie les attributs de l’objet juste après sa création. Voila le code:

Juste après l’instanciation de l’objet, on modifie l’association des deux méthodes qui nous intéressent pour que ces méthodes « pointent » vers nos propres méthodes. On stock les anciennes méthodes quelque part pour pouvoir s’en resservir par la suite. Cette astuce permet le fonctionnement du decorator à la fois sur les appels externes et internes.