Hinzufügen einer Methode zu einer vorhandenen Objektinstanz

642

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

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

Akdom
quelle

Antworten:

921

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 bei jedem Aufruf der Methode als erstes Argument übergeben.

Callables, die Attribute einer Klasse sind (im Gegensatz zu einer Instanz), sind jedoch noch nicht gebunden, sodass 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 selbst nicht überschrieben haben):

>>> a.fooFighters()
fooFighters

Das Problem tritt auf, wenn Sie eine Methode an eine einzelne Instanz anhängen möchten:

>>> 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 wird nicht automatisch gebunden, wenn sie direkt an eine Instanz angehängt wird:

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

Um es zu binden, können wir die MethodType-Funktion im Typmodul verwenden :

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

Diesmal 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 können durch das Lesen über finden Deskriptoren und Metaklasse Programmierung .

Jason Pratt
quelle
65
Anstatt ein manuell zu erstellen MethodType, rufen Sie das Deskriptorprotokoll manuell auf und lassen Sie die Funktion Ihre Instanz erstellen: barFighters.__get__(a)Erzeugt eine gebundene Methode für die barFightersBindung an a.
Martijn Pieters
2
@MartijnPieters alle Vorteile der Verwendung der descriptor protocolvs erstellen eine MethodTypeSeite, vielleicht ein wenig besser lesbar zu sein.
EndermanAPM
17
@EndermanAPM: Mehrere: Es ist wahrscheinlicher, dass es genauso weiterarbeitet wie der Zugriff auf das Attribut einer Instanz. Es wird auch für classmethodund staticmethodund andere Deskriptoren funktionieren . Dadurch wird vermieden, dass der Namespace mit einem weiteren Import überladen wird.
Martijn Pieters
34
Der vollständige Code für den vorgeschlagenen Deskriptoransatz lauteta.barFighters = barFighters.__get__(a)
eqzx
98

Das neue Modul ist seit Python 2.6 veraltet und in 3.0 entfernt. Verwenden Sie Typen

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

Im folgenden Beispiel habe ich den Rückgabewert absichtlich aus der patch_me()Funktion entfernt. Ich denke, dass die Angabe des Rückgabewerts den Eindruck erwecken kann, dass der Patch ein neues Objekt zurückgibt, was nicht der Fall ist - es ändert das eingehende Objekt. Wahrscheinlich kann dies einen disziplinierteren Einsatz von Monkeypatching ermöglichen.

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'>
Evgeny
quelle
1
Wie würde dies funktionieren, wenn sich die hinzugefügte Methode auf sich selbst beziehen muss? Mein erster Versuch führt zu einem Syntaxfehler, aber das Hinzufügen von self in der Definition der Methode scheint nicht zu funktionieren. Importtypen Klasse A (Objekt): # scheint aber auch für Objekte alten Stils zu funktionieren ax = 'ax' pass def patch_me (Ziel): def Methode (Ziel, x): print (self.ax) print ("x =" , x) print ("aufgerufen von", Ziel) target.method = types.MethodType (Methode, Ziel) # bei Bedarf weitere hinzufügen a = A () print (a.ax)
WesR
84

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 Python 3 schreiben, können Sie das explizite Erben von auslassen object, ansonsten sollte der Code derselbe bleiben .

Hinzufügen einer Methode zu einer vorhandenen Objektinstanz

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

Ich verstehe, dass es nicht immer eine gute Entscheidung ist, dies zu tun. Aber wie könnte man das machen?

Ja, es ist möglich - aber nicht zu empfehlen

Ich empfehle das nicht. Das ist eine schlechte Idee. Tu es nicht.

