858 Stimmen

Hinzufügen einer Methode zu einer existierenden Objektinstanz

Ich habe gelesen, dass es in Python möglich ist, eine Methode zu einem bestehenden Objekt (d.h. nicht in der Klassendefinition) hinzuzufügen.

Ich verstehe, dass das nicht immer gut ist. Aber wie könnte man das machen?

1172voto

Jason Pratt Punkte 12377

In Python gibt es einen Unterschied zwischen Funktionen und gebundenen Methoden.

>>> def foo():
...     print "foo"
...
>>> class A:
...     def bar( self ):
...         print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

Gebundene Methoden wurden an eine Instanz "gebunden" (wie beschreibend), und diese Instanz wird als erstes Argument übergeben, wenn die Methode aufgerufen wird.

Callables, die Attribute einer Klasse (im Gegensatz zu einer Instanz) sind, sind jedoch weiterhin ungebunden, so dass Sie die Klassendefinition jederzeit ändern können:

>>> def fooFighters( self ):
...     print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters

Zuvor definierte Instanzen werden ebenfalls aktualisiert (sofern sie das Attribut nicht selbst überschrieben haben):

>>> a.fooFighters()
fooFighters

Das Problem entsteht, wenn Sie eine Methode an eine einzelne Instanz anhängen wollen:

>>> def barFighters( self ):
...     print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

Die Funktion ist nicht automatisch gebunden, wenn sie direkt mit einer Instanz verbunden ist:

>>> a.barFighters
<function barFighters at 0x00A98EF0>

Um sie zu binden, können wir die MethodType-Funktion im Typenmodul :

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters

Dieses Mal sind andere Instanzen der Klasse nicht betroffen:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'

Weitere Informationen finden Sie unter Deskriptoren y Metaklasse Programmierung .

187voto

Vorwort - ein Hinweis zur Kompatibilität: andere Antworten funktionieren möglicherweise nur in Python 2 - diese Antwort sollte in Python 2 und 3 einwandfrei funktionieren. Wenn Sie nur für Python 3 schreiben, können Sie die explizite Vererbung von object aber ansonsten sollte der Code derselbe bleiben.

Hinzufügen einer Methode zu einer existierenden Objektinstanz

Ich habe gelesen, dass es in Python möglich ist, eine Methode zu einem bestehenden Objekt hinzuzufügen (z. B. nicht in der Klassendefinition).

Ich verstehe, dass dies nicht immer eine gute Entscheidung ist. Aber wie kann man das tun?

Ja, es ist möglich - aber nicht empfohlen

Ich empfehle das nicht. Das ist eine schlechte Idee. Tun Sie es nicht.

Dafür gibt es einige Gründe:

  • Sie fügen ein gebundenes Objekt zu jeder Instanz hinzu, mit der Sie dies tun. Wenn Sie dies häufig tun, werden Sie wahrscheinlich eine Menge Speicherplatz verschwenden. Gebundene Methoden werden in der Regel nur für die kurze Dauer ihres Aufrufs erstellt und hören dann auf zu existieren, wenn sie automatisch gelöscht werden. Wenn Sie dies manuell tun, haben Sie eine Namensbindung, die auf die gebundene Methode verweist - was deren Garbage Collection bei Verwendung verhindert.
  • Objektinstanzen eines bestimmten Typs haben im Allgemeinen ihre Methoden auf alle Objekte dieses Typs. Wenn Sie an anderer Stelle Methoden hinzufügen, werden einige Instanzen diese Methoden haben und andere nicht. Programmierer erwarten dies nicht, und Sie riskieren einen Verstoß gegen die Regel der geringsten Überraschung .
  • Da es andere gute Gründe gibt, dies nicht zu tun, verschaffen Sie sich zusätzlich einen schlechten Ruf, wenn Sie es tun.

Ich schlage daher vor, dass Sie dies nicht tun, es sei denn, Sie haben einen wirklich guten Grund. Es ist viel besser, die richtige Methode in der Klassendefinition zu definieren o weniger vorzugsweise, um die Klasse direkt zu patchen, etwa so:

Foo.sample_method = sample_method

Da es aber lehrreich ist, werde ich Ihnen einige Möglichkeiten aufzeigen, wie Sie dies tun können.

Wie es gemacht werden kann

Hier ist etwas Code für die Einrichtung. Wir brauchen eine Klassendefinition. Sie könnte importiert werden, aber das ist wirklich nicht wichtig.

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''

Erstellen Sie eine Instanz:

foo = Foo()

Erstellen Sie eine Methode, um sie zu ergänzen:

def sample_method(self, bar, baz):
    print(bar + baz)

Methode nought (0) - Verwendung der Deskriptormethode, __get__

Gepunktete Lookups auf Funktionen rufen die __get__ Methode der Funktion mit der Instanz, wodurch das Objekt an die Methode gebunden wird und somit eine "gebundene Methode" entsteht.

foo.sample_method = sample_method.__get__(foo)

und jetzt:

>>> foo.sample_method(1,2)
3

Methode eins - types.MethodType

Importieren Sie zunächst die Typen, von denen wir den Methodenkonstruktor erhalten werden:

import types

Jetzt fügen wir die Methode zur Instanz hinzu. Dazu benötigen wir den MethodType-Konstruktor aus der types Modul (das wir oben importiert haben).

