Was ist Affenpatching?

548

Ich versuche zu verstehen, was ist Affen-Patching oder ein Affen-Patch?

Ist das so etwas wie das Überladen oder Delegieren von Methoden / Operatoren?

Hat es etwas mit diesen Dingen gemeinsam?

Sergei Basharov
quelle
Ich denke, die Definition von Google ist nützlich und am allgemeinsten: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

Antworten:

522

Nein, es ist nicht wie eines dieser Dinge. Es ist einfach das dynamische Ersetzen von Attributen zur Laufzeit.

Betrachten Sie beispielsweise eine Klasse mit einer Methode get_data. Diese Methode führt eine externe Suche durch (z. B. in einer Datenbank oder einer Web-API), und verschiedene andere Methoden in der Klasse rufen sie auf. In einem Komponententest möchten Sie jedoch nicht von der externen Datenquelle abhängig sein. Daher ersetzen Sie die get_dataMethode dynamisch durch einen Stub, der einige feste Daten zurückgibt.

Da Python-Klassen veränderbar sind und Methoden nur Attribute der Klasse sind, können Sie dies beliebig tun - und sogar Klassen und Funktionen in einem Modul auf genau dieselbe Weise ersetzen.

Aber, wie ein Kommentator betonte, seien Sie beim Monkeypatching vorsichtig:

  1. Wenn außer Ihren Testlogik-Aufrufen noch etwas anderes angezeigt wird get_data, wird auch Ihr mit Affen gepatchter Ersatz anstelle des Originals aufgerufen - was gut oder schlecht sein kann. Pass auf dich auf.

  2. Wenn eine Variable oder ein Attribut vorhanden ist, die bzw. get_datadas zum Zeitpunkt des Ersetzens auch auf die Funktion verweist, ändert dieser Alias ​​seine Bedeutung nicht und verweist weiterhin auf das Original get_data. (Warum? Python bindet den Namen get_datain Ihrer Klasse nur an ein anderes Funktionsobjekt. Andere Namensbindungen sind davon überhaupt nicht betroffen.)

Daniel Roseman
quelle
1
@LutzPrechelt nur um mir klar zu sein, was meinst du damit pointing to the original get_data function? Meinen Sie, wenn Sie eine Funktion in einer Variablen speichern, wenn jemand diese Funktion ändert, zeigt die Variable weiterhin auf die alte?
Fabriciorissetto
3
@fabriciorissetto: Normalerweise ändern Sie keine Funktionsobjekte in Python. Wenn Sie einen Affen-Patch get_dataausführen, binden Sie den Namen erneut get_dataan eine Scheinfunktion . Wenn ein anderer Name an einer anderen Stelle im Programm an die Funktion gebunden ist, die früher als bekannt war get_data, ändert sich für diesen anderen Namen nichts.
Lutz Prechelt
1
@LutzPrechelt Könntest du etwas mehr dazu erklären?
Calvin Ku
Ich denke, dass das Patchen von Affen besonders zum Debuggen und in Funktionen von Dekorateuren oder Objektfabriken nützlich sein kann.
Denken
Es ist also nur so etwas wie die Verwendung der 'eval'-Funktion, bei der Sie zur Laufzeit neuen Code einfügen können?
Wintermute
363

Ein MonkeyPatch ist ein Teil des Python-Codes, der zur Laufzeit (normalerweise beim Start) anderen Code erweitert oder ändert.

Ein einfaches Beispiel sieht so aus:

from SomeOtherProduct.SomeModule import SomeClass

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

SomeClass.speak = speak

Quelle: MonkeyPatch- Seite im Zope-Wiki.

Paolo
quelle
126

Was ist ein Affenfeld?

Einfach ausgedrückt, beim Affen-Patching werden Änderungen an einem Modul oder einer Klasse vorgenommen, während das Programm ausgeführt wird.

Anwendungsbeispiel

In der Pandas-Dokumentation finden Sie ein Beispiel für das Patchen von Affen:

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

Um dies aufzuschlüsseln, importieren wir zuerst unser Modul:

import pandas as pd