Hier sind einige Gründe:

  • Sie fügen jeder Instanz, für die Sie dies tun, ein gebundenes Objekt hinzu. Wenn Sie dies häufig tun, verschwenden Sie wahrscheinlich viel Speicher. Gebundene Methoden werden normalerweise nur für die kurze Dauer ihres Aufrufs erstellt und existieren dann nicht mehr, wenn automatisch Müll gesammelt wird. Wenn Sie dies manuell tun, erhalten Sie eine Namensbindung, die auf die gebundene Methode verweist. Dadurch wird die Speicherbereinigung bei Verwendung verhindert.
  • Objektinstanzen eines bestimmten Typs haben im Allgemeinen ihre Methoden für alle Objekte dieses Typs. Wenn Sie an anderer Stelle Methoden hinzufügen, verfügen einige Instanzen über diese Methoden, andere nicht. Programmierer werden dies nicht erwarten, und Sie riskieren, gegen die Regel der geringsten Überraschung zu verstoßen .
  • Da es andere wirklich gute Gründe gibt, dies nicht zu tun, geben Sie sich zusätzlich einen schlechten Ruf, wenn Sie dies tun.

Daher schlage ich vor, dass Sie dies nur tun, wenn Sie einen wirklich guten Grund haben. Es ist weitaus besser, die richtige Methode in der Klassendefinition zu definieren oder weniger bevorzugt, die Klasse direkt wie folgt zu patchen:

Foo.sample_method = sample_method

Da es jedoch lehrreich ist, werde ich Ihnen einige Möglichkeiten zeigen, dies zu tun.

Wie es geht

Hier ist ein Setup-Code. Wir brauchen eine Klassendefinition. Es könnte importiert werden, aber es spielt wirklich keine Rolle.

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 hinzuzufügen:

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

Methode nichts (0) - benutze die Deskriptormethode, __get__

Gepunktete Suchvorgänge für Funktionen rufen die __get__Methode der Funktion mit der Instanz auf, binden das Objekt an die Methode und erstellen so eine "gebundene Methode".

foo.sample_method = sample_method.__get__(foo)

und nun:

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

Methode eins - types.MethodType

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

import types

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

Die Argument-Signatur für types.MethodType lautet (function, instance, class):

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

und Verwendung:

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

Methode zwei: lexikalische Bindung

Zuerst 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

Verwendungszweck:

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

Methode drei: functools.partial

Eine Teilfunktion wendet die ersten Argumente auf eine Funktion (und optional auf Schlüsselwortargumente) an und kann später mit den verbleibenden Argumenten (und überschreibenden Schlüsselwortargumenten) aufgerufen werden. Somit:

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

Dies ist sinnvoll, wenn Sie bedenken, 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 hinzuzufügen, wie wir sie möglicherweise zur Klasse hinzufügen, ist sie nicht an die Instanz gebunden und nimmt das implizite Selbst nicht als erstes Argument.

>>> 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 Funktionieren bringen, indem wir die Instanz explizit übergeben (oder irgendetwas anderes, da diese Methode die selfArgumentvariable nicht verwendet ), aber sie würde nicht mit der erwarteten Signatur anderer Instanzen übereinstimmen (wenn wir Affen-Patches durchführen) diese Instanz):

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

Fazit

Sie kennen jetzt verschiedene Möglichkeiten, wie Sie dies tun können, aber im Ernst - tun Sie dies nicht.

Aaron Hall
quelle
1
Der Haftungsausschluss ist das, worüber ich mich gewundert habe. Methodendefinition sind einfach Funktionen, die in der Klassendefinition verschachtelt sind.
Atcold
1
@Atcold Ich habe in der Einleitung auf Gründe eingegangen, um dies zu vermeiden.
Aaron Hall
Die __get__Methode benötigt auch die Klasse als nächsten Parameter : sample_method.__get__(foo, Foo).
Aidas Bendoraitis
2
@AidasBendoraitis Ich würde nicht sagen, dass es "benötigt", es ist ein optionaler Parameter, der beim Anwenden des Deskriptorprotokolls angegeben wird - aber Python-Funktionen verwenden nicht das Argument: github.com/python/cpython/blob/master/Objects/funcobject .c # L581
Aaron Hall
Mein Kommentar basierte auf dieser Referenz: python-reference.readthedocs.io/en/latest/docs/dunderdsc/… Was ich jetzt aus offiziellen Dokumenten sehe, ist optional: docs.python.org/3/howto/descriptor.html# Deskriptor-Protokoll
Aidas Bendoraitis
35

