Respuestas:
En Python, hay una diferencia entre funciones y 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 se han "enlazado" (cuán descriptivo) a una instancia, y esa instancia se pasará como primer argumento cada vez que se llame al método.
Sin embargo, las llamadas que son atributos de una clase (a diferencia de una instancia) aún no están vinculadas, 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 anulado el atributo):
>>> 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 vincularlo, 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'
Puede encontrar más información leyendo descriptores y programación de metaclases .
descriptor protocol
vs creando un MethodType
lado de quizás ser un poco más legible.
classmethod
y staticmethod
y otros descriptores también. Evita abarrotar el espacio de nombres con otra importación más.
a.barFighters = barFighters.__get__(a)
Módulo nuevo en desuso desde python 2.6 y eliminado en 3.0, use tipos
ver http://docs.python.org/library/new.html
En el siguiente ejemplo, he eliminado deliberadamente el valor de retorno de la patch_me()
función. 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 de parches 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 compatibilidad: otras respuestas solo pueden funcionar en Python 2 - esta respuesta debería funcionar perfectamente en Python 2 y 3. Si solo escribe Python 3, puede dejar de lado explícitamente la herencia object
, pero de lo contrario el código debería permanecer igual .
Agregar 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?
No recomiendo esto Esta es una mala idea. No lo hagas
Aquí hay un par de razones:
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, de esta manera:
Foo.sample_method = sample_method
Sin embargo, dado que es instructivo, te mostraré algunas formas de hacerlo.
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)
__get__
Las búsquedas punteadas en funciones llaman al __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
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, requerimos el constructor MethodType del types
módulo (que importamos anteriormente).
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
Primero, creamos una función de contenedor 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
Una función parcial aplica el (los) primer (s) argumento (s) a una función (y opcionalmente argumentos de palabras clave), y luego se puede invocar con los argumentos restantes (y anulando los argumentos de palabras clave). 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.
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 self 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 explícitamente pasando la instancia (o cualquier cosa, ya que este método en realidad no usa la self
variable de argumento), pero no sería coherente con la firma esperada de otras instancias (si estamos parcheando mono) esta instancia):
>>> foo.sample_method(foo, 1, 2)
3
Ahora ya sabe varias formas en las que se podía hacer esto, pero con toda seriedad - no lo hace.
__get__
método también necesita la clase que el siguiente parámetro: sample_method.__get__(foo, Foo)
.
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, 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, referencia a la cual se agrega al 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 simple búsqueda en el diccionario. Es de facto una llamada de A .__ 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 por qué la última línea se imprime dos veces, pero aún está claro lo que está sucediendo allí.
Ahora, lo que hace el __getattribute__ predeterminado es verificar si el atributo es un llamado 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]: <unbound method A.m>
Y debido a que las funciones de Python implementan el protocolo 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 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 solo objeto, entonces debe emular la maquinaria usted 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'>
En Python, el parcheo de mono generalmente funciona sobrescribiendo una firma de clase o 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 parches de monos . Muestra un ejemplo en C # 3.0, que es el lenguaje actual que uso para el trabajo.
Puede 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()
Salida:
This is instance string
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 descriptiva de
procedimientos: invocación de descriptores
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>
A
, no la instancia a
.
setattr(A,'printme',printme)
lugar de simplemente A.printme = printme
?
Como esta pregunta solicitaba versiones que no fueran de Python, aquí está JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
Consolidando las respuestas del wiki de Jason Pratt y de la comunidad, con un vistazo a los resultados de diferentes métodos de enlace:
Especialmente en cuenta cómo la adición de la función de unión como un método de clase de obras , 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 externa de la función ADDMETHOD, ya que me permite asignar dinámicamente nuevos nombres de métodos 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>>
addmethod
reescrito de la siguiente manera def addmethod(self, method, name): self.__dict__[name] = types.MethodType( method, self )
resuelve el problema
Aunque la respuesta de Jasons funciona, solo funciona si uno quiere agregar una función a una clase. No funcionó para mí cuando intenté volver a cargar un método ya existente desde el archivo de código fuente .py.
Me llevó mucho tiempo encontrar una solución, pero el truco parece simple ... 1. importar el código del archivo de código fuente 2. y forzar una recarga 3. 3. usar tipos. FunctionType (...) para convertir el código 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. Ahora puede continuar como lo sugiere "Jason Pratt" usando los tipos. 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
Si puede ser de alguna ayuda, recientemente lancé una biblioteca de Python llamada Gorilla para hacer que el proceso de parchear mono sea más conveniente.
El uso de una 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 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 carpetas, 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 que está vinculada a la instancia de clase. Dejar la función original sin cambios.
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 a a (). En Python, todos los métodos son solo variables que resultan ser funciones.
Test
, no una instancia de ella.
Me resulta 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 que agregaba 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 usar el puntero automático
MethodType
, invoque el protocolo descriptor manualmente y haga que la función produzca su instancia:barFighters.__get__(a)
produce un métodobarFighters
enlazado para enlazadoa
.