Agregar un método a una instancia de objeto existente

votos
488

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

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

Publicado el 04/08/2008 a las 03:17
fuente por usuario
En otros idiomas...                            


18 respuestas

votos
736

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
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>

Los métodos enlazados han sido "vinculados" (cómo descriptivos) 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 enlazados, por lo que puede modificar la definición de la clase cuando lo desee:

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

Las instancias previamente definidas también se actualizan (siempre que no hayan reemplazado el atributo por sí mismas):

>>> 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 "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)

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

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

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

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

Esta vez otras instancias de la clase no se han visto afectadas:

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

Se puede encontrar más información leyendo sobre descriptores y programación de metaclases .

Respondida el 06/08/2008 a las 01:33
fuente por usuario

votos
80

El módulo nuevo está en desuso desde python 2.6 y eliminado en 3.0, use types

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

En el siguiente ejemplo, he eliminado deliberadamente el valor devuelto de la patch_me()función. Creo que dar un valor de retorno puede hacer creer que el parche devuelve un nuevo objeto, lo que no es cierto, sino que modifica el que está entrando. Probablemente esto pueda facilitar un uso más disciplinado del monopatching.

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'>
Respondida el 06/06/2009 a las 06:31
fuente por usuario

votos
47

Adición de un método para 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 es siempre una buena decisión de hacerlo. Pero, ¿cómo puede uno hacer esto?

Sí, es posible - pero no es recomendable

No recomiendo esto. Esta es una mala idea. No lo haga.

Aquí hay un par de razones:

  • Vamos a añadir un objeto unido a cada instancia haces esto. Si hace esto una gran cantidad, es probable que pierda una gran cantidad de memoria. Métodos vinculados normalmente sólo se crearon por la corta duración de su llamada, y entonces dejan de existir cuando el recolector de basura de forma automática. Si lo hace manualmente, tendrá un nombre vinculante referencia al método vinculado - lo que impedirá su recolección de basura en el uso.
  • Instancias de objetos de un determinado tipo generalmente tienen sus métodos en todos los objetos de ese tipo. Si se agrega métodos otra parte, algunos casos tendrán estos métodos y otros no. Los programadores no esperarán que esto, y corre el riesgo de violar la regla de la menor sorpresa .
  • Puesto que hay otras muy buenas razones para no hacer esto, además, usted mismo va a dar una mala reputación si lo hace.

Por lo tanto, sugiero que no hacer esto a menos que tenga una muy buena razón. Es mucho mejor para definir el método correcto en la definición de clase o menos preferiblemente a mono-patch la clase directamente, como este:

Foo.sample_method = sample_method

Puesto que es instructivo, sin embargo, voy a mostrar algunas maneras de hacer esto.

¿Cómo se puede hacer

Aquí hay un código de configuración. Necesitamos una definición de clase. Podría ser importado, pero realmente no importa.

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

Crear una instancia:

foo = Foo()

Crear un método para agregar a la misma:

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

nada Método (0) - utilizar el método de descriptor, __get__

Operaciones de búsqueda de puntos en funciones llaman el __get__método de la función con el ejemplo, la unión del objeto que el método y creando así un "método vinculado".

foo.sample_method = sample_method.__get__(foo)

y ahora:

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

Método uno - types.MethodType

En primer lugar, los tipos de importación, de la que obtendrá el método constructor:

import types

Ahora añadimos el método de la instancia. Para ello, se requiere el constructor tipoMetodo del typesmódulo (que importamos más arriba).

El argumento a favor de la firma types.MethodType es (function, instance, class):

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

y su uso:

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

Método dos: léxico de unión

En primer lugar, creamos una función de contenedor que se fija el método de 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 se aplica el primer argumento (s) a una función (y argumentos opcionalmente de palabras clave), y más tarde se puede llamar con el resto de argumentos (y argumentos de palabra clave primordiales). Así:

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

Esto tiene sentido si tenemos en cuenta que los métodos consolidados son funciones parciales de la instancia.

Sin consolidar la función como un atributo de objeto - por qué esto no funciona:

Si tratamos de añadir el sample_method de la misma manera como podríamos añadirlo a la clase, es no unido de la instancia, y no toma el auto implícita como primer argumento.

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

Podemos hacer el trabajo de la función no unido al pasar de forma explícita la instancia (o cualquier otra cosa, ya que este método no utiliza realmente la selfvariable de argumento), pero no sería coherente con la firma esperada de otras instancias (si estamos mono-parcheo este ejemplo):

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

Conclusión

Ahora ya sabe varias formas en las que se podía hacer esto, pero con toda seriedad - no lo hace.

Respondida el 21/01/2015 a las 05:31
fuente por usuario

votos
30

Creo que las respuestas anteriores se perdió el punto clave.

Vamos a tener una clase con un método:

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

Ahora, vamos a jugar con él en ipython:

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

Ok, por lo que m () de alguna manera se convierte en un método no unido de A . Pero ¿es realmente así?

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

Resulta que m () es sólo una función, la referencia a la que se añade a un diccionario de la clase - no hay magia. Entonces, ¿por Am nos da un método no unido? Es porque el punto no se traduce a una simple búsqueda de diccionario. Es de hecho una llamada de una clase __.__ __ 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

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

Ahora, lo que la hace __getattribute__ por defecto es que se comprueba si el atributo es un llamado descriptor o no, es decir si se implementa un método __get__ especial. Si se implementa este método, entonces lo que se devuelve es el resultado de llamar a ese método __get__. Volviendo a la primera versión de nuestro Una clase, esto es lo que tenemos:

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

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

