正确的__dir__方法覆盖方式是什么?

29 投票
3 回答
9521 浏览
提问于 2025-04-17 19:39

这个问题主要是关于 __dir__ 的,而不是关于 numpy 的。

我有一个 numpy.recarray 的子类(在 Python 2.7 和 numpy 1.6.2 中),我发现当我使用 dir 查看这个对象时,它的字段名称并没有列出来(所以在 ipython 中的自动补全也不能用)。

为了修复这个问题,我尝试在我的子类中重写 __dir__,像这样:

def __dir__(self):
    return sorted(set(
               super(MyRecArray, self).__dir__() + \
               self.__dict__.keys() + self.dtype.fields.keys()))

结果是:AttributeError: 'super' object has no attribute '__dir__'。(我在 这里发现其实在 Python 3.3 中应该可以正常工作...)

作为一种变通方法,我尝试了:

def __dir__(self):
    return sorted(set(
                dir(type(self)) + \
                self.__dict__.keys() + self.dtype.fields.keys()))

据我所知,这个方法可以用,但当然没有那么优雅。

问题:

  1. 后面的解决方案在我的情况下是正确的吗,也就是对于 recarray 的子类?
  2. 有没有办法让它在一般情况下也能工作?我觉得如果有多个继承的话(会打破 super 的调用链),可能就不行了,当然,对于没有 __dict__ 的对象也是这样...
  3. 你知道为什么 recarray 一开始就不支持列出它的字段名称吗?是个疏忽吗?

3 个回答

3
  1. 是的,你的解决方案是对的。recarray没有定义__dir__,因为默认的实现已经可以用了,所以他们就没去实现。而且,numpy的开发者并没有设计这个类让人去继承,所以我觉得他们也没必要去做这件事。

    通常来说,去继承内置类型或者那些不是专门为继承设计的类是个坏主意。因此,我建议你用委托或组合的方式,而不是继承,除非你有特别的理由(比如你想把它传给一个明确检查isinstancenumpy函数)。

  2. 不行。正如你所说,在python3中,他们改变了实现方式,增加了object.__dir__,但在其他版本的python中,我看不到有什么可以做的。而且,再说一次,使用recarray进行多重继承简直是疯狂,这样肯定会出问题。多重继承需要仔细设计,通常类是专门为此设计的(比如混合类)。所以我不建议去处理这种情况,因为尝试的人会遇到其他问题。

    我不明白你为什么要关心那些没有__dict__的类……既然你的子类有这个属性,那它怎么会出问题呢?当你改变子类的实现,比如使用__slots__时,你也可以轻松地改变__dir__。如果你想避免重新定义__dir__,你可以简单地定义一个函数,先检查__dict__,再检查__slots__等等。不过要注意,属性可以通过__getattr____getattribute__以微妙的方式生成,因此你根本无法可靠地捕捉到所有这些属性。

6

这是一个适用于Python 2.7及以上版本和3.3及以上版本的类混合器,它可以简化子类中__dir__方法的实现。希望这个内容对你有帮助。你可以在这里查看详细信息:Gist

import six
class DirMixIn:
    """ Mix-in to make implementing __dir__ method in subclasses simpler
    """

    def __dir__(self):
        if six.PY3:
            return super(DirMixIn, self).__dir__()
        else:
            # code is based on
            # http://www.quora.com/How-dir-is-implemented-Is-there-any-PEP-related-to-that
            def get_attrs(obj):
                import types
                if not hasattr(obj, '__dict__'):
                    return []  # slots only
                if not isinstance(obj.__dict__, (dict, types.DictProxyType)):
                    raise TypeError("%s.__dict__ is not a dictionary"
                                    "" % obj.__name__)
                return obj.__dict__.keys()

            def dir2(obj):
                attrs = set()
                if not hasattr(obj, '__bases__'):
                    # obj is an instance
                    if not hasattr(obj, '__class__'):
                        # slots
                        return sorted(get_attrs(obj))
                    klass = obj.__class__
                    attrs.update(get_attrs(klass))
                else:
                    # obj is a class
                    klass = obj

                for cls in klass.__bases__:
                    attrs.update(get_attrs(cls))
                    attrs.update(dir2(cls))
                attrs.update(get_attrs(obj))
                return list(attrs)

            return dir2(self)
6

你试过这样做吗:

def __dir__(self):
    return sorted(set(
               dir(super(MyRecArray, self)) + \
               self.__dict__.keys() + self.dtype.fields.keys()))

撰写回答