As @MSeifert already explained, dir calls the __dir__ attrbiute of the object class. Therefore, type(X).__dir__ is called, not X.__dir__ . Just for those who are interested, look at what really happens.
The dir implementation is in bltinmodule.c :
builtin_dir(PyObject *self, PyObject *args) { PyObject *arg = NULL; if (!PyArg_UnpackTuple(args, "dir", 0, 1, &arg)) return NULL; return PyObject_Dir(arg); }
The dir function calls the PyObject_Dir API PyObject_Dir . The PyObject_Dir function PyObject_Dir implemented in object.c :
PyObject * PyObject_Dir(PyObject *obj) { return (obj == NULL) ? _dir_locals() : _dir_object(obj); }
PyObject_Dir defined using two helper functions. When an object is passed - as here - then the _dir_object function is _dir_object . It is also implemented in object.c :
static PyObject * _dir_object(PyObject *obj) { PyObject *result, *sorted; PyObject *dirfunc = _PyObject_LookupSpecial(obj, &PyId___dir__); assert(obj); if (dirfunc == NULL) { if (!PyErr_Occurred()) PyErr_SetString(PyExc_TypeError, "object does not provide __dir__"); return NULL; } result = _PyObject_CallNoArg(dirfunc); Py_DECREF(dirfunc); if (result == NULL) return NULL; sorted = PySequence_List(result); Py_DECREF(result); if (sorted == NULL) return NULL; if (PyList_Sort(sorted)) { Py_DECREF(sorted); return NULL; } return sorted; }
This part has focused on:
PyObject *dirfunc = _PyObject_LookupSpecial(obj, &PyId___dir__);
The special __dir__ method for the passed object is used here. This is done using _PyObject_LookupSpecial . _PyObject_LookupSpecial defined in typeobject.c :
PyObject * _PyObject_LookupSpecial(PyObject *self, _Py_Identifier *attrid) { PyObject *res; res = _PyType_LookupId(Py_TYPE(self), attrid); if (res != NULL) { descrgetfunc f; if ((f = Py_TYPE(res)->tp_descr_get) == NULL) Py_INCREF(res); else res = f(res, self, (PyObject *)(Py_TYPE(self))); } return res; }
_PyObject_LookupSpecial first calls the Py_TYPE object that passed through it before searching for the attribute using _PyType_LookupId . Py_TYPE is a macro that receives a member of ob_type objects. This is an extended form:
(((PyObject*)(o))->ob_type)
And as you probably guessed, the ob_type attribute is the class (or type) of the object :
typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type; } PyObject;
So, as you can see above, the __dir__ attribute __dir__ indeed a class of objects.