Als nächstes erstellen wir eine Methodendefinition, die ungebunden und frei außerhalb des Bereichs von Klassendefinitionen existiert (da die Unterscheidung zwischen einer Funktion und einer ungebundenen Methode ziemlich bedeutungslos ist, beseitigt Python 3 die ungebundene Methode):

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]

Als nächstes hängen wir diese Methode einfach an die Klasse an, für die wir sie verwenden möchten:

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

Und dann können wir die Methode für eine Instanz der Klasse verwenden und die Methode löschen, wenn wir fertig sind:

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

Vorsichtsmaßnahme für die Namensverfälschung

Wenn Sie die Namensverknüpfung verwenden (indem Sie Attributen einen doppelten Unterstrich voranstellen, der den Namen ändert und den ich nicht empfehle), müssen Sie die Namensverknüpfung manuell durchführen, wenn Sie dies tun. Da ich das Mangeln von Namen nicht empfehle, werde ich es hier nicht demonstrieren.

Testbeispiel

Wie können wir dieses Wissen zum Beispiel beim Testen nutzen?

Angenommen, wir müssen einen Datenabrufaufruf an eine externe Datenquelle simulieren, der zu einem Fehler führt, da wir in einem solchen Fall ein korrektes Verhalten sicherstellen möchten. Wir können die Datenstruktur mit Affen patchen, um dieses Verhalten sicherzustellen. (Verwenden Sie also einen ähnlichen Methodennamen wie von Daniel Roseman vorgeschlagen :)

import datasource

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

datasource.Structure.get_data = get_data

Und wenn wir es auf Verhalten testen, bei dem diese Methode einen Fehler auslöst, erhalten wir dieses Verhalten bei korrekter Implementierung in den Testergebnissen.

StructureWenn Sie nur die oben genannten Schritte ausführen, ändert sich das Objekt für die Dauer des Prozesses. Daher sollten Sie in Ihren Unittests Setups und Teardowns verwenden, um dies zu vermeiden, z.

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

(Obwohl das oben Genannte in Ordnung ist, wäre es wahrscheinlich eine bessere Idee, die mockBibliothek zum Patchen des Codes zu verwenden. mockDer patchDekorateur wäre weniger fehleranfällig als das oben Genannte, was mehr Codezeilen und damit mehr Möglichkeiten zur Einführung von Fehlern erfordern würde Ich habe den Code noch nicht überprüft, mockaber ich stelle mir vor, dass er Affen-Patches auf ähnliche Weise verwendet.)

Aaron Hall
quelle
Ist es also die Belastung für den Monkeypatcher, einen Verweis auf die reale Methode zu speichern? Was passiert beispielsweise, wenn man den Schritt "Zeiger behalten" vergisst, er geht verloren?
Tommy
3
@Tommy Wenn Refcounts auf eine "überschriebene" Methode auf Null gehen - es wird Müll gesammelt und somit für die Lebensdauer des Prozesses "verloren" (oder es sei denn, das Modul, aus dem es stammt, wird neu geladen, aber das wird normalerweise nicht empfohlen).
Aaron Hall
33

Laut Wikipedia :

In Python bezieht sich der Begriff "Affen-Patch" nur auf dynamische Änderungen einer Klasse oder eines Moduls zur Laufzeit, die durch die Absicht motiviert sind, vorhandenen Code von Drittanbietern zu patchen, um einen Fehler oder eine Funktion zu umgehen, die nicht wie gewünscht funktioniert.

David Heffernan
quelle
16

Erstens: Das Patchen von Affen ist ein böser Hack (meiner Meinung nach).

Es wird häufig verwendet, um eine Methode auf Modul- oder Klassenebene durch eine benutzerdefinierte Implementierung zu ersetzen.

Der häufigste Anwendungsfall ist das Hinzufügen einer Problemumgehung für einen Fehler in einem Modul oder einer Klasse, wenn Sie den ursprünglichen Code nicht ersetzen können. In diesem Fall ersetzen Sie den "falschen" Code durch Affen-Patching durch eine Implementierung in Ihrem eigenen Modul / Paket.

