I want to change the dir()
output for my class. Normally, for all other objects, it's done by defining own __dir__
method in their class. But if I do this for my class, it's not called.
class X(object):
def __dir__():
raise Exception("No!")
>>>dir(X)
['__class__', '__delattr__', '__dict__',....
How can I change the dir()
output for class?
That's because dir
calls the __dir__
of the type of the input (equivalent to: type(inp).__dir__(inp)
). For instances of a class it will call the classes __dir__
but if called on a class it will call the __dir__
of the meta-class.
class X(object):
def __dir__(self): # missing self parameter
raise Exception("No!")
dir(X()) # instance!
# Exception: No!
So if you want to customize dir
for your class (not instances of your class) you need to add a metaclass for your X
:
import six
class DirMeta(type):
def __dir__(cls):
raise Exception("No!")
@six.add_metaclass(DirMeta)
class X(object):
pass
dir(X)
# Exception: No!
As @MSeifert already explained, dir
calls the __dir__
attrbiute of the object's class. So type(X).__dir__
is called, not X.__dir__
. Just for those that are interested, here is a look behinds the scenes of what exactly occurs.
The implementation of dir
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 API function PyObject_Dir
. The PyObject_Dir
function is implemented in object.c:
PyObject *
PyObject_Dir(PyObject *obj)
{
return (obj == NULL) ? _dir_locals() : _dir_object(obj);
}
PyObject_Dir
is defined using two helper functions. When an object is passed in - as is the case here - then the _dir_object
function is called. 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;
}
/* use __dir__ */
result = _PyObject_CallNoArg(dirfunc);
Py_DECREF(dirfunc);
if (result == NULL)
return NULL;
/* return sorted(result) */
sorted = PySequence_List(result);
Py_DECREF(result);
if (sorted == NULL)
return NULL;
if (PyList_Sort(sorted)) {
Py_DECREF(sorted);
return NULL;
}
return sorted;
}
The part were focusing on is:
PyObject *dirfunc = _PyObject_LookupSpecial(obj, &PyId___dir__);
This is where the __dir__
special method is looked-up on the object passed in. This is done using _PyObject_LookupSpecial
. _PyObject_LookupSpecial
is 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 Py_TYPE
on the object passed in, before looking-up the attribute using _PyType_LookupId
. Py_TYPE
is a macro that gets the ob_type
member of objects. It's expanded form is:
(((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 from above, the __dir__
attribute used is indeed the object's class.
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