Ich denke, dass die obigen Antworten den entscheidenden Punkt verfehlt haben.

Lassen Sie uns eine Klasse mit einer Methode haben:

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

Lassen Sie uns jetzt in ipython damit spielen:

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

Ok, so m () wird irgendwie ein ungebundenes Methode A . Aber ist es wirklich so?

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

Es stellt sich heraus, dass m () nur eine Funktion ist, deren Verweis zu einem Klassenwörterbuch hinzugefügt wird - es gibt keine Magie. Warum gibt uns Am dann eine ungebundene Methode? Dies liegt daran, dass der Punkt nicht in eine einfache Wörterbuchsuche übersetzt wird. Es ist de facto ein Aufruf von A .__ Klasse __.__ 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

Jetzt bin ich mir nicht sicher, warum die letzte Zeile zweimal gedruckt wird, aber es ist immer noch klar, was dort vor sich geht.

Das Standard-__getattribute__ überprüft nun, ob das Attribut ein sogenannter Deskriptor ist oder nicht, dh ob es eine spezielle __get__- Methode implementiert. Wenn diese Methode implementiert wird, wird das Ergebnis des Aufrufs dieser __get__ -Methode zurückgegeben. Wir kehren zur ersten Version unserer A- Klasse zurück und haben Folgendes:

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

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

Ok, wie fügt man einem vorhandenen Objekt eine Methode hinzu? Angenommen, es macht Ihnen nichts aus, die Klasse zu patchen, ist es so einfach wie:

B.m = m

Dann "wird" Bm dank der Deskriptormagie zu einer ungebundenen Methode.

Wenn Sie eine Methode nur einem einzelnen Objekt hinzufügen möchten, müssen Sie die Maschinerie mithilfe von types selbst emulieren. MethodType:

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

Apropos:

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'>
Tomasz Zieliński
quelle
19

In Python funktioniert das Patchen von Affen im Allgemeinen durch Überschreiben einer Klassen- oder Funktionssignatur mit Ihrer eigenen. Unten ist ein Beispiel aus dem Zope-Wiki :

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

Dieser Code überschreibt / erstellt eine Methode namens speak für die Klasse. In Jeff Atwoods jüngstem Beitrag über das Patchen von Affen . Er zeigt ein Beispiel in C # 3.0, der aktuellen Sprache, die ich für die Arbeit verwende.

John Downey
quelle
6
Aber es beeinflusst alle Instanzen der Klasse, nicht nur eine.
glglgl
14

Sie können Lambda verwenden, um eine Methode an eine Instanz zu binden:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()

Ausgabe:

This is instance string
Evgeny Prokurat
quelle
9

Es gibt mindestens zwei Möglichkeiten, eine Methode an eine Instanz anzuhängen, ohne types.MethodType:

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>

Nützliche Links:
Datenmodell - Aufrufen von Deskriptoren
Descriptor HowTo Guide - Aufrufen von Deskriptoren

ndpu
quelle
7

Was Sie suchen, setattrglaube ich. Verwenden Sie diese Option, um ein Attribut für ein Objekt festzulegen.

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>
HS.
quelle
8
Dadurch wird die Klasse gepatcht A, nicht die Instanz a.
Ethan Furman
5
Gibt es einen Grund, setattr(A,'printme',printme)statt einfach zu verwenden A.printme = printme?
Tobias Kienzler
1
Es ist sinnvoll, wenn man zur Laufzeit einen Methodennamen erstellt.
rr-
6

Da diese Frage für Nicht-Python-Versionen gestellt wurde, ist hier JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
Tamzin Blake
quelle
5

