Qu'est-ce que le patch de singe?

549

J'essaie de comprendre, qu'est-ce qu'un patch de singe ou un patch de singe?

Est-ce quelque chose comme des méthodes / opérateurs surchargeant ou déléguant?

At-il quelque chose de commun avec ces choses?

Sergei Basharov
la source
Je pense que la définition de google est utile et la plus générale:Monkey patching is a technique to add, modify, or suppress the default behavior of a piece of code at runtime without changing its original source code.
Charlie Parker

Réponses:

522

Non, ce n'est comme aucune de ces choses. Il s'agit simplement du remplacement dynamique d'attributs lors de l'exécution.

Par exemple, considérons une classe qui a une méthode get_data. Cette méthode effectue une recherche externe (sur une base de données ou une API Web, par exemple), et diverses autres méthodes de la classe l'appellent. Cependant, dans un test unitaire, vous ne voulez pas dépendre de la source de données externe - vous remplacez donc dynamiquement la get_dataméthode par un stub qui renvoie des données fixes.

Parce que les classes Python sont mutables et que les méthodes ne sont que des attributs de la classe, vous pouvez le faire autant que vous le souhaitez - et, en fait, vous pouvez même remplacer les classes et les fonctions d'un module exactement de la même manière.

Mais, comme l'a fait remarquer un intervenant, soyez prudent lorsque vous effectuez une opération de monkeyping:

  1. Si quelque chose d'autre que vos appels de logique de test appelle get_dataégalement, il appellera également votre remplacement patché par un singe plutôt que l'original - ce qui peut être bon ou mauvais. Méfiez-vous.

  2. S'il existe une variable ou un attribut qui pointe également vers la get_datafonction au moment où vous le remplacez, cet alias ne changera pas sa signification et continuera à pointer vers l'original get_data. (Pourquoi? Python lie simplement le nom get_datade votre classe à un autre objet fonction; les autres liaisons de nom ne sont pas du tout affectées.)

Daniel Roseman
la source
1
@LutzPrechelt juste pour être clair pour moi, que voulez-vous dire pointing to the original get_data function? Voulez-vous dire que lorsque vous stockez une fonction dans une variable, si quelqu'un change cette fonction, la variable continuera à pointer vers l'ancienne?
fabriciorissetto
3
@fabriciorissetto: Normalement, vous ne changez pas les objets fonction en Python. Lorsque vous effectuez un patch de singe get_data, vous liez à nouveau le nom get_dataà une fonction factice . Si un autre nom quelque part ailleurs dans le programme est lié à la fonction-anciennement connue sous le nom de get_data, rien ne changera pour cet autre nom.
Lutz Prechelt
1
@LutzPrechelt Pourriez-vous expliquer un peu plus à ce sujet?
Calvin Ku
Je pense que le patch de singe peut être utile en particulier pour le débogage et dans les fonctions de décorateurs ou d'usines d'objets. Cependant, rappelez-vous qu'explicite vaut mieux qu'implicite alors assurez-vous que votre code est insensible au contexte, lisez "Goto considéré comme nuisible", etc ...
aoeu256
Donc, c'est juste quelque chose qui ressemble à l'utilisation de la fonction 'eval', où vous pourriez insérer du nouveau code au moment de l'exécution?
Wintermute
363

Un MonkeyPatch est un morceau de code Python qui étend ou modifie un autre code au moment de l'exécution (généralement au démarrage).

Un exemple simple ressemble à ceci:

from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
    return "ook ook eee eee eee!"

SomeClass.speak = speak

Source: page MonkeyPatch sur le wiki de Zope.

Paolo
la source
126

Qu'est-ce qu'un patch singe?

Autrement dit, le patch de singe apporte des modifications à un module ou à une classe pendant que le programme est en cours d'exécution.

Exemple d'utilisation

Il y a un exemple de patch de singe dans la documentation Pandas:

import pandas as pd
def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Pour décomposer cela, nous importons d'abord notre module:

import pandas as pd

Ensuite, nous créons une définition de méthode, qui existe non liée et libre en dehors de la portée de toutes les définitions de classe (puisque la distinction est assez dénuée de sens entre une fonction et une méthode non liée, Python 3 supprime la méthode non liée):

def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

Ensuite, nous attachons simplement cette méthode à la classe sur laquelle nous voulons l'utiliser:

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class

Et puis nous pouvons utiliser la méthode sur une instance de la classe et supprimer la méthode lorsque nous avons terminé:

df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Avertissement pour la manipulation de nom

Si vous utilisez la manipulation de noms (préfixer les attributs avec un double soulignement, ce qui modifie le nom et que je ne recommande pas), vous devrez manuellement modifier les noms si vous faites cela. Comme je ne recommande pas la manipulation de noms, je ne le démontrerai pas ici.

Exemple de test

Comment pouvons-nous utiliser ces connaissances, par exemple, dans les tests?

Supposons que nous devons simuler un appel de récupération de données vers une source de données externe qui entraîne une erreur, car nous voulons garantir un comportement correct dans un tel cas. Nous pouvons corriger la structure des données par singe pour garantir ce comportement. (Donc, en utilisant un nom de méthode similaire à celui suggéré par Daniel Roseman :)

import datasource

def get_data(self):
    '''monkey patch datasource.Structure with this to simulate error'''
    raise datasource.DataRetrievalError

datasource.Structure.get_data = get_data

Et lorsque nous le testons pour un comportement qui repose sur cette méthode générant une erreur, s'il est correctement implémenté, nous obtiendrons ce comportement dans les résultats du test.

Faire simplement ce qui précède modifiera l' Structureobjet pendant toute la durée du processus, vous voudrez donc utiliser des configurations et des démontages dans vos tests pour éviter de le faire, par exemple:

