Risposte:
In Python c'è una differenza tra funzioni e metodi associati.
>>> 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>>
>>>
I metodi associati sono stati "associati" (in modo descrittivo) a un'istanza e tale istanza verrà passata come primo argomento ogni volta che viene chiamato il metodo.
I callable che sono attributi di una classe (al contrario di un'istanza) sono comunque non associati, quindi puoi modificare la definizione della classe ogni volta che vuoi:
>>> 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
Anche le istanze definite in precedenza vengono aggiornate (purché non abbiano sovrascritto l'attributo stesso):
>>> a.fooFighters()
fooFighters
Il problema si presenta quando si desidera collegare un metodo a una singola istanza:
>>> 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)
La funzione non è automaticamente associata quando è collegata direttamente a un'istanza:
>>> a.barFighters
<function barFighters at 0x00A98EF0>
Per associarlo, possiamo usare la funzione MethodType nel modulo types :
>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters
Questa volta altre istanze della classe non sono state interessate:
>>> a2.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'
Ulteriori informazioni possono essere trovate leggendo i descrittori e la programmazione delle metaclasse .
descriptor protocol
vs creando un vantaggio MethodType
forse un po 'più leggibile.
classmethod
e staticmethod
e altri descrittori. Evita di ingombrare lo spazio dei nomi con un'altra importazione.
a.barFighters = barFighters.__get__(a)
Il nuovo modulo è obsoleto a partire da Python 2.6 e rimosso in 3.0, usa i tipi
vedi http://docs.python.org/library/new.html
Nell'esempio seguente ho deliberatamente rimosso il valore restituito dalla patch_me()
funzione. Penso che dare valore restituito possa far credere che patch ritorni un nuovo oggetto, il che non è vero - modifica quello in arrivo. Probabilmente questo può facilitare un uso più disciplinato del monkeypatching.
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'>
Prefazione - una nota sulla compatibilità: altre risposte possono funzionare solo in Python 2 - questa risposta dovrebbe funzionare perfettamente in Python 2 e 3. Se si scrive solo Python 3, si potrebbe tralasciare di ereditare esplicitamente object
, ma altrimenti il codice dovrebbe rimanere lo stesso .
Aggiunta di un metodo a un'istanza di oggetto esistente
Ho letto che è possibile aggiungere un metodo a un oggetto esistente (es. Non nella definizione della classe) in Python.
Capisco che non è sempre una buona decisione farlo. Ma come si fa?
Non lo consiglio. Questa è una cattiva idea. Non farlo
Ecco un paio di motivi:
Pertanto, ti suggerisco di non farlo a meno che tu non abbia una buona ragione. È molto meglio definire il metodo corretto nella definizione della classe o, meno preferibilmente, applicare una patch alla classe direttamente in questo modo:
Foo.sample_method = sample_method
Dato che è istruttivo, tuttavia, ti mostrerò alcuni modi per farlo.
Ecco un po 'di codice di installazione. Abbiamo bisogno di una definizione di classe. Potrebbe essere importato, ma non importa.
class Foo(object):
'''An empty class to demonstrate adding a method to an instance'''
Crea un'istanza:
foo = Foo()
Crea un metodo per aggiungerlo:
def sample_method(self, bar, baz):
print(bar + baz)
__get__
Le ricerche punteggiate sulle funzioni chiamano il __get__
metodo della funzione con l'istanza, associando l'oggetto al metodo e creando così un "metodo associato".
foo.sample_method = sample_method.__get__(foo)
e adesso:
>>> foo.sample_method(1,2)
3
Innanzitutto, importare i tipi, da cui otterremo il costruttore del metodo:
import types
Ora aggiungiamo il metodo all'istanza. Per fare ciò, richiediamo il costruttore MethodType dal types
modulo (che abbiamo importato sopra).
La firma dell'argomento per types.MethodType è (function, instance, class)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
e utilizzo:
>>> foo.sample_method(1,2)
3
Innanzitutto, creiamo una funzione wrapper che associa il metodo all'istanza:
def bind(instance, method):
def binding_scope_fn(*args, **kwargs):
return method(instance, *args, **kwargs)
return binding_scope_fn
utilizzo:
>>> foo.sample_method = bind(foo, sample_method)
>>> foo.sample_method(1,2)
3
Una funzione parziale applica il primo argomento (i) a una funzione (e facoltativamente argomenti di parole chiave) e può essere successivamente chiamata con gli argomenti rimanenti (e sostituendo argomenti di parole chiave). Così:
>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3
Ciò ha senso se si considera che i metodi associati sono funzioni parziali dell'istanza.
Se proviamo ad aggiungere sample_method nello stesso modo in cui potremmo aggiungerlo alla classe, non è associato all'istanza e non considera il sé implicito come primo argomento.
>>> 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)
Possiamo far funzionare la funzione non associata passando esplicitamente l'istanza (o qualsiasi altra cosa, poiché questo metodo in realtà non utilizza la self
variabile argomento), ma non sarebbe coerente con la firma prevista di altre istanze (se stiamo correggendo le scimmie questa istanza):
>>> foo.sample_method(foo, 1, 2)
3
Ora sapete diversi modi si può fare questo, ma in tutta serietà - non si esegue questa.
__get__
metodo ha bisogno anche la classe come il parametro successivo: sample_method.__get__(foo, Foo)
.
Penso che le risposte di cui sopra abbiano mancato il punto chiave.
Diamo una classe con un metodo:
class A(object):
def m(self):
pass
Ora, giochiamo con esso in ipython:
In [2]: A.m
Out[2]: <unbound method A.m>
Ok, quindi m () diventa in qualche modo un metodo non legato di A . Ma è davvero così?
In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
Si scopre che m () è solo una funzione, riferimento al quale viene aggiunto al dizionario di classe A - non c'è magia. Allora perché Am ci offre un metodo illimitato? È perché il punto non è tradotto in una semplice ricerca nel dizionario. È di fatto una chiamata di classe A .__ __.__ 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
Ora, non sono sicuro dalla cima della mia testa perché l'ultima riga è stampata due volte, ma è ancora chiaro cosa sta succedendo lì.
Ora, ciò che fa il __getattribute__ predefinito è che controlla se l'attributo è un cosiddetto descrittore o meno, cioè se implementa un metodo speciale __get__. Se implementa quel metodo, ciò che viene restituito è il risultato della chiamata a quel metodo __get__. Tornando alla prima versione della nostra classe A , questo è ciò che abbiamo:
In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>
E poiché le funzioni Python implementano il protocollo descrittore, se vengono chiamate per conto di un oggetto, si legano a quell'oggetto nel loro metodo __get__.
Ok, quindi come aggiungere un metodo a un oggetto esistente? Supponendo che non ti dispiaccia la lezione di patch, è semplice come:
B.m = m
Quindi Bm "diventa" un metodo non associato, grazie al descrittore magico.
E se vuoi aggiungere un metodo solo a un singolo oggetto, allora devi emulare il macchinario tu stesso, usando types.MethodType:
b.m = types.MethodType(m, b)
A proposito:
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'>
In Python il patching delle scimmie generalmente funziona sovrascrivendo una firma di classe o di funzioni con la tua. Di seguito è riportato un esempio dal Wiki di Zope :
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
Quel codice sovrascriverà / creerà un metodo chiamato speak sulla classe. Nel recente post di Jeff Atwood sul patching delle scimmie . Mostra un esempio in C # 3.0 che è la lingua corrente che uso per lavoro.
È possibile utilizzare lambda per associare un metodo a un'istanza:
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()
Produzione:
This is instance string
Esistono almeno due modi per collegare un metodo a un'istanza senza 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>>
Link utili:
Modello di dati - invocazione di descrittori
Descrittore HowTo Guide - invocazione di descrittori
Quello che stai cercando è setattr
credo. Usalo per impostare un attributo su un oggetto.
>>> 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>
A
, non l'istanza a
.
setattr(A,'printme',printme)
invece di semplicemente A.printme = printme
?
Poiché questa domanda ha richiesto versioni non Python, ecco JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
Consolidando le risposte di Jason Pratt e della wiki della community, con uno sguardo ai risultati di diversi metodi di associazione:
Soprattutto notare come l'aggiunta la funzione di legame come metodo di classe opere , ma la portata riferimento non è corretto.
#!/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>>
Personalmente, preferisco il percorso della funzione ADDMETHOD esterna, in quanto mi consente di assegnare dinamicamente nuovi nomi di metodo all'interno di un iteratore.
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>>
addmethod
riscritto nel modo seguente def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )
risolve il problema
Sebbene la risposta di Jasons funzioni, funziona solo se si desidera aggiungere una funzione a una classe. Non ha funzionato per me quando ho provato a ricaricare un metodo già esistente dal file di codice sorgente .py.
Mi ci sono voluti anni per trovare una soluzione alternativa, ma il trucco sembra semplice ... 1.st importare il codice dal file del codice sorgente 2.nd forzare una ricarica 3.rd use types.FunctionType (...) per convertire il metodo importato e associato a una funzione che puoi anche trasmettere sulle variabili globali correnti, poiché il metodo ricaricato si troverà in un altro spazio dei nomi 4. Ora puoi continuare come suggerito da "Jason Pratt" usando i tipi. )
Esempio:
# 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
Se può essere di qualche aiuto, di recente ho rilasciato una libreria Python di nome Gorilla per rendere più conveniente il processo di patching delle scimmie.
L'uso di una funzione needle()
per patchare un modulo chiamato è guineapig
il seguente:
import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
print("awesome")
Ma si occupa anche di casi d'uso più interessanti, come mostrato nelle FAQ dalla documentazione .
Il codice è disponibile su GitHub .
Questa domanda è stata aperta anni fa, ma ehi, c'è un modo semplice per simulare l'associazione di una funzione a un'istanza di classe usando i decoratori:
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 ()
Lì, quando si passa la funzione e l'istanza al decoratore del raccoglitore, verrà creata una nuova funzione, con lo stesso oggetto codice del primo. Quindi, l'istanza data della classe viene archiviata in un attributo della funzione appena creata. Il decoratore restituisce una (terza) funzione chiamando automaticamente la funzione copiata, fornendo l'istanza come primo parametro.
In conclusione si ottiene una funzione che simula il suo legame con l'istanza della classe. Lasciare invariata la funzione originale.
Ciò che Jason Pratt ha pubblicato è corretto.
>>> 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'>
Come puoi vedere, Python non considera b () diverso da a (). In Python tutti i metodi sono solo variabili che sembrano essere funzioni.
Test
, non un'istanza di essa.
Trovo strano che nessuno abbia menzionato che tutti i metodi sopra elencati creano un riferimento di ciclo tra il metodo aggiunto e l'istanza, facendo sì che l'oggetto sia persistente fino alla garbage collection. C'era un vecchio trucco per aggiungere un descrittore estendendo la classe dell'oggetto:
def addmethod(obj, name, func):
klass = obj.__class__
subclass = type(klass.__name__, (klass,), {})
setattr(subclass, name, func)
obj.__class__ = subclass
from types import MethodType
def method(self):
print 'hi!'
setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )
Con questo, è possibile utilizzare il puntatore automatico
MethodType
, invocare manualmente il protocollo descrittore e fare in modo che la funzione produca la propria istanza:barFighters.__get__(a)
produce un metodobarFighters
associato per il bindinga
.