Überschreiben Sie eine Methode auf Instanzebene

83

Gibt es in Python eine Möglichkeit, eine Klassenmethode auf Instanzebene zu überschreiben? Beispielsweise:

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF
# METHOD OVERRIDE
boby.bark() # WoOoOoF!!
Pistacchio
quelle

Antworten:

13

Bitte tun Sie dies nicht wie gezeigt. Ihr Code wird unlesbar, wenn Sie eine Instanz monkeypatchen, um sich von der Klasse zu unterscheiden.

Sie können monkeypatched Code nicht debuggen.

Wenn Sie einen Fehler in bobyund findenprint type(boby) , werden Sie sehen, dass (a) es ein Hund ist, aber (b) aus einem unbekannten Grund nicht richtig bellt. Das ist ein Albtraum. TU es nicht.

Bitte machen Sie dies stattdessen.

class Dog:
    def bark(self):
        print "WOOF"

class BobyDog( Dog ):
    def bark( self ):
        print "WoOoOoF!!"

otherDog= Dog()
otherDog.bark() # WOOF

boby = BobyDog()
boby.bark() # WoOoOoF!!
S.Lott
quelle
8
@arivero: Ich dachte, das "Bitte mach das nicht wie gezeigt" machte das vollkommen klar. Welche anderen oder anderen Wörter möchten Sie sehen, um klarer zu machen, dass dies nicht die gestellte Frage beantwortet, sondern Ratschläge gibt, warum dies eine schlechte Idee ist?
S.Lott
43
Ich bin weder mit dem Rat noch mit dem OP einverstanden, wie es scheint. Aber ich gehe davon aus, dass die Leute Gründe haben zu fragen. Oder auch wenn das OP dies nicht getan hat, könnten andere zukünftige Besucher dies tun. Also, IMHO, eine Antwort plus ein Verweis ist besser als nur ein Verweis.
Arivero
13
@arivero: Das hat meine Frage nicht beantwortet.
S.Lott
9
@ S.Lott Ich denke, Sie sollten das "gezeigte" mit der tatsächlichen Antwort verknüpfen. Ich habe kein wirkliches Problem damit, dass dies die akzeptierte Antwort ist, aber es gibt Gründe, warum Sie unter bestimmten Umständen und aufgrund meiner Überfliegungslesung einen Affen-Patch benötigen Ich habe "wie gezeigt" als das verstanden, was Sie gezeigt haben, und nicht als eine andere Antwort.
Daniel Chatfield
4
Unterklassen sind ein viel stärkerer Vertrag als das Patchen von Affen durch eine Methode. Starke Verträge verhindern nach Möglichkeit unerwartetes Verhalten. In einigen Fällen ist jedoch ein lockerer Vertrag wünschenswert. In solchen Situationen würde ich es vorziehen, einen Rückruf anstelle von Affen-Patches zu verwenden, da der Klassenvertrag unverletzt bleibt, wenn ein bestimmtes Verhalten angepasst werden kann. Ihre Antwort, obwohl praktische Ratschläge, ist für diese Frage ziemlich schlecht und Ihr Codierungsstil ist inkonsistent.
Aaron3468
161

Ja es ist möglich:

class Dog:
    def bark(self):
        print "Woof"

def new_bark(self):
    print "Woof Woof"

foo = Dog()

funcType = type(Dog.bark)

# "Woof"
foo.bark()

# replace bark with new_bark for this object only
foo.bark = funcType(new_bark, foo, Dog)

foo.bark()
# "Woof Woof"
codelogic
quelle
1
Ein kleiner Kommentar, wenn Sie funcType (new_bark, foo, Dog) ausführen, wird der Name == bark und die Instanzmethode Dog.new_bark in foo hinzugefügt .__ dict__ richtig? Wenn Sie also erneut aufrufen, suchen Sie zuerst im Instanzwörterbuch und rufen Sie danach auf
James
11
Bitte erläutern Sie, was dies bewirkt, insbesondere was funcTypeund warum dies erforderlich ist.
Aleksandr Dubinsky
3
Ich denke, dies kann ein bisschen einfacher und expliziter gemacht werden, indem funcType = types.MethodType(nach dem Importieren types) anstelle von verwendet wird funcType = type(Dog.bark).
Elias Zamaria
13
Ich denke, dies funktioniert nicht mit Python 3. Ich erhalte den Fehler "TypeError: function () Argument 1 muss Code sein, nicht Funktion". Irgendwelche Vorschläge für Python 3?
Sait
2
Sie können __get__die Funktion auch aufrufen , um sie an die Instanz zu binden.
Mad Physicist
34

Sie müssen MethodType aus dem Typmodul verwenden. Zweck von MethodType ist das Überschreiben von Methoden auf Instanzebene (damit self in überschriebenen Methoden verfügbar ist).