Konsolidierung der Antworten von Jason Pratt und des Community-Wikis mit Blick auf die Ergebnisse verschiedener Bindungsmethoden:

Beachten Sie insbesondere, wie das Hinzufügen der Bindungsfunktion als Klassenmethode funktioniert , der Referenzierungsbereich jedoch falsch ist.

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>

Persönlich bevorzuge ich die externe ADDMETHOD-Funktionsroute, da ich damit auch innerhalb eines Iterators dynamisch neue Methodennamen zuweisen kann.

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
Nisan.H
quelle
addmethodneu geschrieben auf folgende Weise def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )löst das Problem
Antony Hatchkins
5

Dies ist eigentlich ein Addon zur Antwort von "Jason Pratt"

Obwohl Jasons Antwort funktioniert, funktioniert es nur, wenn man einer Klasse eine Funktion hinzufügen möchte. Es hat bei mir nicht funktioniert, als ich versucht habe, eine bereits vorhandene Methode aus der .py-Quellcodedatei neu zu laden.

Ich habe ewig gebraucht, um eine Problemumgehung zu finden, aber der Trick scheint einfach zu sein ... 1. Importieren Sie den Code aus der Quellcodedatei. 2. Erzwingen Sie ein erneutes Laden. 3. Verwenden Sie types.FunctionType (...), um den zu konvertieren importierte und gebundene Methode an eine Funktion Sie können auch die aktuellen globalen Variablen weitergeben, da sich die neu geladene Methode in einem anderen Namespace befindet. 4. Jetzt können Sie wie von "Jason Pratt" vorgeschlagen mit den types.MethodType (... )

Beispiel:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code
Max
quelle
3

Wenn es hilfreich sein kann, habe ich kürzlich eine Python-Bibliothek namens Gorilla veröffentlicht, um das Patchen von Affen bequemer zu gestalten.

Die Verwendung einer Funktion needle()zum Patchen eines Moduls mit dem Namen guineapiglautet wie folgt:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

Es werden aber auch interessantere Anwendungsfälle behandelt, wie in den FAQ aus der Dokumentation gezeigt .

Der Code ist auf GitHub verfügbar .

ChristopherC
quelle
3

Diese Frage wurde vor Jahren gestellt, aber hey, es gibt eine einfache Möglichkeit, die Bindung einer Funktion an eine Klasseninstanz mithilfe von Dekoratoren zu simulieren:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()

Wenn Sie dort die Funktion und die Instanz an den Ordnerdekorateur übergeben, wird dort eine neue Funktion mit demselben Codeobjekt wie das erste erstellt. Anschließend wird die angegebene Instanz der Klasse in einem Attribut der neu erstellten Funktion gespeichert. Der Dekorateur gibt eine (dritte) Funktion zurück, die automatisch die kopierte Funktion aufruft und die Instanz als ersten Parameter angibt.

Abschließend erhalten Sie eine Funktion, die simuliert, dass sie an die Klasseninstanz gebunden ist. Die ursprüngliche Funktion bleibt unverändert.

gelegen
quelle
2

Was Jason Pratt gepostet hat, ist richtig.

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

Wie Sie sehen können, betrachtet Python b () nicht anders als a (). In Python sind alle Methoden nur Variablen, die zufällig Funktionen sind.

Akuminieren
quelle
7
Sie patchen die Klasse Test, keine Instanz davon.
Ethan Furman
Sie fügen einer Klasse eine Methode hinzu, keine Objektinstanz.
TomSawyer
2

Ich finde es seltsam, dass niemand erwähnt hat, dass alle oben aufgeführten Methoden eine Zyklusreferenz zwischen der hinzugefügten Methode und der Instanz erstellen, wodurch das Objekt bis zur Speicherbereinigung persistent bleibt. Es gab einen alten Trick beim Hinzufügen eines Deskriptors durch Erweitern der Klasse des Objekts:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
Yu Feng
quelle
2
from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

Hiermit können Sie den Selbstzeiger verwenden

Arturo Morales Rangel
quelle