def setUp(self):
    # retain a pointer to the actual real method:
    self.real_get_data = datasource.Structure.get_data
    # monkey patch it:
    datasource.Structure.get_data = get_data

def tearDown(self):
    # give the real method back to the Structure object:
    datasource.Structure.get_data = self.real_get_data

(Bien que ce qui précède soit correct, ce serait probablement une meilleure idée d'utiliser la mockbibliothèque pour patcher le code. mockLe patchdécorateur de . Je n'ai pas encore revu le code mockmais j'imagine qu'il utilise le patch de singe de la même manière.)

Aaron Hall
la source
la charge pour le monkeypatcher est-elle donc de stocker une référence à la méthode réelle? Par exemple, que se passe-t-il si l'on oublie l'étape "conserver un pointeur", elle est perdue?
Tommy
3
@Tommy Si les recomptes à une méthode "écrasée" vont à zéro - ils sont récupérés et donc "perdus" pour la durée de vie du processus (ou à moins que le module dont il est issu soit rechargé, mais cela est généralement déconseillé).
Aaron Hall
33

Selon Wikipedia :

En Python, le terme correctif de singe fait uniquement référence aux modifications dynamiques d'une classe ou d'un module au moment de l'exécution, motivées par l'intention de corriger le code tiers existant comme solution de contournement à un bogue ou une fonctionnalité qui n'agit pas comme vous le souhaitez.

David Heffernan
la source
16

Premièrement: le patching de singe est un hack diabolique (à mon avis).

Il est souvent utilisé pour remplacer une méthode au niveau du module ou de la classe par une implémentation personnalisée.

Le cas d'utilisation le plus courant consiste à ajouter une solution de contournement pour un bogue dans un module ou une classe lorsque vous ne pouvez pas remplacer le code d'origine. Dans ce cas, vous remplacez le "mauvais" code par le patch de singe par une implémentation à l'intérieur de votre propre module / package.

Andreas Jung
la source
8
Dans le cas où certains modules patcheraient la même chose: vous êtes condamné.
Andreas Jung
49
Bien que sa puissance puisse en effet être dangereuse en général, elle est idéale pour les tests
dkrikun
1
Le cas d'utilisation le plus courant est en fait pour les tests, en particulier les tests unitaires. Vous souhaitez tester uniquement votre code, vous corrigez donc tout appel externe pour retourner un résultat attendu.
brocoli
1
ce n'est pas mal, je l'utilise pour corriger les bugs dans les logiciels d'autres personnes jusqu'à ce qu'une nouvelle version sorte au lieu de bifurquer et de créer une nouvelle dépendance.
nurettin
1
La correction de singe peut être effectuée d'une «manière fonctionnelle pure» et non de la manière mutable, «contextuelle», de type goto en effectuant uniquement la correction à l'intérieur des décorateurs qui renvoient une nouvelle version corrigée de votre classe / méthode (plutôt que de la modifier). Beaucoup de programmeurs C # / Java ne connaissent pas le développement piloté par REPL, ils codent donc dans leurs IDE qui nécessitent que tout soit défini statiquement. Puisque C # / Java n'avait pas de patch de singe, ils assument son mal quand ils le voient en JavaScript, Smalltalk, Lisp, Python, etc ... car cela va à l'encontre de leur pratique de développement statique IDE.
aoeu256
13

La correction de singe ne peut être effectuée que dans des langages dynamiques, dont python est un bon exemple. La modification d'une méthode au moment de l'exécution au lieu de mettre à jour la définition d'objet en est un exemple; de ​​même, l'ajout d'attributs (qu'il s'agisse de méthodes ou de variables) au moment de l'exécution est considéré comme une correction de singe. Cela se fait souvent lorsque vous travaillez avec des modules dont vous n'avez pas la source, de sorte que les définitions d'objet ne peuvent pas être facilement modifiées.

Ceci est considéré comme mauvais car cela signifie que la définition d'un objet ne décrit pas complètement ou précisément comment il se comporte réellement.

Aaron Dufour
la source
Cependant, le patch de singe peut être utile tant qu'au lieu de modifier un objet ou une classe existant, vous créez une nouvelle version d'un objet avec des membres patchés à l'intérieur d'un décorateur qui crie "hey im va vous patcher".
aoeu256
Vous pouvez utiliser des annotations sur les membres patchés pour stocker dans le membre patché le décorateur qui a été utilisé pour patcher dans les patches. Disons que vous disposez d'un décorateur annulable qui crée une nouvelle version annulable d'un objet fonction avec une méthode d'annulation. Vous pouvez mettre dans le décorateur un champ patcher pointant vers votre décorateur annulable.
aoeu256
5

Le patch de singe rouvre les classes ou méthodes existantes dans la classe au moment de l'exécution et modifie le comportement, qui doit être utilisé avec prudence, ou vous ne devez l'utiliser que lorsque vous en avez vraiment besoin.

Comme Python est un langage de programmation dynamique, les classes sont modifiables afin que vous puissiez les rouvrir et les modifier ou même les remplacer.

kamal
la source
1

Qu'est-ce que le patch de singe? La correction de singe est une technique utilisée pour mettre à jour dynamiquement le comportement d'un morceau de code au moment de l'exécution.

Pourquoi utiliser le patch de singe? Il nous permet de modifier ou d'étendre le comportement des bibliothèques, modules, classes ou méthodes lors de l'exécution sans réellement modifier le code source

Conclusion La correction de singe est une technique intéressante et nous avons maintenant appris à le faire en Python. Cependant, comme nous l'avons vu, il a ses propres inconvénients et doit être utilisé avec précaution.

Pour plus d'informations, veuillez consulter [1]: https://medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

akash kumar
la source