Añadir un método a una instancia de objeto existente

python oop methods monkeypatching


He leído que es posible añadir 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 se puede hacer esto?




Answer 1 Jason Pratt


En Python,hay una diferencia entre las funciones y los métodos de unión.

>>> 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 "atados" (cuán descriptivos)a una instancia,y esa instancia se pasará como primer argumento siempre que se llame al método.

Sin embargo,las llamadas que son atributos de una clase (en contraposición a una instancia)siguen sin estar ligadas,por lo que se puede modificar la definición de la clase cuando se 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 y cuando no hayan anulado el atributo por sí mismas):

>>> a.fooFighters()
fooFighters

El problema viene cuando se quiere 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>

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

Esta vez,otros casos de la clase no se han visto afectados:

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




Answer 2 Evgeny


El 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, patch_me() deliberadamente el valor de retorno de la función patch_me () . Creo que dar un valor de retorno puede hacernos 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'>



Answer 3 Aaron Hall


Prefacio: una nota sobre compatibilidad: es posible que otras respuestas solo funcionen en Python 2; esta respuesta debería funcionar perfectamente en Python 2 y 3. Si solo escribe Python 3, puede omitir la herencia explícita del object , pero de lo contrario el código debería seguir siendo el mismo.

Añadir un método a una instancia de objeto existente

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

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

Sí,es posible-pero no recomendable

No recomiendo esto.Es una mala idea.No lo hagas.

Aquí hay un par de razones:

  • Añadirá un objeto encuadernado a cada instancia en la que haga esto.Si haces esto mucho,probablemente desperdicies mucha memoria.Los métodos atados se crean normalmente sólo para la corta duración de su llamada,y luego dejan de existir cuando se recoge automáticamente la basura.Si lo haces manualmente,tendrás un nombre de enlace que haga referencia al método de enlace,lo que evitará que se recoja la basura en el uso.
  • Las instancias de objeto 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 corre el riesgo de violar la regla de la menor sorpresa .
  • Ya que hay otras buenas razones para no hacerlo,además te darás una mala reputación si lo haces.

Por lo tanto, le sugiero que no haga esto a menos que tenga 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, así:

Foo.sample_method = sample_method

Sin embargo,ya que es instructivo,voy a mostrarles 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 ser importada,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 añadirlo:

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

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

Las búsquedas con puntos en 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 vinculado".

foo.sample_method = sample_method.__get__(foo)

y ahora:

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

Método uno-tipos.MétodoTipo

Primero,los tipos de importación,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 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 el uso:

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

Método dos:la 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

usage:

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

Método tres:functools.parcial

Una función parcial aplica el primer o los primeros argumentos a una función (y opcionalmente los argumentos de las palabras clave),y puede ser llamada más tarde con los argumentos restantes (y los argumentos de las palabras clave de anulación).De esta manera:

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

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

La función no ligada como un atributo del objeto-por qué esto no funciona:

Si intentamos añadir el método_de_muestra de la misma manera que podríamos añadirlo a la clase,no está ligado 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 la variable self argumento), pero no sería consistente con la firma esperada de otras instancias (si somos mono- parcheando esta instancia):

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

Conclusion

Ahora sabe varias formas en que podría hacer esto, pero con toda seriedad, no lo haga.




Answer 4 Tomasz Zieliński


Creo que las respuestas anteriores no dieron 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, 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 solo una función, cuya referencia se agrega al diccionario de la clase A ; no hay magia. Entonces, ¿ por qué Am nos da un método libre? 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

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

Ahora, lo que hace el __getattribute__ predeterminado es comprobar si el atributo es un denominado descriptor o no, es decir, si implementa un método especial __get__. 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]: <unbound method A.m>

Y como las funciones Python implementan el protocolo descriptor,si son llamadas en nombre de un objeto,se vinculan a ese objeto en su método __get__.

Bien,entonces,¿cómo se añade un método a un objeto existente? Asumiendo que no te importa parchear la clase,es tan simple como:

B.m = m

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

Y si quieres añadir un método a un solo objeto,entonces tienes que emular la maquinaria por ti mismo,usando 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'>



Answer 5 John Downey


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