Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override the __dir__ method for a class?

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?

like image 954
George Shuklin Avatar asked Sep 08 '17 16:09

George Shuklin


2 Answers

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!
like image 54
MSeifert Avatar answered Sep 18 '22 06:09

MSeifert


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.

like image 34
Christian Dean Avatar answered Sep 19 '22 06:09

Christian Dean