siehe unten Beispiel.

import types

class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print "WoOoOoF!!"

boby.bark = types.MethodType(_bark, boby)

boby.bark() # WoOoOoF!!
Harshal Dhumal
quelle
30

Um die hervorragende Antwort von @ codelogic zu erklären, schlage ich einen expliziteren Ansatz vor. Dies ist die gleiche Technik wie die. Operator eine Klassenmethode gründlich bindet, wenn Sie als Instanzattribut darauf zugreifen, mit der Ausnahme, dass Ihre Methode tatsächlich eine außerhalb einer Klasse definierte Funktion ist.

Bei der Arbeit mit dem Code von @ codelogic besteht der einzige Unterschied darin, wie die Methode gebunden ist. Ich verwende die Tatsache , dass die Funktionen und Methoden sind nicht-Daten - Deskriptoren in Python, und das Aufrufen von __get__Verfahren. Beachten Sie insbesondere, dass sowohl das Original als auch die Ersetzung identische Signaturen haben. Dies bedeutet, dass Sie die Ersetzung als vollständige Klassenmethode schreiben und über auf alle Instanzattribute zugreifen könnenself .

Klasse Hund:
    def bark (self):
        print "Woof"

def new_bark (self):
    print "Woof Woof"

foo = Hund ()

# "Woof"
foo.bark ()

# Ersetzen Sie die Rinde nur für dieses Objekt durch new_bark
foo.bark = new_bark .__ get __ (foo, Hund)

foo.bark ()
# "Wau-Wau"

Durch Zuweisen der gebundenen Methode zu einem Instanzattribut haben Sie eine nahezu vollständige Simulation zum Überschreiben einer Methode erstellt. Eine praktische Funktion, die fehlt, ist der Zugriff auf die No-Arg-Version von super, da Sie sich nicht in einer Klassendefinition befinden. Eine andere Sache ist, dass das __name__Attribut Ihrer gebundenen Methode nicht den Namen der Funktion annimmt, die es überschreibt, wie es in der Klassendefinition der Fall wäre, aber Sie können es trotzdem manuell festlegen. Der dritte Unterschied besteht darin, dass Ihre manuell gebundene Methode eine einfache Attributreferenz ist, die zufällig eine Funktion ist. Der .Operator ruft lediglich diese Referenz ab. Beim Aufrufen einer regulären Methode von einer Instanz aus erstellt der Bindungsprozess jedes Mal eine neue gebundene Methode.

Der einzige Grund, warum dies funktioniert, ist übrigens, dass Instanzattribute Nicht-Daten- Deskriptoren überschreiben . Datendeskriptoren haben __set__Methoden, die Methoden (zum Glück für Sie) nicht haben. Datendeskriptoren in der Klasse haben tatsächlich Vorrang vor allen Instanzattributen. Aus diesem Grund können Sie einer Eigenschaft zuweisen: Ihre __set__Methode wird aufgerufen, wenn Sie versuchen, eine Zuweisung vorzunehmen. Ich persönlich gehe gerne noch einen Schritt weiter und verstecke den tatsächlichen Wert des zugrunde liegenden Attributs in der Instanz __dict__, auf die mit normalen Mitteln nicht zugegriffen werden kann, gerade weil die Eigenschaft es beschattet.

Sie sollten auch bedenken, dass dies für magische Methoden (doppelter Unterstrich) sinnlos ist . Magische Methoden können natürlich auf diese Weise überschrieben werden, aber die Operationen, die sie verwenden, betrachten nur den Typ. Sie können beispielsweise __contains__in Ihrer Instanz etwas Besonderes festlegen , aber ein Aufruf x in instancewürde dies ignorieren und type(instance).__contains__(instance, x)stattdessen verwenden. Dies gilt für alle im Python- Datenmodell angegebenen magischen Methoden .

Verrückter Physiker
quelle
1
Dies sollte die akzeptierte Antwort sein: Es ist sauber und funktioniert in Python 3.
BlenderBender
@BlenderBender. Ich schätze Ihre Unterstützung
Mad Physicist
Was ist der Unterschied zwischen dieser Antwort und der über Ihrer von @Harshal Dhumai?
1313e
1
@ 1313. Funktionell sollte es keinen großen Unterschied geben. Ich vermute, dass die obige Antwort eine größere Auswahl an Callables als Eingabe akzeptiert, aber ohne die Dokumente zu lesen und herumzuspielen, bin ich mir nicht sicher.
Mad Physicist
Python 2-Dokumente hierfür: docs.python.org/2/howto/descriptor.html#functions-and-methods . Theoretisch ist es also dasselbe wie die Antwort von @Harshal Dhumai.
Rockallite
26
class Dog:
    def bark(self):
        print "WOOF"

boby = Dog()
boby.bark() # WOOF

