2013-05-05 12 views
7

Sto scrivendo Python che indirizza le versioni 3.2 e successive. Sembra che usare la funzione built-in callable sia il modo più diretto ed efficiente per farlo. Ho visto raccomandazioni per hasattr(x, "__call__"), collections.Callable(x) e semplicemente usando try/except in prossimità di una chiamata tentata.Utilizzo di callable (x) contro hasattr (x, "__call__")

Ho testato elementi richiamabili (una classe e una funzione), utilizzando timeit con 100.000 iterazioni; in entrambi i casi l'utilizzo di callable richiede solo il 75% circa del tempo di verifica dell'attributo. Quando l'oggetto non è callable (un intero e una stringa) usando callable rimane allo stesso costo di una classe o di una funzione mentre il controllo per l'attributo è circa 2,3 volte più costoso rispetto a una classe o funzione. Non mi aspettavo questa differenza, ma preferisco anche l'approccio chiaro e conciso callable(x).

Ma sono relativamente nuovo a Python e non esperto, quindi ci sono ragioni per cui non sono a conoscenza del fatto che dovrei usare l'approccio hasattr o un altro approccio?

FWIW, seguono i risultati dei vari tempi. Il primo carattere è solo t per timeit, il secondo indica quale sia il tipo dell'oggetto sottoposto a test (c = class, f = function, i = intero, s = stringa) e il resto indica il metodo (attr - check attribute, call - usa callable, prova - usa try/except).

 
tcattr 0.03665385400199739 
tccall 0.026238360142997408 
tctry 0.09736267629614304 
tfattr 0.03624538065832894 
tfcall 0.026362861895904643 
tftry 0.032501874250556284 
tiattr 0.08297350149314298 
ticall 0.025826044152381655 
titry 0.10657657453430147 
tsattr 0.0840187013927789 
tscall 0.02585409547373274 
tstry 0.10742772077628615 
+1

sidenote: callable è stato [rimosso in python3.0] (http://www.python.org/dev/peps/pep-3100/#built-in-namespace) e - dopo aver notato che questo [era un brutto scelta] (http://bugs.python.org/issue10518) - [riportato in 3.2] (http://docs.python.org/3.2/library/functions.html?highlight=callable#callable) – mata

risposta

6

hasattr() tornerà più falsi positivi di callable:

>>> class X(object): 
...  def __getattr__(self, name): 
...   return name 
... 
>>> i = X() 
>>> from collections import Callable 
>>> isinstance(i, Callable) 
False 
>>> callable(i) 
False 
>>> hasattr(i, '__call__') 
True 
>>> i() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'X' object is not callable 

io non sono sicuro che callable eri test, ma entrambi aspetto più bello di hasattr e gestire più casi, così io li uso in luogo di hasattr().

+1

[' callable'] (http://docs.python.org/3/library/functions.html#callable) può anche restituire [falsi positivi] (http://ideone.com/PexRWg). – jfs

+1

@ J.F.Sebastian - Vero, ma solo una persona pazza definirebbe una classe con un '__call__' non richiamabile. Sarebbe solo un bug nel codice reale. –

+0

Grazie a entrambi per la prospettiva. Per completezza stavo usando il built-in callable, non le collections.Callable. – bmacnaughton

4

callable non è solo il più veloce, ma il Zen fornisce quattro ragioni più importanti da usare al posto degli altri due aggeggi:

Bello è meglio di brutto.
L'esplicito è meglio che implicito.
Semplice è meglio che complesso.
Contabilità.

4

Ottima domanda! Direi che dovresti usare callable. Diversi punti a parte il problema di velocità:

  1. È esplicito, semplice, chiaro, breve e ordinato.
  2. È un built-in Python, quindi chiunque non sappia già cosa può scoprire facilmente.
  3. try... except TypeError ha un problema: TypeError a volte può essere generato da altre cose. Ad esempio, se si chiama correttamente una funzione che solleva TypeError nel suo corpo, lo except erroneamente lo intercetterà e presupporrà che l'oggetto non sia richiamabile.
  4. Alcune personalizzazioni comuni, come __getattr__, possono causare hasattr errori.
  5. collections.abc.Callable sembra un macchinario piuttosto pesante per qualcosa di così semplice. Dopotutto, lo callable fa lo stesso lavoro.

Nota: il blocco try è un very common pattern in Python per questo genere di cose, in modo che possa vedere un sacco di esso in codice di altre persone. Tuttavia, come ho delineato sopra, questo è un caso in cui non è del tutto adatto.