Python equivalente de Ruby ''method_missing''
oop metaprogramming (4)
¿Cuál es el equivalente de Python del método de method_missing
método de Ruby? Intenté usar __getattr__
pero este gancho se aplica también a los campos. Solo quiero interceptar las invocaciones del método. ¿Cuál es la forma Python de hacerlo?
No hay diferencia en Python entre las propiedades y los métodos. Un método es solo una propiedad, cuyo tipo es solo un método de instancemethod
, que resulta ser invocable (admite __call__
).
Si desea implementar esto, su método __getattr__
debería devolver una función (una lambda
o una def
regular, cualquiera sea el conjunto que necesite) y tal vez verificar algo después de la llamada.
Podría implementar una característica de falta de método como de la siguiente manera:
https://gist.github.com/gterzian/6400170
import unittest
from functools import partial
class MethodMissing:
def method_missing(self, name, *args, **kwargs):
''''''please implement''''''
raise NotImplementedError(''please implement a "method_missing" method'')
def __getattr__(self, name):
return partial(self.method_missing, name)
class Wrapper(object, MethodMissing):
def __init__(self, item):
self.item = item
def method_missing(self, name, *args, **kwargs):
if name in dir(self.item):
method = getattr(self.item, name)
if callable(method):
return method(*args, **kwargs)
else:
raise AttributeError('' %s has not method named "%s" '' % (self.item, name))
class Item(object):
def __init__(self, name):
self.name = name
def test(self, string):
return string + '' was passed on''
class EmptyWrapper(object, MethodMissing):
''''''not implementing a missing_method''''''
pass
class TestWrapper(unittest.TestCase):
def setUp(self):
self.item = Item(''test'')
self.wrapper = Wrapper(self.item)
self.empty_wrapper = EmptyWrapper()
def test_proxy_method_call(self):
string = self.wrapper.test(''message'')
self.assertEqual(string, ''message was passed on'')
def test_normal_attribute_not_proxied(self, ):
with self.assertRaises(AttributeError):
self.wrapper.name
self.wrapper.name()
def test_empty_wrapper_raises_error(self, ):
with self.assertRaises(NotImplementedError):
self.empty_wrapper.test(''message'')
if __name__ == ''__main__'':
unittest.main()
Python no distingue entre métodos y atributos (también conocidos como "variables de instancia") como lo hace Ruby. Los métodos y otros atributos de objetos se buscan exactamente de la misma manera en Python, ni siquiera Python conoce la diferencia en la etapa de búsqueda. Hasta que se encuentre el atributo, es solo una cadena.
Por lo tanto, si solicita una forma de garantizar que __getattr__
solo se __getattr__
por métodos, me temo que probablemente no encuentre una solución elegante. Pero es bastante fácil simplemente devolver una función (o incluso un nuevo método enlazado dinámicamente ) desde __getattr__
.
Aunque no lo recomiendo !!!!!!!!!!!!!!!!!!!!!
este tipo de se acerca más a la implementación del comportamiento de llamar al método especial para cada nombre que no corresponda a un atributo / método llamable. Por supuesto, todavía no tienen espacios de nombres separados, por lo que puede parecer un poco extraño. Funciona al anular __getattribute__
que funciona en un nivel inferior y luego __getattr__
intenta recuperar un atributo si falla, devuelve un método especial de curry para llamar con el nombre con el que lo llamó, si tiene éxito, lo pasa si es llamable. envuelve el resultado con un objeto proxy que actúa casi exactamente de la misma manera después, excepto que implementa la llamada con su método especial.
No le permite acceder al objeto llamante porque no podría pensar en una buena manera de hacerlo sin la clase de pérdida de memoria (el objeto llamante) si ya es un atributo no llamable que almacena (el único que creo es lo que se puede pensar es comenzar un nuevo hilo que lo elimine después de un minuto, para entonces probablemente lo haya llamado a menos que lo esté utilizando en un cierre que no sería compatible en ese caso).
Edit: Olvidé que la llamada puede tener algunos falsos positivos.
depende de la http://pypi.python.org/pypi/ProxyTypes library
from peak.util.proxies import ObjectWrapper
from functools import partial
def m(name, *args, **kwargs):
print(name,repr(args),repr(kwargs))
class CallProxy(ObjectWrapper):
def __init__(self, obj, m, method_name):
ObjectWrapper.__init__(self, obj)
object.__setattr__(self, "_method_name", method_name)
object.__setattr__(self, "_m", m)
def __call__(self, *args, **kwargs):
return self._m(self._method_name, *args,**kwargs)
class Y(object):
def __init__(self):
self.x = [3]
def __getattribute__(self, name):
try:
val = object.__getattribute__(self, name)
if not callable(val):
return CallProxy(val, m, name)
else:
return val
except AttributeError:
return partial(m, name)
In [2]: y=Y()
In [3]: y.x
Out[3]: [3]
In [4]: y.z
Out[4]: <functools.partial at 0x2667890>
In [5]: y.zz([12])
(''zz'', ''([12],)'', ''{}'')
In [6]: y.x.append(5)
In [7]: y.x
Out[7]: [3, 5]
In [8]: y.x(1,2,3,key="no")
(''x'', ''(2, 3)'', "{''key'': ''no''}")