La autorización, así como añadir un método a un objeto existente? Asumiendo que no importa la clase de parches, es tan simple como:

B.m = m

Entonces Bm "se convierte en" un método no unido, gracias a la magia de descriptores.

Y si desea agregar un método sólo para un único objeto, entonces usted tiene que emular la maquinaria a sí mismo, mediante el uso de types.MethodType:

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

Por cierto:

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'>
Respondida el 22/01/2012 a las 15:20
fuente por usuario

votos
16

En Python, el parche de mono generalmente funciona sobrescribiendo una clase o firma de funciones con la suya. A continuación se muestra un ejemplo de Zope Wiki :

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 speak en la clase. En la reciente publicación de Jeff Atwood sobre el parche de monos . Él muestra un ejemplo en C # 3.0 que es el idioma actual que uso para el trabajo.

Respondida el 04/08/2008 a las 03:31
fuente por usuario

votos
9

Hay al menos dos formas de adjuntar un método para una instancia sin 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>>

Enlaces de interés:
modelo de datos - que invocan descriptores
Guía descriptor HowTo - descriptores que invocan

Respondida el 26/04/2013 a las 16:47
fuente por usuario

votos
7

Puede utilizar lambda para unirse a un método de 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()

Este es el string instancia

Proceso terminado con código de salida 0

Respondida el 21/07/2014 a las 13:55
fuente por usuario

votos
6

Dado que esta pregunta para las versiones no-Python, aquí está JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
Respondida el 09/03/2012 a las 16:07
fuente por usuario

votos
6

Lo que estás buscando es setattryo 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>
Respondida el 07/08/2008 a las 12:30
fuente por usuario

votos
5

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

A pesar de Jason responder a las obras, que sólo funciona si se quiere agregar una función a una clase. No funcionó para mí cuando trataba de recargar un método ya existente en el archivo de código fuente .py.

Me tomó durante siglos para encontrar una solución, pero el truco parece simple ... 1.st importar el código del archivo de código fuente 2.º forzar una recarga types.FunctionType uso 3.a (...) para convertir el importado y método vinculado a una función también puede pasar en las variables globales actuales, como el método sería recargado en un espacio de nombres 4.th diferente ahora puede continuar según lo sugerido por "Jason Pratt" utilizando el types.MethodType (... )

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
Respondida el 18/08/2015 a las 15:32
fuente por usuario

votos
5

Ustedes realmente debe mirar fruta prohibida , es una biblioteca de Python que proporciona apoyo a los monos de reparación para cualquier clase de Python, incluso cuerdas.

Respondida el 25/08/2013 a las 22:56
fuente por usuario

votos
5

La consolidación de Jason Pratt y el wiki de la comunidad respuestas, con una mirada a los resultados de los diferentes métodos de unión:

Especialmente en cuenta cómo la adición de la función de unión como un método de clase funciona , pero el alcance referencia es incorrecta.

#!/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, prefiero la ruta addMethod función externa, ya que me permite asignar dinámicamente nuevos nombres de método dentro de un iterador también.

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>>
Respondida el 28/01/2012 a las 01:12
fuente por usuario

votos
4

Lo que Jason Pratt publicó es correcto.

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

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.

Respondida el 22/08/2008 a las 15:40
fuente por usuario

votos
3

Si puede ser de alguna ayuda, recientemente he publicado una biblioteca de Python llamado gorila para hacer el proceso de parcheo mono más conveniente.

Utilizando una función needle()para parchear un módulo denominado guineapigva como sigue:

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

Pero también se encarga de los casos de uso más interesantes, como se muestra en el FAQ de la documentación .

El código está disponible en GitHub .

Respondida el 15/07/2014 a las 03:12
fuente por usuario

votos
2

Me parece extraño que nadie mencionó que todos los métodos mencionados anteriormente crea una referencia de ciclo entre el método agregado y el ejemplo, haciendo que el objeto sea persistente hasta la recolección de basura. Había un viejo truco de añadir un descriptor extendiendo la clase del objeto:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
Respondida el 30/04/2017 a las 01:57
fuente por usuario

votos
2

Esta pregunta se abrió hace años, pero bueno, hay una manera fácil de simular la unión de una función a una instancia de la clase utilizando 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 se pasa a la función y la instancia al decorador aglutinante, se creará una nueva función, con el mismo objeto de código como el primero. A continuación, la instancia de la clase dada es almacenada en un atributo de la función de nueva creación. El decorador de regresar de una función (tercero) activar automáticamente la función de copiado, dando la instancia como primer parámetro.

En conclusión se obtiene una función de simulación se unión a la instancia de clase. Dejar que la función original sin cambios.

Respondida el 21/12/2015 a las 18:39
fuente por usuario

votos
1
from types import MethodType

def method(self):
   print 'hi!'


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

Con esto, se puede utilizar el puntero del auto

Respondida el 27/07/2017 a las 01:21
fuente por usuario

votos
-8

No conozco la sintaxis de Python, pero sé que Ruby puede hacerlo, y es bastante trivial. Supongamos que desea agregar un método a la matriz que imprime la longitud a la salida estándar:

class Array
  def print_length
    puts length
  end
end

Si no desea modificar toda la clase, puede simplemente agregar el método a una sola instancia de la matriz, y ninguna otra matriz tendrá el método:

array = [1, 2, 3]
def array.print_length
  puts length
end

Solo tenga en cuenta los problemas relacionados con el uso de esta función. Jeff Atwood realmente escribió sobre esto no hace mucho tiempo.

Respondida el 04/08/2008 a las 03:36
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more