2015-10-14 6 views
8

Esiste un modo per creare una classe che le istanze rispondano alle chiamate di metodo arbitrarie?Istanza della classe Python che risponde a tutte le chiamate di metodo

So che esiste un metodo speciale __getattr__(self, attr) che verrebbe chiamato quando qualcuno sta tentando di accedere a un attributo di un'istanza. Sto cercando qualcosa di simile che mi permetta di intercettare anche le chiamate ai metodi. Il comportamento desiderato sarebbe simile a questa:

class A(object): 
    def __methodintercept__(self, method, *args, **kwargs): # is there a special method like this?? 
     print(str(method)) 


>>> a = A() 
>>> a.foomatic() 
foomatic 

EDIT

Le altre domande suggerite non affrontano il mio caso: io non voglio per avvolgere un'altra classe o modificare la metaclasse di una seconda classe o simile. Voglio solo avere una classe che risponda alle chiamate al metodo arbitrario.

Grazie a jonrshape ora so che __getattr__(self, attr) verrà chiamato anche quando un metodo viene chiamato nello stesso modo in cui verrebbe quando si accede a un attributo. Ma come faccio a distinguere in __getattr__ se attr deriva da una chiamata al metodo o un accesso di attributo e come ottenere i parametri di una potenziale chiamata di metodo?

+2

I metodi non sono gestiti diversamente da altri attributi, è comunque necessario '__getattr__' /' __getattribute__'. – jonrsharpe

+0

@jonrsharpe mmh se implemento una classe A con '__getattr__' e una stampa all'interno e faccio' a = A(); a.foo' stamperà 'foo' ma se chiamo' a = A(); a.foo() 'genererà un' TypeError: 'NoneType' oggetto non è callable' – Salo

+1

'__getattr__' dovrà ancora * restituire * qualcosa richiamabile, non * chiamarlo *, altrimenti si ottiene il valore predefinito' None' – jonrsharpe

risposta

7

questa è una cosa che mi è venuta, che si comporterà esattamente come se esiste il metodo.

Prima di tutto stabilire una cosa: non è possibile distinguere se __getattr__attr viene da una chiamata di funzione o di un "accesso attributo", perché un metodo di classe è un attributo della classe.Così qualcuno può accedere a tale metodo, anche se non intendono chiamare, come in:

class Test: 
    def method(self): 
     print "Hi, I am method" 

>> t = Test() 
>> t.method # just access the method "as an attribute" 
<bound method Test.method of <__main__.Test instance at 0x10a970c68>> 

>> t.method() # actually call the method 
Hi, I am method 

Pertanto, la cosa più vicina che potrei pensare è questo comportamento:

creare una classe A, come ad che:

  1. Quando proviamo ad accedere a un attributo/metodo, che esiste già in quella classe, comportarsi normalmente e restituire semplicemente l'attributo/metodo richiesto.
  2. Quando si tenta di accedere a qualcosa che non esiste nella definizione della classe, trattarlo come un metodo di classe e disporre di un gestore globale per tutti questi metodi.

io per prima dare la definizione della classe e poi mostrare come l'accesso a un metodo che non esiste si comporta esattamente come l'accesso a quella che esiste, se sono solo accedervi, o addirittura chiamandolo.

definizione Classe:

class A(object): 
    def __init__(self): 
     self.x = 1 # set some attribute 

    def __getattr__(self,attr): 
     try: 
      return super(A, self).__getattr__(attr) 
     except AttributeError: 
      return self.__get_global_handler(attr) 

    def __get_global_handler(self, name): 
     # Do anything that you need to do before simulating the method call 
     handler = self.__global_handler 
     handler.im_func.func_name = name # Change the method's name 
     return handler 

    def __global_handler(self, *args, **kwargs): 
     # Do something with these arguments 
     print "I am an imaginary method with name %s" % self.__global_handler.im_func.func_name 
     print "My arguments are: " + str(args) 
     print "My keyword arguments are: " + str(kwargs) 

    def real_method(self, *args, **kwargs): 
     print "I am a method that you actually defined" 
     print "My name is %s" % self.real_method.im_func.func_name 
     print "My arguments are: " + str(args) 
     print "My keyword arguments are: " + str(kwargs) 

