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)
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;
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With