如何覆盖类的__dir__方法?

时间:2017-09-08 16:01:12

标签: python class metaprogramming

我想更改班级的dir()输出。通常,对于所有其他对象,通过在类中定义自己的__dir__方法来完成。但如果我为我的班级做这件事,那就不会被召唤。

class X(object):
    def __dir__():
        raise Exception("No!")

>>>dir(X)
['__class__', '__delattr__', '__dict__',....

如何更改课程的dir()输出?

2 个答案:

答案 0 :(得分:9)

这是因为dir调用了输入类型的__dir__(相当于:type(inp).__dir__(inp))。对于类的实例,它将调用类__dir__但如果在类上调用它将调用元类的__dir__

class X(object):
    def __dir__(self):  # missing self parameter
        raise Exception("No!")

dir(X())  # instance!
# Exception: No!

因此,如果您要为自己的班级(不是班级实例)自定义dir,则需要为X添加元类:

import six

class DirMeta(type):
    def __dir__(cls):
        raise Exception("No!")

@six.add_metaclass(DirMeta)
class X(object):
    pass

dir(X)
# Exception: No!

答案 1 :(得分:2)

正如@MSeifert已经解释的那样,dir会调用对象类的__dir__ attrbiute。因此调用type(X).__dir__,而不是X.__dir__。对于那些感兴趣的人来说,这里是一个看看究竟发生了什么的幕后故事。

dir的实施位于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);
}

dir函数调用API函数PyObject_DirPyObject_Dir功能在object.c中实现:

PyObject *
PyObject_Dir(PyObject *obj)
{
    return (obj == NULL) ? _dir_locals() : _dir_object(obj);
}
使用两个辅助函数定义

PyObject_Dir。传入对象时 - 就像这里的情况一样 - 然后调用_dir_object函数。它也在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;
}

部分重点是:

PyObject *dirfunc = _PyObject_LookupSpecial(obj, &PyId___dir__);

这是在传入的对象上查找__dir__特殊方法的地方。这是使用_PyObject_LookupSpecial完成的。 _PyObject_LookupSpecial中定义了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

Py_TYPE首先在传入的对象上调用_PyType_LookupId,然后使用ob_type查找属性。 typeobject.c获取对象的(((PyObject*)(o))->ob_type) 成员。它的扩展形式是:

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject;

正如您可能猜到的那样,Py_TYPE is a macro

__dir__

因此,正如您从上面所看到的,使用的userService.name === service.name && !user.disabled属性确实是对象的类。