Pregunta

Leí 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 se podría hacer esto?

¿Fue útil?

Solución

En Python, existe una diferencia entre funciones y métodos vinculados.

>>> 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 vinculados han sido "vinculados" (qué descriptivos) a una instancia, y esa instancia se pasará como primer argumento cada vez que se llame al método.

Sin embargo, los invocables que son atributos de una clase (a diferencia de una instancia) aún no están vinculados, por lo que puedes modificar la definición de clase cuando quieras:

>>> 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 definidas previamente también se actualizan (siempre que no hayan anulado el atributo):

>>> a.fooFighters()
fooFighters

El problema surge cuando quieres 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 vincularlo, podemos usar el 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'

Puede encontrar más información leyendo sobre descriptores y metaclase programación.

Otros consejos

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

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

En el siguiente ejemplo, eliminé deliberadamente el valor de retorno de patch_me() función.Creo que dar un valor de retorno puede hacer creer que el parche devuelve un nuevo objeto, lo cual no es cierto: modifica el entrante.Probablemente esto pueda facilitar un uso más disciplinado del parche de mono.

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

Prefacio: una nota sobre la compatibilidad:Es posible que otras respuestas solo funcionen en Python 2; esta respuesta debería funcionar perfectamente en Python 2 y 3.Si escribe Python 3 únicamente, puede omitir explícitamente la herencia de object, pero por lo demás el código debería seguir siendo el mismo.

Agregar un método a una instancia de objeto existente

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

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

Sí, es posible, pero no recomendado.

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

Aquí hay un par de razones:

  • Agregará un objeto vinculado a cada instancia en la que haga esto.Si haces esto con frecuencia, probablemente desperdiciarás mucha memoria.Los métodos vinculados generalmente solo se crean durante el corto período de su llamada y luego dejan de existir cuando se recolecta automáticamente la basura.Si hace esto manualmente, tendrá un enlace de nombre que hace referencia al método enlazado, lo que evitará su recolección de basura durante el uso.
  • Las instancias de objetos de un tipo determinado generalmente tienen sus métodos en todos los objetos de ese tipo.Si agrega métodos en otro lugar, algunas instancias tendrán esos métodos y otras no.Los programadores no esperarán esto y usted corre el riesgo de violar las regla de la menor sorpresa.
  • Dado que existen otras muy buenas razones para no hacer esto, además te ganarás una mala reputación si lo haces.

Por lo tanto, le sugiero que no haga esto a menos que tenga una muy buena razón. Es mucho mejor definir el método correcto en la definición de clase. o menos preferiblemente parchear la clase directamente, así:

Foo.sample_method = sample_method

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

¿Cómo se puede hacer?

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

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

Crea una instancia:

foo = Foo()

Cree un método para agregarle:

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

Método cero (0): utilice el método del descriptor, __get__

Las búsquedas punteadas sobre funciones llaman a __get__ método de la función con la instancia, vinculando el objeto al 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: tipos.MethodType

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

import types

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

La firma del argumento para tipos.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:vinculación léxica

Primero, creamos una función contenedora que vincula 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.parcial

Una función parcial aplica los primeros argumentos a una función (y, opcionalmente, argumentos de palabras clave) y luego se puede llamar con los argumentos restantes (y los argumentos de palabras clave anulares).De este modo:

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

Esto tiene sentido si se considera que los métodos vinculados son funciones parciales de la instancia.

Función independiente como atributo de objeto: por qué esto no funciona:

Si intentamos agregar sample_method de la misma manera que lo agregaríamos a la clase, no está vinculado a la instancia y no toma el yo implícito 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 que la función independiente funcione pasando explícitamente la instancia (o cualquier cosa, ya que este método en realidad no usa el self variable de argumento), pero no sería consistente con la firma esperada de otras instancias (si estamos parcheando esta instancia):

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

Conclusión

Ahora conoces varias formas de podría Haz esto, pero con toda seriedad, no lo hagas.

Creo que las respuestas anteriores perdieron el punto clave.

Tengamos una clase con un método:

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

Ahora, juguemos con él en ipython:

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

OK entonces metro() de alguna manera se convierte en un método ilimitado de A.¿Pero es realmente así?

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

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

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

Ahora, lo que hace el __getattribute__ predeterminado es verificar si el atributo es uno de los llamados descriptor o no, es decirsi implementa un método __get__ especial.Si implementa ese 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 A 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 de descriptor, si se llaman en nombre de un objeto, se vinculan a ese objeto en su método __get__.

Bien, entonces, ¿cómo agregar un método a un objeto existente?Suponiendo que no te importe parchear la clase, es tan simple como:

B.m = m

Entonces bm "se convierte" en un método independiente, gracias a la magia del descriptor.

Y si desea agregar un método solo a un único objeto, entonces debe emular la maquinaria usted mismo, utilizando tipos.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'>

En Python, el parcheo de mono generalmente funciona sobrescribiendo la firma de una clase o función con la suya propia.A continuación se muestra un ejemplo de la Wiki 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 Jeff Atwood publicación reciente 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:

>>> 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 útiles:
Modelo de datos: invocación de descriptores
Guía práctica de descriptores: invocación de descriptores

Puedes usar lambda para vincular 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()

Producción:

This is instance string

Lo que estás buscando es setattr Yo creo.Utilícelo 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>

Dado que esta pregunta solicitaba versiones que no sean Python, aquí está JavaScript:

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

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

Tenga en cuenta especialmente cómo agregar la función de enlace como método de clase obras, pero el alcance 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 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 de la función ADDMETHOD externa, 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__ <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>>

Ustedes realmente deberían mirar fruta prohibida, es una biblioteca de Python que brinda soporte para parchear CUALQUIER clase de Python, incluso cadenas.

En realidad, esto es un complemento a la respuesta de "Jason Pratt".

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

Me tomó mucho tiempo encontrar una solución, pero el truco parece simple...1.st Importar el código desde el archivo del código fuente 2.nd Force a Reload 3.rd Usar tipos. El método recargado estaría en un espacio de nombres diferente 4.Th Now puede continuar según lo sugerido por "Jason Pratt" usando los tipos.methodype (...)

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 '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 sólo variables que resultan ser funciones.

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

Usando una función needle() parchear un módulo llamado guineapig va de la siguiente manera:

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 la Preguntas más frecuentes desde el documentación.

El código está disponible en GitHub.

Esta pregunta se abrió hace años, pero bueno, hay una manera fácil de simular el enlace 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 carpeta, creará una nueva función, con el mismo objeto de código que la primera.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 que llama automáticamente a la función copiada, dando la instancia como primer parámetro.

En conclusión, obtienes una función que simula su vinculación a la instancia de clase.Dejando la función original sin cambios.

Me parece extraño que nadie haya mencionado 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.Había un viejo truco para agregar 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
from types import MethodType

def method(self):
   print 'hi!'


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

Con esto, puedes usar el autopuntero.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top