【问题标题】:How to override the __dir__ method for a class?如何覆盖类的 __dir__ 方法?
【发布时间】:2023-03-15 20:41:01
【问题描述】:

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

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

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

如何更改类的dir() 输出?

【问题讨论】:

  • Special method lookup;使用type(X).__dir__,而不是X.__dir__
  • “通常,对于所有其他对象,它是通过在其类中定义自己的 __dir__ 方法来完成的” - 与此对象相同。你在这个类的类中定义了一个__dir__ 方法,也就是它的元类。

标签: python class metaprogramming


【解决方案1】:

这是因为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!

【讨论】:

  • 成功了,谢谢。但是现在我被困在__dir__ 里面,因为实际上我不想raise 任何东西,我只想删除一些方法(来自unittest 的检查)。但我找不到任何方法来访问cls 内容。我知道,我不应该进入这片森林……
  • 哦,我找到了cls.__dict__.keys()。正是我需要的。
  • @GeorgeShuklin 另一种方法是使用来自type 的继承。例如使用normal_dir = super().__dir__()(这是您调用dir 时通常会显示的内容),然后在返回之前修改(添加/删除内容)normal_dir。但是cls.__dict__.keys() 也应该可以工作。 :)
【解决方案2】:

正如@MSeifert 已经解释的那样,dir 调用对象类的__dir__ 属性。所以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_LookupSpecialtypeobject.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 首先在传入的对象上调用Py_TYPE,然后使用_PyType_LookupId 查找属性。 Py_TYPE is a macro 获取对象的 ob_type 成员。它的扩展形式是:

(((PyObject*)(o))->ob_type)

正如您可能猜到的那样,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;

所以,从上面可以看出,使用的__dir__ 属性确实是对象的类。

【讨论】:

    猜你喜欢
    • 2013-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-30
    • 2011-03-05
    • 2021-01-14
    • 2021-12-27
    • 1970-01-01
    相关资源
    最近更新 更多