# METHOD OVERRIDE
def new_bark():
    print "WoOoOoF!!"
boby.bark = new_bark

boby.bark() # WoOoOoF!!

Sie können die bobyVariable innerhalb der Funktion verwenden, wenn Sie dies benötigen. Da Sie die Methode nur für dieses eine Instanzobjekt überschreiben, ist dieser Weg einfacher und hat genau den gleichen Effekt wie die Verwendung self.

nosklo
quelle
1
IMHO mit der ursprünglichen Signatur erhöht die Lesbarkeit, insbesondere wenn die Funktion an anderer Stelle im Code definiert ist, nicht in der Nähe der Instanz. Die Ausnahme wäre der Fall, wenn die überschreibende Methode auch unabhängig als Funktion verwendet wird. In diesem einfachen Beispiel spielt das natürlich keine Rolle.
Codelogic
1
Ich verstehe nicht, warum dies nicht die akzeptierte Antwort ist. Es wird aufgerufen patchingund dies ist der richtige Weg, dies zu tun (zB boby = Dog()und boby.bark = new_bark). Es ist unglaublich nützlich beim Testen von Einheiten für Kontrollen. Weitere Erklärungen finden Sie unter tryolabs.com/blog/2013/07/05/run-time-method-patching-python (Beispiele) - nein, ich bin nicht mit der verlinkten Site oder dem verlinkten Autor verbunden.
Geoff
5
Die Methode new_bark hat keinen Zugriff auf self (Instanz), sodass der Benutzer nicht auf Instanzeigenschaften in new_bark zugreifen kann. Stattdessen muss man MethodType aus dem Typmodul verwenden (siehe meine Antwort unten).
Harshal Dhumal
2

Da hier niemand erwähnt functools.partial:

from functools import partial

class Dog:
    name = "aaa"
    def bark(self):
        print("WOOF")

boby = Dog()
boby.bark() # WOOF

def _bark(self):
    print("WoOoOoF!!")

boby.bark = partial(_bark, boby)
boby.bark() # WoOoOoF!!
user1285245
quelle
1

Da Funktionen in Python erstklassige Objekte sind, können Sie sie beim Initialisieren Ihres Klassenobjekts übergeben oder jederzeit für eine bestimmte Klasseninstanz überschreiben:

class Dog:
    def __init__(self,  barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        print "woof"

d=Dog()
print "calling original bark"
d.bark()

def barknew():
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()

def barknew1():
    print "nowoof"

d1.bark=barknew1
print "calling another new"
d1.bark()

und die Ergebnisse sind

calling original bark
woof
calling the new bark
wooOOOoof
calling another new
nowoof
JV.
quelle
-4

Obwohl mir die Vererbungsidee von S. Lott gefallen hat und ich der Sache 'Typ (a)' zustimme, denke ich, dass Funktionen auf folgende Weise verwaltet werden können:

class Dog:
    def __init__(self, barkmethod=None):
        self.bark=self.barkp
        if barkmethod:
           self.bark=barkmethod
    def barkp(self):
        """original bark"""
        print "woof"

d=Dog()
print "calling original bark"
d.bark()
print "that was %s\n" % d.bark.__doc__

def barknew():
    """a new type of bark"""
    print "wooOOOoof"

d1=Dog(barknew)
print "calling the new bark"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

def barknew1():
    """another type of new bark"""
    print "nowoof"

d1.bark=barknew1
print "another new"
d1.bark()
print "that was %s\n" % d1.bark.__doc__

und die Ausgabe ist:

calling original bark
woof
that was original bark

calling the new bark
wooOOOoof
that was a new type of bark

another new
nowoof
that was another type of new bark
JV.
quelle
2
Wenn es "verwaltet" werden muss, dann stimmt für mich etwas nicht. Besonders wenn es eine erstklassige Sprachfunktion gibt, die den Job bereits erledigt.
S.Lott
-4

Sehr geehrte Damen und Herren, dies wird nicht überschrieben. Sie rufen dieselbe Funktion nur zweimal mit dem Objekt auf. Grundsätzlich bezieht sich das Überschreiben auf mehr als eine Klasse. Wenn dieselbe Signaturmethode in verschiedenen Klassen vorhanden ist, entscheidet die Funktion, die Sie aufrufen, über das Objekt, das dies aufruft. Das Überschreiben ist in Python möglich, wenn Sie dafür sorgen, dass mehr als eine Klasse dieselben Funktionen schreibt und eine Sache mehr teilt, dass das Überladen in Python nicht zulässig ist

Nagimsit
quelle
-4

Ich fand, dass dies die genaueste Antwort auf die ursprüngliche Frage ist

https://stackoverflow.com/a/10829381/7640677

import a

def _new_print_message(message):
    print "NEW:", message

a.print_message = _new_print_message

import b
b.execute()
Ruvalcaba
quelle