Правильный способ переопределить метод __dir__ в python
Этот вопрос должен быть больше о __dir__
, чем о numpy
.
У меня есть подкласс numpy.recarray
(в python 2.7, numpy 1.6.2), и я заметил, что имена полей recarray
не указаны, когда 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
-call), и, конечно, для объектов без __dict__
...
- Знаете ли вы, почему
recarray
не поддерживает перечисление имен полей для начала? простой надзор?
Ответы
Ответ 1
Python 2.7+, 3.3+ mixin, который упрощает реализацию метода __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)
Ответ 2
-
и 3: Да, ваше решение правильно. recarray
не определяет __dir__
просто потому, что реализация по умолчанию была в порядке, поэтому они не потрудились ее реализовать, а numpy
разработчики не проектировали класс, который должен быть подклассом, поэтому я не понимаю, почему они должны иметь беспокоили.
Часто бывает плохой подход к подклассам встроенных типов или классов, которые специально не предназначены для наследования, поэтому я предлагаю вам использовать делегирование/состав вместо наследования, за исключением случаев, когда есть определенная причина (например, вы хотите передать его функции numpy
, которая с явной проверкой проверяется с помощью isinstance
).
-
Нет. Как вы указали в python3, они изменили реализацию так, что есть object.__dir__
, но в других версиях python я не вижу ничего, что вы можете сделать. Кроме того, использование recarray
с множественным наследованием просто сумасшедшее, все сломается. Многократное наследование должно быть тщательно спроектировано, и обычно классы специально разработаны для его использования (например, микширования). Поэтому я бы не стал рассматривать этот случай, так как тот, кто его попробует, будет укушен другими проблемами.
Я не понимаю, почему вы должны заботиться о классах, у которых нет __dict__
... так как ваш подкласс имеет его, как он должен прерываться? Когда вы измените реализацию подкласса, например. используя __slots__
, вы также можете легко изменить __dir__
. Если вы хотите избежать переопределения __dir__
, вы можете просто определить функцию, которая проверяет на __dict__
, а затем на __slots__
и т.д. Однако обратите внимание, что атрибуты могут быть сгенерированы тонким образом с помощью __getattr__
и __getattribute__
, и, таким образом, вы просто не может надежно уловить их всех.
Ответ 3
Вы пробовали:
def __dir__(self):
return sorted(set(
dir(super(MyRecArray, self)) + \
self.__dict__.keys() + self.dtype.fields.keys()))