ho aggiunto il metodo real_method solo così ho qualcosa che esiste realmente nella classe per confrontare il suo comportamento con quello di un 'metodo di immaginario'

Ecco il risultato:

>> a = A() 
>> # First let's try simple access (no method call) 
>> a.real_method # The method that is actually defined in the class 
<bound method A.real_method of <test.A object at 0x10a9784d0>> 

>> a.imaginary_method # Some method that is not defined 
<bound method A.imaginary_method of <test.A object at 0x10a9784d0>> 

>> # Now let's try to call each of these methods 
>> a.real_method(1, 2, x=3, y=4) 
I am a method that you actually defined 
My name is real_method 
My arguments are: (1, 2) 
My keyword arguments are: {'y': 4, 'x': 3} 

>> a.imaginary_method(1, 2, x=3, y=4) 
I am an imaginary method with name imaginary_method 
My arguments are: (1, 2) 
My keyword arguments are: {'y': 4, 'x': 3} 

>> # Now let's try to access the x attribute, just to make sure that 'regular' attribute access works fine as well 
>> a.x 
1 
+0

Grazie per la tua risposta, mi ha dato una comprensione più profonda di Python. L'unica cosa che ho trovato: se chiamo 'a.attribute_that_doesnt_exist' risponderà con niente – Salo

+1

@Salo Se chiami' a.attribute_that_doesnt_exist' non dovrebbe rispondere con nulla ('None'). Dovrebbe effettivamente restituire un oggetto "metodo vincolato". Quindi: 'a.method' restituisce il metodo. Se si aggiungono le parentesi '()' (con argomenti, facoltativamente) dopo la chiamata al metodo ('a.method()'), verrà valutata. Aprire un interprete python ('python', idealmente' ipython'), e digitare 'a.attribute_that_doesnt_exist'. Dovresti ottenere sth come '>' Questo è il modo in cui Python ti dice che questo è un metodo di classe, ma non lo chiami. – tomas

+0

Hai ragione, grazie ancora – Salo

0

Le chiamate di metodo non sono diverse dall'accesso di attributo. __getattr__() o __getattribute__() è il modo di rispondere alle richieste di attributo arbitrarie.

Non è possibile sapere se l'accesso proviene da "solo recupero" o "chiamata di metodo".

Funziona in questo modo: in primo luogo, attribuire il recupero, poi, invocano l'oggetto recuperato (in Python, chiamata è solo un altro operatore: tutto può essere chiamato e sarà un'eccezione se non è richiamabile). Uno non sa, e non dovrebbe, sapere dell'altro (beh, è ​​possibile analizzare il codice sullo stack delle chiamate, ma non è assolutamente la cosa da fare qui).

Uno dei motivi è: le funzioni sono oggetti di prima classe in Python, ovvero una funzione (o, piuttosto, un riferimento ad essa) non è diversa da qualsiasi altro tipo di dati: posso ottenere il riferimento, salvarlo e passarlo in giro. Cioè non c'è alcuna differenza tra la richiesta di un campo dati e un metodo.

Elaborare su ciò che è necessario per noi per suggerire una soluzione migliore.

Ad es., Se è necessario il "metodo" per poter essere richiamati con firme diverse, *args e **kwargs è la strada da percorrere.

4

unittest.mock.Mock esegue questa operazione per impostazione predefinita.

from unittest.mock import Mock 

a = Mock() 

a.arbitrary_method()        # No error 
a.arbitrary_method.called      # True 
a.new_method 
a.new_method.called        # False 
a.new_method("some", "args") 
a.new_method.called        # True 
a.new_method.assert_called_with("some", "args") # No error 
a.new_method_assert_called_with("other", "args") # AssertionError 
Problemi correlati