Adición de un método a una instancia de objeto existente

He leído que es posible agregar un método a un objeto existente (es decir, no en la definición de clase) en Python.

Entiendo que no siempre es bueno hacerlo. Pero, ¿cómo podría uno hacer esto?

En Python, hay una diferencia entre las funciones y los métodos enlazados.

>>> def foo(): ... print "foo" ... >>> class A: ... def bar( self ): ... print "bar" ... >>> a = A() >>> foo  >>> a.bar > >>> 

Los métodos enlazados se han “enlazado” (de forma descriptiva) a una instancia, y esa instancia se pasará como el primer argumento cada vez que se llame al método.

Sin embargo, los callables que son atributos de una clase (a diferencia de una instancia) aún no están vinculados, por lo que puede modificar la definición de la clase siempre que lo desee:

 >>> def fooFighters( self ): ... print "fooFighters" ... >>> A.fooFighters = fooFighters >>> a2 = A() >>> a2.fooFighters > >>> a2.fooFighters() fooFighters 

Las instancias previamente definidas también se actualizan (siempre y cuando no hayan anulado el atributo en sí):

 >>> a.fooFighters() fooFighters 

El problema surge cuando desea adjuntar un método a una sola instancia:

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

La función no se enlaza automáticamente cuando se adjunta directamente a una instancia:

 >>> a.barFighters  

Para enlazarlo , podemos usar la función MethodType en el módulo de tipos :

 >>> import types >>> a.barFighters = types.MethodType( barFighters, a ) >>> a.barFighters > >>> a.barFighters() barFighters 

Esta vez otras instancias de la clase no han sido afectadas:

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

Se puede encontrar más información leyendo sobre descriptores y progtwigción de metaclase .

El módulo nuevo está en desuso desde Python 2.6 y se eliminó en 3.0, use tipos

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

En el siguiente ejemplo, he eliminado deliberadamente el valor de patch_me() función patch_me() . Creo que dar un valor de retorno puede hacer que uno crea que un parche devuelve un nuevo objeto, lo cual no es cierto: modifica el entrante. Probablemente esto puede facilitar un uso más disciplinado de la crianza de monos.

 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  

Prefacio – una nota sobre compatibilidad: otras respuestas solo pueden funcionar en Python 2 – esta respuesta debería funcionar perfectamente bien en Python 2 y 3. Si escribe Python 3 solo, puede dejar de heredar explícitamente el object , pero de lo contrario el código debería seguir siendo el código. mismo.

Adición de un método a una instancia de objeto existente

He leído que es posible agregar un método a un objeto existente (por ejemplo, no en la definición de clase) en Python.

Entiendo que no siempre es una buena decisión hacerlo. Pero, ¿cómo podría uno hacer esto?

Sí, es posible, pero no recomendado.

Yo no recomiendo esto. Esta es una mala idea. No lo hagas

Aquí hay un par de razones:

  • Agregará un objeto enlazado a cada instancia a la que le haga esto. Si haces esto mucho, probablemente desperdiciarás mucha memoria. Los métodos de enlace normalmente solo se crean para la corta duración de su llamada, y luego dejan de existir cuando se recolecta automáticamente la basura. Si lo hace manualmente, tendrá un enlace de nombre que hace referencia al método enlazado, lo que evitará su recolección de basura en el uso.
  • Las instancias de objetos de un tipo dado generalmente tienen sus métodos en todos los objetos de ese tipo. Si agrega métodos a otra parte, algunas instancias tendrán esos métodos y otras no. Los progtwigdores no esperan esto, y corres el riesgo de violar la regla de menos sorpresa .
  • Ya que existen otras buenas razones para no hacer esto, también te darás una mala reputación si lo haces.

Por lo tanto, sugiero que no hagas esto a menos que tengas una buena razón. Es mucho mejor definir el método correcto en la definición de la clase o, menos preferiblemente, parchear la clase directamente, de esta manera:

 Foo.sample_method = sample_method 

Sin embargo, como es instructivo, te mostraré algunas formas de hacerlo.

Como se puede hacer

Aquí hay un código de configuración. Necesitamos una definición de clase. Podría importarse, pero en realidad no importa.

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