Die Argumentensignatur für types.MethodType (in Python 3) lautet (function, instance) :

foo.sample_method = types.MethodType(sample_method, foo)

und Nutzung:

>>> foo.sample_method(1,2)
3

In Python 2 lautete die Signatur übrigens (function, instance, class) :

foo.sample_method = types.MethodType(sample_method, foo, Foo)

Methode zwei: lexikalische Bindung

Zunächst erstellen wir eine Wrapper-Funktion, die die Methode an die Instanz bindet:

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn

Verwendung:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3

Methode drei: functools.partial

Eine Teilfunktion wendet das/die erste(n) Argument(e) auf eine Funktion (und optional auf Schlüsselwortargumente) an und kann später mit den übrigen Argumenten (und überschreibenden Schlüsselwortargumenten) aufgerufen werden. Also

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

Dies ist sinnvoll, wenn man bedenkt, dass gebundene Methoden Teilfunktionen der Instanz sind.

Ungebundene Funktion als Objektattribut - warum das nicht funktioniert:

Wenn wir versuchen, die sample_method auf die gleiche Weise wie die Klasse hinzuzufügen, ist sie nicht an die Instanz gebunden und nimmt nicht das implizite self als erstes Argument an.

>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)

Wir können die ungebundene Funktion zum Laufen bringen, indem wir explizit die Instanz übergeben (oder irgendetwas anderes, da diese Methode nicht wirklich die self Argumentvariable), aber es wäre nicht konsistent mit der erwarteten Signatur anderer Instanzen (wenn wir monkey-Parcheando diese Instanz sind):

>>> foo.sample_method(foo, 1, 2)
3

Schlussfolgerung

Sie kennen jetzt mehrere Möglichkeiten, wie Sie könnte tun, aber ganz im Ernst - tun Sie das nicht.

105voto

Evgeny Punkte 10456

Modul neu ist seit Python 2.6 veraltet und wurde in 3.0 entfernt, verwenden Sie Typen

siehe http://docs.python.org/library/new.html

In dem folgenden Beispiel habe ich absichtlich den Rückgabewert von patch_me() Funktion. Ich denke, dass die Angabe des Rückgabewerts den Eindruck erwecken kann, dass patch ein neues Objekt zurückgibt, was nicht stimmt - es verändert das eingehende Objekt. Wahrscheinlich kann dies eine diszipliniertere Verwendung von Monkeypatching erleichtern.

import types

class A(object):#but seems to work for old style objects too
    pass

def patch_me(target):
    def method(target,x):
        print "x=",x
        print "called from", target
    target.method = types.MethodType(method,target)
    #add more if needed

a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>  
patch_me(a)    #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6)        #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>

42voto

Tomasz Zieliński Punkte 15606

Ich denke, dass die obigen Antworten den Kernpunkt verfehlt haben.

Nehmen wir eine Klasse mit einer Methode:

class A(object):
    def m(self):
        pass

Spielen wir nun damit in ipython:

In [2]: A.m
Out[2]: <unbound method A.m>

Ok, also m() wird irgendwie zu einer ungebundenen Methode von A . Aber ist das wirklich so?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

Es stellt sich heraus, dass m() ist nur eine Funktion, deren Verweis in A Klassenwörterbuch - es gibt keine Magie. Warum dann A.m gibt uns eine ungebundene Methode? Das liegt daran, dass der Punkt nicht in eine einfache Wörterbuchabfrage übersetzt wird. Es ist de facto ein Aufruf von A.__class__.__getattribute__(A, 'm'):

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

Ich weiß zwar nicht genau, warum die letzte Zeile zweimal gedruckt wird, aber es ist doch klar, was hier vor sich geht.

Die Standardfunktion __getattribute__ prüft, ob es sich bei dem Attribut um ein sogenanntes Deskriptor oder nicht, d.h. ob es eine spezielle __get__-Methode implementiert. Wenn sie diese Methode implementiert, dann ist das, was zurückgegeben wird, das Ergebnis des Aufrufs dieser __get__-Methode. Gehen wir zurück zur ersten Version unserer A Klasse, das ist es, was wir haben:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

Und da Python-Funktionen das Deskriptor-Protokoll implementieren, binden sie sich, wenn sie im Namen eines Objekts aufgerufen werden, in ihrer __get__-Methode an dieses Objekt.

Ok, wie fügt man also eine Methode zu einem bestehenden Objekt hinzu? Angenommen, die Klasse Parcheando stört dich nicht, dann ist es so einfach wie:

B.m = m

Dann B.m "wird dank der Deskriptor-Magie zu einer ungebundenen Methode.

Und wenn Sie eine Methode nur zu einem einzelnen Objekt hinzufügen wollen, müssen Sie die Maschinerie selbst nachbilden, indem Sie types.MethodType verwenden:

b.m = types.MethodType(m, b)

Im Übrigen:

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>

32voto

John Downey Punkte 13394

In Python funktioniert Monkeypatching im Allgemeinen durch das Überschreiben der Signatur einer Klasse oder Funktion mit Ihrer eigenen. Nachfolgend ein Beispiel aus der Zope-Wiki :

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak

Dieser Code überschreibt/erzeugt eine Methode namens speak in der Klasse. In Jeff Atwoods jüngster Beitrag auf monkey Parcheando Er zeigte ein Beispiel in C# 3.0, der Sprache, die ich derzeit bei der Arbeit verwende.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X