2011-07-15 18 views

Respuesta

27

No hay diferencia en Python entre propiedades y métodos. Un método es solo una propiedad, cuyo tipo es solo instancemethod, que resulta ser invocable (admite __call__).

Si desea implementar esto, su método __getattr__ debe devolver una función (a lambda o un habitual def, sean cuales sean sus necesidades en suite) y tal vez comprobar algo después de la llamada.

+1

Gracias. Encontré [esto] (http://venodesigns.net/2010/11/02/emulating-rubys-method_missing-in-python/) en un pequeño google. – missingfaktor

+2

Como referencia, el enlace en cuestión elude la ambigüedad definiendo una lista de nombres de atributos que deben considerarse métodos (lo que suena como que es un poco contrario al propósito, ya que podría simplemente _definir_ cada uno de esos métodos y delegarlos en un stub). –

4

Python no distingue entre métodos y atributos (a.k.a. "variables de instancia") de la forma en que lo hace Ruby. Los métodos y otros atributos de objeto 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.

Así que si usted está pidiendo una manera de asegurar que __getattr__ es única llamada de métodos, me temo que probablemente no va a encontrar una solución elegante. Pero es bastante fácil simplemente devolver una función (o incluso una nueva marca dynamically bound method) de __getattr__.

+1

Por la misma razón usarías 'method_missing' en Ruby o' doesNotUnderstand' en Smalltalk. – missingfaktor

+0

Entiendo por qué querrías usar '__getattr__'.Simplemente no entiendo por qué "solo quieres interceptar las invocaciones de métodos". – senderle

+0

Mi diseño no requirió la interceptación del acceso al campo, pero no parece ser un gran obstáculo. Encuentro que la solución provista por @Emil es lo suficientemente buena para mis requisitos. – missingfaktor

1

Aunque no lo recomiendo !!!!!!!!!!!!!!!!!!!!!

este tipo de método se acerca más a implementar el comportamiento de llamar al método especial para cada nombre que no corresponde a un atributo/método invocable. Por supuesto, todavía no tienen espacios de nombres separados, por lo que puede parecer un poco extraño. Funciona sobrescribiendo __getattribute__, que funciona en un nivel inferior, luego __getattr__, intenta recuperar un atributo si falla, devuelve un método especial al curry para llamar con el nombre con el que lo llamó, si lo logra, lo pasa si es invocable, de lo contrario envuelve el resultado con un objeto proxy que actúa casi de la misma manera luego, excepto que implementa la llamada con su método especial.

No le permite acceder al objeto que llama porque no se me ocurrió una buena manera de hacerlo sin una especie de memoria perdida (el objeto que llama) si ya es un atributo no recuperable que almacena (lo único que se me ocurre es comenzar un nuevo hilo que lo borre después de un minuto, para entonces probablemente lo haya llamado a menos que lo esté usando en un cierre que no sería compatible en ese caso).

Editar: Olvidé mi llamamiento puede tener algunos falsos positivos.

depende de la biblioteca http://pypi.python.org/pypi/ProxyTypes

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'}") 
3

Se podría implementar un missing_method como función de la forma a continuación:

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() 
Cuestiones relacionadas