Andreas Jung
quelle
8
Für den Fall, dass einige Module das Gleiche tun: Sie sind zum Scheitern verurteilt.
Andreas Jung
49
Während seine Macht in der Tat im Allgemeinen gefährlich sein könnte, ist es großartig zum Testen
dkrikun
1
Der häufigste Anwendungsfall ist das Testen, insbesondere Unit-Tests. Sie möchten nur Ihren Code testen, also patchen Sie jeden externen Aufruf, um ein erwartetes Ergebnis zurückzugeben.
Brocoli
1
Es ist nicht böse, ich benutze es, um Fehler in der Software anderer Leute zu patchen, bis eine neue Version herauskommt, anstatt eine neue Abhängigkeit zu forken und zu erstellen.
Nurettin
1
Das Patchen von Affen kann auf eine "rein funktionale Art und Weise" erfolgen, nicht auf die veränderbare, "kontextsensitive", goto-artige Art und Weise, indem nur in Dekorateuren gepatcht wird, die eine neue gepatchte Version Ihrer Klasse / Methode zurückgeben (anstatt sie zu modifizieren). Viele C # / Java-Programmierer wissen nichts über REPL-gesteuerte Entwicklung, daher codieren sie in ihren IDEs, sodass alles statisch definiert werden muss. Da C # / Java kein Affen-Patching hatte, nehmen sie an, dass es böse ist, wenn sie es in JavaScript, Smalltalk, Lisp, Python usw. sehen, da dies gegen ihre statische IDE-gesteuerte Entwicklungspraxis verstößt.
aoeu256
13

Das Patchen von Affen kann nur in dynamischen Sprachen durchgeführt werden, wofür Python ein gutes Beispiel ist. Das Ändern einer Methode zur Laufzeit anstelle des Aktualisierens der Objektdefinition ist ein Beispiel. Ebenso wird das Hinzufügen von Attributen (ob Methoden oder Variablen) zur Laufzeit als Affen-Patching betrachtet. Diese werden häufig ausgeführt, wenn Sie mit Modulen arbeiten, für die Sie nicht die Quelle haben, sodass die Objektdefinitionen nicht einfach geändert werden können.

Dies wird als schlecht angesehen, da die Definition eines Objekts nicht vollständig oder genau beschreibt, wie es sich tatsächlich verhält.

Aaron Dufour
quelle
Das Patchen von Affen kann jedoch nützlich sein, solange Sie nicht ein vorhandenes Objekt oder eine vorhandene Klasse ändern, sondern eine neue Version eines Objekts mit Patches in Elementen in einem Dekorateur erstellen, die "Hey, ich werde Sie patchen" schreien.
aoeu256
Sie können Anmerkungen zu gepatchten Elementen verwenden, um in dem gepatchten Element zu speichern, mit welchem ​​Dekorator die Patches gepatcht wurden. Nehmen wir an, Sie haben einen undoable-Dekorator, der mit einer Undo-Methode eine neue undoable-Version eines Funktionsobjekts erstellt. Sie können im Dekorator ein Patcherfeld einfügen, das auf Ihren rückgängig zu machenden Dekorator zeigt.
aoeu256
5

Beim Affen-Patching werden die vorhandenen Klassen oder Methoden in der Klasse zur Laufzeit erneut geöffnet und das Verhalten geändert. Dies sollte mit Vorsicht angewendet werden, oder Sie sollten es nur verwenden, wenn Sie es wirklich benötigen.

Da Python eine dynamische Programmiersprache ist, können Klassen geändert werden, sodass Sie sie erneut öffnen und ändern oder sogar ersetzen können.

Kamal
quelle
1

Was ist Affenpatching?Das Patchen von Affen ist eine Technik, mit der das Verhalten eines Codeteils zur Laufzeit dynamisch aktualisiert wird.

Warum Affenpatching verwenden?Es ermöglicht uns, das Verhalten von Bibliotheken, Modulen, Klassen oder Methoden zur Laufzeit zu ändern oder zu erweitern, ohne den Quellcode tatsächlich zu ändern

Fazit Das Patchen von Affen ist eine coole Technik, und jetzt haben wir gelernt, wie man das in Python macht. Wie wir bereits besprochen haben, hat es jedoch seine eigenen Nachteile und sollte sorgfältig verwendet werden.

Weitere Informationen finden Sie unter [1]: https://medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

Akash Kumar
quelle