2016-05-09 15 views
6

Quindi la domanda è abbastanza semplice: Se abbiamo una classe a caso, diciamo un int e cerchiamo di accedere a un attributo non definito:Perché nessuna __getitem__ solleva TypeError

my_int = 5 
my_int.this_is_a_test 

otterremo questo errore:

AttributeError: 'int' object has no attribute 'this_is_a_test' 

Ma se cerchiamo di accedere ad un indice di essa (nel qual caso Python farà una ricerca per un attributo __getitem__):

my_int = 5 
my_int[0] 

otteniamo:

TypeError: 'int' object has no attribute '__getitem__' 

Qual è la logica dietro il cambiamento di tipo di eccezione? Mi sembra strano che venga sollevato un numero TypeError, lamentando un attributo mancante (AttributeError sembra un candidato molto migliore)

+4

Se si accede a 'my_int .__ getitem__' direttamente, si ottiene un' AttributeError'. Ma * questo è un dettaglio di implementazione * - il problema che stai vedendo è che non supporta l'indicizzazione, che è un errore di tipo. – jonrsharpe

+0

Si noti che il primo esempio fa riferimento a un attributo, mentre il secondo esempio fa riferimento a un elemento. –

+0

Non c'è abc 'Indexable' (tutti i tipi di contenitori usano' __getitem__'), ma suppongo che 'TypeError' suppone che' int' non sia di alcun tipo che sia indicizzabile. – timgeb

risposta

3

Dipende dalla tua intenzione.

In [1]: my_int = 5 

In [2]: my_int.__getitem__(0) # -> AttributeError 

In [3]: my_int[0] # -> TypeError 

Quando si utilizza . si chiama implicitamente la funzione getattr, che solleva naturalmente il AttributeError se l'attributo non esiste.

Aggiornamento 2. Diamo un'occhiata al bytecode.

In [11]: import dis 

In [12]: def via_operator(): 
      my_int = 5 
      my_int[0] 


In [13]: def via_getattr(): 
      my_int = 5 
      my_int.__getitem__(0) 

In [14]: dis.dis(via_operator) 
    2   0 LOAD_CONST    1 (5) 
       3 STORE_FAST    0 (my_int) 

    3   6 LOAD_FAST    0 (my_int) 
       9 LOAD_CONST    2 (0) 
      12 BINARY_SUBSCR  
      13 POP_TOP    
      14 LOAD_CONST    0 (None) 
      17 RETURN_VALUE   

In [15]: dis.dis(via_getattr) 
    2   0 LOAD_CONST    1 (5) 
       3 STORE_FAST    0 (my_int) 

    3   6 LOAD_FAST    0 (my_int) 
       9 LOAD_ATTR    0 (__getitem__) 
      12 LOAD_CONST    2 (0) 
      15 CALL_FUNCTION   1 
      18 POP_TOP    
      19 LOAD_CONST    0 (None) 
      22 RETURN_VALUE 

Come si vede, il [] ha una speciale istruzione di virtual-machine. Dal docs

BINARY_SUBSCR: Implements TOS = TOS1[TOS].

Quindi è abbastanza naturale per sollevare un TypeError, quando non si riesce ad eseguire un'istruzione.

Update 1: Guardando le getattr fonti, è chiaro che questa funzione non può mai sollevare una tale TypeError, quindi l'operatore [] non chiama sotto il cofano (per il built-in tipi, almeno, anche se è meglio trovare le fonti per chiarire questo bit).

static PyObject * 
builtin_getattr(PyObject *self, PyObject *args) 
{ 
    PyObject *v, *result, *dflt = NULL; 
    PyObject *name; 

    if (!PyArg_UnpackTuple(args, "getattr", 2, 3, &v, &name, &dflt)) 
     return NULL; 
#ifdef Py_USING_UNICODE 
    if (PyUnicode_Check(name)) { 
     name = _PyUnicode_AsDefaultEncodedString(name, NULL); 
     if (name == NULL) 
      return NULL; 
    } 
#endif 

    if (!PyString_Check(name)) { 
     PyErr_SetString(PyExc_TypeError, 
         "getattr(): attribute name must be string"); 
     return NULL; 
    } 
    result = PyObject_GetAttr(v, name); 
    if (result == NULL && dflt != NULL && 
     PyErr_ExceptionMatches(PyExc_AttributeError)) 
    { 
     PyErr_Clear(); 
     Py_INCREF(dflt); 
     result = dflt; 
    } 
    return result; 
} 
+0

Certo, ma la notazione della parentesi quadra è zucchero per chiamare '__getitem__', quindi la vera domanda è se un 'AttributeError' non sarebbe più consistente quando si tenta di chiamare implicitamente' __getitem__' e il metodo non esiste. – timgeb

+1

@timgeb Ora sto scorrendo il codice sorgente per capirlo. –

+0

@timgeb Immagino, ho trovato una spiegazione ragionevole. –

1

Un AttributeError per my_int[0] può essere fuorviante, perché non si sta cercando di accedere ad un attributo di my_int, si sta tentando di accedere a una oggetto. Il numero TypeError viene generato perché int non supporta l'indicizzazione e questo messaggio di eccezione è stato aggiornato in Python 3.X.

Detto questo, non sarebbe inopportuno lanciare un AttributeError che non ci sia __getitem__. Sospetto che possa trattarsi di un numero TypeError perché i numeri (int, float, long) sono gli unici tipi di dati incorporati che non supportano l'indicizzazione.

Quando si verifica questo errore, sarà a causa del fatto che si pensa che my_int contenga un oggetto di un tipo diverso, da cui lo TypeError.

Problemi correlati