2012-01-29 13 views
25

Estoy tratando de interceptar las llamadas a los métodos mágicos de doble subrayado de Python en las nuevas clases de estilo. Este es un ejemplo trivial, pero el show de la intención:¿Cómo puedo interceptar llamadas a los métodos "mágicos" de Python en nuevas clases de estilo?

class ShowMeList(object): 
    def __init__(self, it): 
     self._data = list(it) 

    def __getattr__(self, name): 
     attr = object.__getattribute__(self._data, name) 
     if callable(attr): 
      def wrapper(*a, **kw): 
       print "before the call" 
       result = attr(*a, **kw) 
       print "after the call" 
       return result 
      return wrapper 
     return attr 

Si utilizo objeto proxy que alrededor de la lista consigo el comportamiento esperado de los métodos no-mágicas, pero mi función de contenedor nunca es llamado por métodos mágicos.

>>> l = ShowMeList(range(8)) 

>>> l #call to __repr__ 
<__main__.ShowMeList object at 0x9640eac> 

>>> l.append(9) 
before the call 
after the call 

>> len(l._data) 
9 

Si no heredar de objeto (primera línea class ShowMeList:) todo funciona como se espera:

>>> l = ShowMeList(range(8)) 

>>> l #call to __repr__ 
before the call 
after the call 
[0, 1, 2, 3, 4, 5, 6, 7] 

>>> l.append(9) 
before the call 
after the call 

>> len(l._data) 
9 

¿Cómo lograr esto interceptación con nuevas clases de estilo?

+1

Lo que en realidad tratando de hacer mediante la interceptación de los métodos doble subrayado? ¿O es solo por curiosidad? – thesamet

+0

Siempre guardo una lista de todos los métodos mágicos aquí: https://github.com/niccokunzmann/wwp/blob/6df6d7f60893a23dc84a32ba244b31120b1241a9/magicMethods.py (generado, por lo que funciona para python 2 y 3) – User

+0

En realidad, parece que lo que quieres hacer es interceptar llamadas a los métodos mágicos de _instancias_ de una nueva clase de estilo, sin embargo, sigue siendo una buena pregunta en mi humilde opinión. – martineau

Respuesta

24

Por razones de rendimiento, Python siempre busca en la clase (y las clases principales) __dict__ los métodos mágicos y no utiliza el mecanismo de búsqueda de atributos normal. Una solución alternativa es usar una metaclase para agregar automáticamente proxies para métodos mágicos en el momento de la creación de la clase; Utilicé esta técnica para evitar tener que escribir métodos de llamada repetidos para las clases contenedoras, por ejemplo.

class Wrapper(object): 
    """Wrapper class that provides proxy access to an instance of some 
     internal instance.""" 

    __wraps__ = None 
    __ignore__ = "class mro new init setattr getattr getattribute" 

    def __init__(self, obj): 
     if self.__wraps__ is None: 
      raise TypeError("base class Wrapper may not be instantiated") 
     elif isinstance(obj, self.__wraps__): 
      self._obj = obj 
     else: 
      raise ValueError("wrapped object must be of %s" % self.__wraps__) 

    # provide proxy access to regular attributes of wrapped object 
    def __getattr__(self, name): 
     return getattr(self._obj, name) 

    # create proxies for wrapped object's double-underscore attributes 
    class __metaclass__(type): 
     def __init__(cls, name, bases, dct): 

      def make_proxy(name): 
       def proxy(self, *args): 
        return getattr(self._obj, name) 
       return proxy 

      type.__init__(cls, name, bases, dct) 
      if cls.__wraps__: 
       ignore = set("__%s__" % n for n in cls.__ignore__.split()) 
       for name in dir(cls.__wraps__): 
        if name.startswith("__"): 
         if name not in ignore and name not in dct: 
          setattr(cls, name, property(make_proxy(name))) 

Uso:

class DictWrapper(Wrapper): 
    __wraps__ = dict 

wrapped_dict = DictWrapper(dict(a=1, b=2, c=3)) 

# make sure it worked.... 
assert "b" in wrapped_dict      # __contains__ 
assert wrapped_dict == dict(a=1, b=2, c=3)  # __eq__ 
assert "'a': 1" in str(wrapped_dict)    # __str__ 
assert wrapped_dict.__doc__.startswith("dict()") # __doc__ 
+0

@kindall Eliminé lo que parecía una línea de código huérfano, pero es posible que desee comprobar que su ejemplo no haya perdido la funcionalidad prevista. – Air

+0

¿Ve algún problema con 'ignore.update (dct)' antes de pasar por 'dir()' y condensar las dos últimas instrucciones 'if' en una sola? Se siente más limpio para mí, pero tal vez haya consecuencias imprevistas que yo, en mi inexperiencia, no anticipé. – Air

3

A partir de las respuestas a Asymmetric behavior for __getattr__, newstyle vs oldstyle classes (véase también la Python docs), modificando el acceso a los métodos "mágicos" con __getattr__ o __getattribute__ no sólo es posible con las clases de nuevo estilo. Esta restricción hace que el intérprete sea mucho más rápido.

+0

Gracias. Encontré [esta respuesta] (http://stackoverflow.com/a/3416058/531516) útil para dar vida a su respuesta. – Finn

6

El uso de __getattr__ y __getattribute__ son los últimos recursos de una clase para responder a la obtención de un atributo.

considerar lo siguiente:

>>> class C: 
    x = 1 
    def __init__(self): 
     self.y = 2 
    def __getattr__(self, attr): 
     print(attr) 

>>> c = C() 
>>> c.x 
1 
>>> c.y 
2 
>>> c.z 
z 

El método __getattr__ sólo se invoca cuando algo no funciona (No va a trabajar en los operadores, y se puede leer acerca de que here).

En su ejemplo, el __repr__ y muchos otros métodos mágicos ya están definidos en la clase object.

Una cosa se puede hacer, pensó, y es definir esos métodos mágicos y luego llamar al método __getattr__. Consulte esta otra pregunta por mí y sus respuestas (link) para ver algún código haciendo eso.

-1

Corte y copia de la documentación:

Para las clases de estilo antiguo, métodos especiales siempre se miraron exactamente de la misma manera que cualquier otro método o atributo.

Para las clases de estilo nuevo, solo se garantiza que las invocaciones implícitas de métodos especiales funcionan correctamente si están definidas en el tipo de un objeto, no en el diccionario de instancia del objeto.

+5

Esta respuesta es 100% correcta sin explicar nada y no ser útil por completo. Lo siento. –

+2

Una vieja broma de Bell Labs: un piloto perdido en una espesa niebla ve un edificio de oficinas, grita "¿dónde estoy?", Recibe la respuesta "en un avión", y aterriza en ... Allentown. ¿Cómo lo supo? "La respuesta es 100% correcta ..." – denis

Cuestiones relacionadas