Aggiunta di un metodo a un'istanza di oggetto esistente
-
08-06-2019 - |
Domanda
Ho letto che è possibile aggiungere un metodo a un oggetto esistente (cioè non nella definizione della classe) in Python.
Capisco che non sia sempre bene farlo.Ma come si potrebbe fare ciò?
Soluzione
In Python esiste 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 termini descrittivi) a un'istanza e tale istanza verrà passata come primo argomento ogni volta che viene chiamato il metodo.
Tuttavia, i chiamabili che sono attributi di una classe (al contrario di un'istanza) non sono ancora 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
Vengono aggiornate anche le istanze definite in precedenza (a condizione che non abbiano sovrascritto l'attributo stesso):
>>> a.fooFighters()
fooFighters
Il problema sorge quando vuoi allegare 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 viene automaticamente associata quando è collegata direttamente a un'istanza:
>>> a.barFighters
<function barFighters at 0x00A98EF0>
Per associarlo, possiamo usare il file Funzione MethodType nel modulo tipi:
>>> 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'
Maggiori informazioni possono essere trovate leggendo su descrittori E metaclasse programmazione.
Altri suggerimenti
Modulo nuovo è deprecato a partire da Python 2.6 e rimosso nella versione 3.0, utilizzare tipi
Vedere http://docs.python.org/library/new.html
Nell'esempio seguente ho deliberatamente rimosso il valore di ritorno da patch_me()
funzione.Penso che dare un valore restituito possa far credere che patch restituisca 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 potrebbero funzionare solo in Python 2: questa risposta dovrebbe funzionare perfettamente in Python 2 e 3.Se scrivi solo Python 3, potresti tralasciare esplicitamente l'ereditarietà from object
, ma per il resto 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 (ad es.non nella definizione della classe) in Python.
Capisco che non sia sempre una buona decisione farlo. Ma come si potrebbe fare ciò?
Sì, è possibile, ma non consigliato
Non lo consiglio.Questa è una cattiva idea.Non farlo.
Ecco un paio di motivi:
- Aggiungerai un oggetto associato a ogni istanza a cui esegui questa operazione.Se lo fai spesso, probabilmente sprecherai molta memoria.I metodi associati vengono in genere creati solo per la breve durata della chiamata e quindi cessano di esistere quando viene effettuato automaticamente il Garbage Collection.Se lo fai manualmente, avrai un'associazione del nome che fa riferimento al metodo associato, il che impedirà la raccolta dei rifiuti durante l'utilizzo.
- Le istanze di oggetti di un dato tipo generalmente hanno i propri metodi su tutti gli oggetti di quel tipo.Se aggiungi metodi altrove, alcune istanze avranno tali metodi e altre no.I programmatori non se lo aspetteranno e rischierai di violare il file regola della minima sorpresa.
- Dato che ci sono altri ottimi motivi per non farlo, se lo fai ti guadagnerai anche una cattiva reputazione.
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 per applicare direttamente la patch alla classe, in questo modo:
Foo.sample_method = sample_method
Poiché è istruttivo, tuttavia, ti mostrerò alcuni modi per farlo.
Come può essere fatto
Ecco del codice di configurazione.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 da aggiungere ad esso:
def sample_method(self, bar, baz):
print(bar + baz)
Metodo zero (0) - utilizza il metodo descrittore, __get__
Le ricerche punteggiate sulle funzioni chiamano il file __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
Metodo uno: tipi.MethodType
Innanzitutto, importa i tipi, da cui otterremo il costruttore del metodo:
import types
Ora aggiungiamo il metodo all'istanza.Per fare ciò, abbiamo bisogno del costruttore MethodType da types
module (che abbiamo importato sopra).
La firma dell'argomento per tipi.MethodType è (function, instance, class)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
e utilizzo:
>>> foo.sample_method(1,2)
3
Metodo due:legame lessicale
Per prima cosa creiamo una funzione wrapper che lega 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
Metodo tre:functools.partial
Una funzione parziale applica i primi argomenti a una funzione (e facoltativamente gli argomenti delle parole chiave) e può successivamente essere chiamata con gli argomenti rimanenti (e sovrascrivendo gli argomenti delle 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.
Funzione non associata come attributo dell'oggetto: perché non funziona:
Se proviamo ad aggiungere sample_method nello stesso modo in cui lo aggiungeremmo alla classe, non sarà associato all'istanza e non prenderà 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 il metodo self
variabile argomento), ma non sarebbe coerente con la firma prevista di altre istanze (se stiamo applicando una patch a questa istanza):
>>> foo.sample_method(foo, 1, 2)
3
Conclusione
Ora conosci diversi modi in cui tu Potevo fallo, ma in tutta serietà, non farlo.
Penso che le risposte di cui sopra abbiano mancato il punto chiave.
Creiamo una classe con un metodo:
class A(object):
def m(self):
pass
Ora giochiamo con ipython:
In [2]: A.m
Out[2]: <unbound method A.m>
Ok allora M() diventa in qualche modo un metodo illimitato di UN.Ma è davvero così?
In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
Si scopre che M() è solo una funzione, alla quale viene aggiunto un riferimento UN dizionario di classe: non c'è magia.Allora perché Sono ci fornisce un metodo non associato?È perché il punto non viene tradotto in una semplice ricerca nel dizionario.Di fatto è una chiamata di 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
Ora, non sono sicuro del motivo per cui l'ultima riga viene stampata due volte, ma è comunque chiaro cosa sta succedendo lì.
Ora, ciò che fa __getattribute__ predefinito è controllare se l'attributo è un cosiddetto descrittore oppure no, cioèse implementa uno speciale metodo __get__.Se implementa quel metodo, ciò che viene restituito è il risultato della chiamata a quel metodo __get__.Tornando alla prima versione del ns UN classe, 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 applicare patch alla classe, è semplice come:
B.m = m
Poi B.m "diventa" un metodo slegato, grazie alla magia del descrittore.
E se vuoi aggiungere un metodo solo a un singolo oggetto, allora devi emulare tu stesso il meccanismo, utilizzando 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 l'applicazione delle patch a scimmie generalmente funziona sovrascrivendo una firma di classe o di funzione con la propria.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.In Jeff Atwood post recente sull'applicazione di patch alle scimmie.Mostra un esempio in C# 3.0 che è il linguaggio corrente che utilizzo per lavoro.
Esistono almeno due modi per allegare 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 dei descrittori
Guida pratica ai descrittori: richiamare i descrittori
Puoi 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
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>
Poiché questa domanda è stata posta per versioni non Python, ecco JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
Consolidando le risposte wiki di Jason Pratt e della community, con uno sguardo ai risultati dei diversi metodi di rilegatura:
Nota in particolare come aggiungere la funzione di associazione come metodo di classe lavori, ma l'ambito di 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, poiché mi consente di assegnare dinamicamente anche 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>>
Ragazzi, dovreste davvero guardare frutto proibito, è una libreria Python che fornisce supporto all'applicazione di patch a QUALSIASI classe Python, anche alle stringhe.
Questa è in realtà un'aggiunta alla risposta di "Jason Pratt"
Sebbene la risposta di Jason 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 del 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 forze a ricarica 3.rd usi types.functiontype (...) Per convertire il metodo importato e associato in una funzione è possibile trasmettere anche le variabili globali attuali, come Il metodo ricaricato sarebbe in uno spazio dei nomi diverso 4. Ora puoi continuare come suggerito da "Jason Pratt" usando i tipi.MethodType (...)
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
Ciò che ha pubblicato Jason Pratt è 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.
Se può esserti d'aiuto, di recente ho rilasciato una libreria Python chiamata Gorilla per rendere più conveniente il processo di applicazione delle patch alle scimmie.
Utilizzando una funzione needle()
per patchare un modulo denominato guineapig
va come segue:
import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
print("awesome")
Ma si occupa anche di casi d'uso più interessanti, come mostrato nel file FAQ dal documentazione.
Il codice è disponibile su GitHub.
Questa domanda è stata aperta anni fa, ma ehi, esiste un modo semplice per simulare l'associazione di una funzione a un'istanza di classe utilizzando 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 passi la funzione e l'istanza al decoratore del raccoglitore, creerà una nuova funzione, con lo stesso oggetto codice della prima.Quindi, l'istanza data della classe viene memorizzata in un attributo della funzione appena creata.Il decoratore restituisce una (terza) funzione che chiama automaticamente la funzione copiata, fornendo l'istanza come primo parametro.
In conclusione ottieni una funzione che simula il suo legame con l'istanza della classe.Lasciando invariata la funzione originaria.
Trovo strano che nessuno abbia menzionato che tutti i metodi sopra elencati creano un riferimento ciclico tra il metodo aggiunto e l'istanza, rendendo l'oggetto persistente fino alla raccolta dei rifiuti.C'era un vecchio trucco che aggiungeva 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, puoi usare il puntatore automatico