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