Crear una instancia:

 foo = Foo() 

Crea un método para agregarlo:

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

Método nada (0) – use el método descriptor, __get__

Las búsquedas de puntos en las funciones llaman al método __get__ de la función con la instancia, vinculando el objeto al método y creando así un “método enlazado”.

 foo.sample_method = sample_method.__get__(foo) 

y ahora:

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

Método uno – tipos.Tipo de método

Primero, importa los tipos, de los cuales obtendremos el constructor del método:

 import types 

Ahora agregamos el método a la instancia. Para hacer esto, requerimos el constructor MethodType del módulo de types (que importamos arriba).

La firma del argumento para types.MethodType es (function, instance, class) :

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

y uso:

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

Método dos: unión léxica

Primero, creamos una función de envoltura que une el método a la instancia:

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

uso:

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

Método tres: functools.partial

Una función parcial aplica los primeros argumentos a una función (y, opcionalmente, los argumentos de palabras clave), y luego se puede llamar con los argumentos restantes (y los argumentos de palabras clave que prevalecen). Así:

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

Esto tiene sentido cuando considera que los métodos enlazados son funciones parciales de la instancia.

La función Unbound como atributo de objeto, por qué esto no funciona:

Si intentamos agregar sample_method de la misma manera que podríamos agregarlo a la clase, no está vinculado a la instancia y no toma el carácter implícito como primer argumento.

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

Podemos hacer que la función no enlazada funcione explícitamente pasando la instancia (o cualquier otra cosa, ya que este método no usa realmente la variable self argumento), pero no sería consistente con la firma esperada de otras instancias (si somos un mono) parcheando esta instancia):

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

Conclusión

Ahora conoce varias formas de hacerlo, pero con toda seriedad, no lo haga.

Creo que las respuestas anteriores perdieron el punto clave.

Tengamos una clase con un método:

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

Ahora, vamos a jugar con él en ipython:

 In [2]: Am Out[2]:  

Ok, entonces m () de alguna manera se convierte en un método independiente de A. ¿Pero es realmente así?

 In [5]: A.__dict__['m'] Out[5]:  

Resulta que m () es solo una función, referencia a la que se agrega a un diccionario de clase A , no hay magia. Entonces, ¿ por qué Am nos da un método independiente? Es porque el punto no se traduce a una búsqueda de diccionario simple. Es de facto una llamada de 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]: Am  - m  - m 

Ahora, no estoy seguro de por qué la última línea se imprime dos veces, pero aún queda claro lo que está pasando allí.

Ahora, lo que hace el __getattribute__ predeterminado es que comprueba si el atributo es un denominado descriptor o no, es decir, si implementa un método __get__ especial. Si implementa ese método, lo que se devuelve es el resultado de llamar a ese método __get__. Volviendo a la primera versión de nuestra clase A , esto es lo que tenemos:

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

Y debido a que las funciones de Python implementan el protocolo del descriptor, si se llaman en nombre de un objeto, se unen a ese objeto en su método __get__.

Ok, entonces, ¿cómo agregar un método a un objeto existente? Asumiendo que no te importa parchar la clase, es tan simple como:

 Bm = m 

Entonces Bm “se convierte” en un método independiente, gracias al descriptor magic.

Y si desea agregar un método solo a un solo objeto, entonces debe emular la maquinaria usted mismo, utilizando tipos.Tipo de método:

 bm = types.MethodType(m, b) 

Por cierto:

 In [2]: Am Out[2]:  In [59]: type(Am) Out[59]:  In [60]: type(bm) Out[60]:  In [61]: types.MethodType Out[61]:  

En Python Monkey, el parcheo generalmente funciona al sobrescribir una firma de clase o función con la suya. A continuación se muestra un ejemplo de la Wiki de Zope :

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

Ese código sobrescribirá / creará un método llamado hablar en la clase. En el reciente post de Jeff Atwood sobre parches de monos . Muestra un ejemplo en C # 3.0, que es el lenguaje actual que uso para trabajar.

Hay al menos dos formas de adjuntar un método a una instancia sin types.MethodType . types.MethodType :

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

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 > 

