Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why no __getitem__ raises TypeError

Tags:

python

So the question is pretty simple: If we have a random class, let's say an int and we try to access a non defined attribute:

my_int = 5
my_int.this_is_a_test

We will get this error:

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

But if we try to access an index of it (in which case Python will do a lookup for a __getitem__ attribute):

my_int = 5
my_int[0]

We get:

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

What is the logic behind the change in exception type? It seems weird to me that a TypeError is raised, complaining about a missing attribute (AttributeError seems like a much better candidate for that)

like image 411
Alvaro Avatar asked May 09 '16 19:05

Alvaro


2 Answers

It depends on your intention.

In [1]: my_int = 5

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

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

When you use . you implicitly call the getattr function, that naturally raises the AttributeError if the attribute doesn't exist.

Update 2. Let's look at the 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   

As you see, the [] has a special virtual-machine instruction. From the docs

BINARY_SUBSCR: Implements TOS = TOS1[TOS].

Hence it's quite natural to raise a TypeError, when you fail at executing an instruction.

Update 1: Looking at the getattr sources, it's clear that this function can never raise such a TypeError, hence the [] operator doesn't call it under the hood (for the built-in types at least, though it's better to find the sources to clarify this 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;
}
like image 194
Eli Korvigo Avatar answered Oct 25 '22 12:10

Eli Korvigo


An AttributeError for my_int[0] may be misleading because you aren't trying to access an attribute of my_int, you are trying to access an item. The TypeError is raised because int doesn't support subscripting, and this exception message was updated in Python 3.X.

That being said, it wouldn't be inappropriate to throw an AttributeError that there is no __getitem__. I suspect that this may be a TypeError because numbers (int, float, long) are the only built-in data-types that do not support subscripting.

When this error comes up, it will be due to you thinking that my_int contains an object of a different type, hence the TypeError.

like image 41
Jared Goguen Avatar answered Oct 25 '22 12:10

Jared Goguen