You can find out what is actually happening by disassembling python bytecode.
>>> from dis import dis
>>> dis(compile('print outer', '<string>', 'exec'))
1 0 LOAD_NAME 0 (outer)
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
And reading the source for the underlying opcodes.
PRINT_ITEM eventually reaches this block of code:
else if (Py_TYPE(op)->tp_print == NULL) {
PyObject *s;
if (flags & Py_PRINT_RAW)
s = PyObject_Str(op);
else
s = PyObject_Repr(op);
...
}
else
ret = (*Py_TYPE(op)->tp_print)(op, fp, flags);
This means that __str__
or __repr__
will be called only if object's type does not have a tp_print function. And tupleobject has one.
If you want to understand the internals of CPython the best way is to read the source code. I recommend a series of tutorials on python internals, it explains everything you must know to fully understand the output of python dis function.