2:

 >>> instancemethod = type(Am) >>> instancemethod  >>> a.foo2 = instancemethod(foo, a, type(a)) >>> a.foo2() im foo, invoked with: <__main__.A instance at 0x973ec6c> >>> a.foo2 > 

Enlaces útiles:
Modelo de datos – descriptores de invocación
Guía HowTo del descriptor – invocar descriptores

Puedes usar lambda para enlazar un método a una instancia:

 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() 

Esta es la cadena de instancia

Proceso terminado con el código de salida 0

Lo que estás buscando es setattr , creo. Use esto para establecer un atributo en un objeto.

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

Ya que esta pregunta hizo para versiones que no son de Python, aquí está JavaScript:

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

Consolidando las respuestas de la comunidad de Jason Pratt y de la comunidad, con un vistazo a los resultados de los diferentes métodos de enlace:

Tenga en cuenta especialmente cómo agregar la función de enlace como un método de clase funciona , pero el scope de referencia es incorrecto.

 #!/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 br from {0} to {1}'.format(br, k) br = k print 'new br = {0}'.format(br) 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__ > # addmethod > # f0 > print '\nattributes of b:' listattr(b) # OUT: # attributes of b: # __init__ > # addmethod > # f0 > # f1 > # f2 > # f3 > # f4 > print '\nattributes of c:' listattr(c) # OUT: # attributes of c: # __init__ > # addmethod > # f0 > 

Personalmente, prefiero la ruta externa ADDMETHOD, ya que también me permite asignar dinámicamente nuevos nombres de métodos dentro de un iterador.

 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__ > # addmethod > # f0 > # f1 > # f2 > # f3 > # f4 > 

Ustedes realmente deberían mirar las frutas prohibidas , es una biblioteca de python que brinda soporte para parchar a MUCHOS de cualquier clase de python, incluso cadenas.

Esto es en realidad un complemento a la respuesta de “Jason Pratt”

Aunque la respuesta de Jasons funciona, solo funciona si uno quiere agregar una función a una clase. No me funcionó cuando intenté volver a cargar un método ya existente desde el archivo de código fuente .py.

Me tomó mucho tiempo encontrar una solución alternativa, pero el truco parece simple … 1.st Importar el código del archivo de código fuente 2.y forzar una recarga 3.er tipos de uso.FunctionType (…) para convertir el El método importado y vinculado a una función también puede pasar las variables globales actuales, ya que el método recargado estaría en un espacio de nombres diferente 4.a ahora puede continuar según lo sugerido por “Jason Pratt” usando los tipos.Tipo de método (… )

Ejemplo:

 # 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 

Lo que Jason Pratt publicó es correcto.

 >>> class Test(object): ... def a(self): ... pass ... >>> def b(self): ... pass ... >>> Test.b = b >>> type(b)  >>> type(Test.a)  >>> type(Test.b)  

Como puede ver, Python no considera que b () sea diferente de a (). En Python, todos los métodos son solo variables que resultan ser funciones.

Si puede ser de alguna ayuda, recientemente lancé una biblioteca de Python llamada Gorilla para que el proceso de parcheo de monos sea más conveniente.

El uso de la función needle() para parchear un módulo llamado guineapig es el siguiente:

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

Pero también se ocupa de casos de uso más interesantes, como se muestra en las Preguntas frecuentes de la documentación .

El código está disponible en GitHub .

Esta pregunta se abrió hace años, pero hey, hay una manera fácil de simular la vinculación de una función a una instancia de clase usando decoradores:

 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 () 

Allí, cuando pase la función y la instancia al decorador de carpetas, creará una nueva función, con el mismo objeto de código que el primero. Luego, la instancia dada de la clase se almacena en un atributo de la función recién creada. El decorador devuelve una (tercera) función llamando automáticamente a la función copiada, dando a la instancia el primer parámetro.

En conclusión, obtienes una función que simula que está vinculada a la instancia de clase. Dejar la función original sin cambios.

Me parece extraño que nadie mencione que todos los métodos enumerados anteriormente crean una referencia de ciclo entre el método agregado y la instancia, lo que hace que el objeto sea persistente hasta la recolección de basura. Hubo un viejo truco al agregar un descriptor al extender la clase del objeto:

 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 esto, puede utilizar el puntero a sí mismo.