正确的__dir__方法覆盖方式是什么?
这个问题主要是关于 __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()))
据我所知,这个方法可以用,但当然没有那么优雅。
问题:
- 后面的解决方案在我的情况下是正确的吗,也就是对于
recarray
的子类? - 有没有办法让它在一般情况下也能工作?我觉得如果有多个继承的话(会打破
super
的调用链),可能就不行了,当然,对于没有__dict__
的对象也是这样... - 你知道为什么
recarray
一开始就不支持列出它的字段名称吗?是个疏忽吗?
3 个回答
是的,你的解决方案是对的。
recarray
没有定义__dir__
,因为默认的实现已经可以用了,所以他们就没去实现。而且,numpy
的开发者并没有设计这个类让人去继承,所以我觉得他们也没必要去做这件事。通常来说,去继承内置类型或者那些不是专门为继承设计的类是个坏主意。因此,我建议你用委托或组合的方式,而不是继承,除非你有特别的理由(比如你想把它传给一个明确检查
isinstance
的numpy
函数)。不行。正如你所说,在python3中,他们改变了实现方式,增加了
object.__dir__
,但在其他版本的python中,我看不到有什么可以做的。而且,再说一次,使用recarray
进行多重继承简直是疯狂,这样肯定会出问题。多重继承需要仔细设计,通常类是专门为此设计的(比如混合类)。所以我不建议去处理这种情况,因为尝试的人会遇到其他问题。我不明白你为什么要关心那些没有
__dict__
的类……既然你的子类有这个属性,那它怎么会出问题呢?当你改变子类的实现,比如使用__slots__
时,你也可以轻松地改变__dir__
。如果你想避免重新定义__dir__
,你可以简单地定义一个函数,先检查__dict__
,再检查__slots__
等等。不过要注意,属性可以通过__getattr__
和__getattribute__
以微妙的方式生成,因此你根本无法可靠地捕捉到所有这些属性。
这是一个适用于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)
你试过这样做吗:
def __dir__(self):
return sorted(set(
dir(super(MyRecArray, self)) + \
self.__dict__.keys() + self.dtype.fields.keys()))