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ò?

È stato utile?

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top