2012-01-29 12 views
25

Sto provando ad intercettare le chiamate ai due metodi magici di sottolineatura di Python in nuove classi di stile. Questo è un esempio banale ma lo show di l'intento:Come posso intercettare le chiamate ai metodi "magici" di python in nuove classi di stile?

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 

Se uso che oggetto proxy intorno lista ottengo il comportamento previsto per i metodi non-magiche, ma la mia funzione wrapper è mai chiamato per i metodi di magia.

>>> 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 

Se non ereditare da oggetto (prima linea class ShowMeList:) tutto funziona come previsto:

>>> 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 

Come faccio a ottenere questo risultato intercetta con nuovi stili di classi?

+1

Cosa stai veramente cercando di fare intercettando i metodi a doppia sottolineatura? O è solo per curiosità? – thesamet

+0

Conservo sempre un elenco di tutti i metodi magici qui: https://github.com/niccokunzmann/wwp/blob/6df6d7f60893a23dc84a32ba244b31120b1241a9/magicMethods.py (generato, quindi funziona con Python 2 e 3) – User

+0

In realtà sembra che quello che vuoi fare sia intercettare le chiamate ai metodi magici di _instances_ di una nuova classe di stile - comunque è comunque una buona domanda. – martineau

risposta

24

Per motivi di prestazioni, Python cerca sempre nella classe (e nelle classi parent ') __dict__ per i metodi magici e non utilizza il normale meccanismo di ricerca degli attributi. Una soluzione alternativa è usare una metaclasse per aggiungere automaticamente proxy per i metodi magici al momento della creazione della classe; Ho usato questa tecnica per evitare di dover scrivere i metodi call-through boilerplate per le classi wrapper, per esempio.

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))) 

Usage:

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 Ho rimosso quella che sembrava una riga di codice orfana, ma potresti voler controllare che il tuo esempio non abbia perso la funzionalità prevista. – Air

+0

Si vede qualche problema nel fare 'ignore.update (dct)' prima di eseguire il ciclo attraverso 'dir()' e quindi condensare le ultime due istruzioni 'if' in una? Mi sento più pulito ma forse ci sono conseguenze non intenzionali che io, nella mia inesperienza, non ho previsto. – Air

3

Come delle risposte a Asymmetric behavior for __getattr__, newstyle vs oldstyle classes (vedi anche la Python docs), modificando l'accesso a metodi "magici" con __getattr__ o __getattribute__ non è solo possibile con classi di nuovo stile. Questa restrizione rende l'interprete molto più veloce.

+0

Grazie. Ho trovato [questa risposta] (http://stackoverflow.com/a/3416058/531516) utile per arricchire la tua risposta. – Finn

6

Utilizzo di __getattr__ e __getattribute__ sono le ultime risorse di una classe per rispondere alla ricezione di un attributo.

Si consideri il seguente:

>>> 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 

Il metodo __getattr__ viene chiamato solo quando niente funziona (Non funziona per gli operatori, e si può leggere su questo here).

Nell'esempio, lo __repr__ e molti altri metodi magici sono già definiti nella classe object.

Una cosa può essere fatta, pensata, ed è per definire quei metodi magici e quindi chiamare il metodo __getattr__. Controlla questa altra domanda da me e le sue risposte (link) per vedere qualche codice farlo.

-1

Tagliare e copiare dalla documentazione:

Per le classi di vecchio stile, i metodi speciali sono sempre guardato esattamente nello stesso modo di qualsiasi altro metodo o un attributo.

Per le classi di nuovo stile, le invocazioni implicite di metodi speciali sono garantite per funzionare correttamente solo se definite sul tipo di un oggetto, non nel dizionario di istanza dell'oggetto.

+5

Questa risposta è corretta al 100% mentre non spiega nulla ed è del tutto inutile. Scusate. –

+2

Una vecchia battuta dei Bell Labs: un pilota perso in una fitta nebbia vede un edificio per uffici, grida "dove sono?", Ottiene la risposta "in un aeroplano", e atterra in ... Allentown. Come lo sapeva? "La risposta è corretta al 100% ..." – denis

Problemi correlati