2013-05-04 8 views
14

Sto cercando una soluzione pitonica su come memorizzare un metodo che viene chiamato su un oggetto proprio all'interno dell'oggetto.Come catturare qualsiasi metodo chiamato su un oggetto in python?

Perché in pitone, se voglio prendere ad esempio il metodo abs(), voglio sovraccaricare questo operatore come:

Catcher(object): 
    def __abs__(self): 
     self.function = abs 

c = Catcher() 
abs(c) # Now c.function stores 'abs' as it was called on c 

Se voglio prendere una funzione, che hanno un altro attributo in essa, per esempio pow(), ho intenzione di usare questo:

Catcher(object): 
    def __pow__(self, value): 
     self.function = pow 
     self.value = value 

c = Catcher() 
c ** 2 # Now c.function stores 'pow', and c.value stores '2' 

Ora, quello che sto cercando è una soluzione generale, per catturare e memorizzare qualsiasi tipo di funzione chiamata sul Catcher, senza l'implementazione di tutti i sovraccarichi, e altri casi. E come puoi vedere, voglio anche memorizzare i valori (magari in una lista, se ce n'è più di uno?) che sono gli attributi di un metodo.

Grazie in anticipo!

+0

E le funzioni che non delegano a un metodo dunder? – delnan

+0

Si potrebbe voler guardare in classe decoratori e metaclassi. –

+0

@delnan Suppongo che anche quelli siano OK, perché nel mio caso queste funzioni cercano qualcos'altro, un valore o un metodo da chiamare. –

risposta

6

Un metaclass non sarà d'aiuto; anche se si cercano metodi speciali sul tipo dell'oggetto corrente (quindi la classe per le istanze), __getattribute__ o __getattr__ non vengono consultati quando lo fanno (probabilmente perché sono essi stessi metodi speciali). Quindi, per prendere i metodi di derve, con , sei costretto a crearli tutti.

È possibile ottenere un elenco abbastanza decente di tutti gli operatori metodi speciali (__pow__, __gt__, etc.) enumerando il operator module:

import operator 
operator_hooks = [name for name in dir(operator) if name.startswith('__') and name.endswith('__')] 

Armato di quella lista un decoratore di classe potrebbe essere:

def instrument_operator_hooks(cls): 
    def add_hook(name): 
     operator_func = getattr(operator, name.strip('_'), None) 
     existing = getattr(cls, name, None) 

     def op_hook(self, *args, **kw): 
      print "Hooking into {}".format(name) 
      self._function = operator_func 
      self._params = (args, kw) 
      if existing is not None: 
       return existing(self, *args, **kw) 
      raise AttributeError(name) 

     try: 
      setattr(cls, name, op_hook) 
     except (AttributeError, TypeError): 
      pass # skip __name__ and __doc__ and the like 

    for hook_name in operator_hooks: 
     add_hook(hook_name) 
    return cls 

Quindi applicare che alla classe:

@instrument_operator_hooks 
class CatchAll(object): 
    pass 

Demo:

>>> c = CatchAll() 
>>> c ** 2 
Hooking into __pow__ 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 11, in op_hook 
AttributeError: __pow__ 
>>> c._function 
<built-in function pow> 
>>> c._params 
((2,), {}) 

Così, anche se la nostra classe non definisce esplicitamente __pow__, abbiamo ancora agganciato in esso.

+0

Dato che sono abbastanza nuovo per '@ decoratori', ho dovuto leggere questo [articolo] (http://www.artima.com/weblogs/viewpost.jsp?thread=240808), che è incredibilmente semplice, e poi ho appena capito, quello che hai fatto .. e devo ammettere, ora, che so cosa sta succedendo - non è più quel tipo di magia :) :) Ho reimplementato la tua soluzione in una classe decoratore - credo, è più facile seguire ciò che sta accadendo nel mio codice. –

+0

@PeterVaro: Questo va bene. :-) Il punto centrale della mia risposta era come generare comunque un elenco di nomi di metodi dunder. :-P –

2

Questo è un modo per farlo.

import inspect 
from functools import wraps 
from collections import namedtuple 

call = namedtuple('Call', ['fname', 'args', 'kwargs']) 
calls = [] 

def register_calls(f): 
    @wraps(f) 
    def f_call(*args, **kw): 
     calls.append(call(f.__name__, args, kw)) 
     print calls 
     return f(*args, **kw) 
    return f_call 


def decorate_methods(decorator): 
    def class_decorator(cls): 
     for name, m in inspect.getmembers(cls, inspect.ismethod): 
      setattr(cls, name, decorator(m)) 
     return cls 
    return class_decorator 


@decorate_methods(register_calls) 
class Test(object): 

    def test1(self): 
     print 'test1' 

    def test2(self): 
     print 'test2' 

Ora tutte le chiamate a test1 e test2 sarà registri nella callslist.

decorate_methods applica un decoratore a ciascun metodo della classe. register_calls registra le chiamate ai metodi in calls, con il nome della funzione e gli argomenti.

+0

Ma questo richiede ancora di creare * tutti * i metodi speciali sulla classe prima. –

+0

@morphyn sì, Martijn Pieters ha ragione, ho appena provato questo - forse non sto usando correttamente - ma non posso fare quello che voglio con questo ... –

+0

Sì, hai ancora bisogno di creare i metodi . Non ho capito cosa volevi. Stai cercando 'method_missing 'di ruby ​​quindi :) Dovrai usare' __getattr__' allora. –