Subclassing dict: должен ли вызывать.__ init __()?
Вот двоякий вопрос с теоретической частью и практический:
При подклассификации dict:
class ImageDB(dict):
def __init__(self, directory):
dict.__init__(self) # Necessary??
...
следует dict.__init__(self)
вызывать, как меру "безопасности" (например, если есть некоторые нетривиальные детали реализации, которые имеют значение)? существует ли риск того, что код сломается с будущей версией Python, если dict.__init__()
не вызван? Я ищу фундаментальную причину делать то или другое, здесь (практически, вызов dict.__init__()
безопасен).
Я предполагаю, что при вызове ImageDB.__init__(self, directory)
self уже является новым пустым объектом dict и поэтому нет необходимости вызывать dict.__init__
(сначала я хочу, чтобы dict был пустым). Правильно ли это?
Edit
Более практический вопрос, лежащий в основе фундаментального вопроса выше, заключается в следующем. Я думал о подклассе dict, потому что я часто использовал синтаксис db [...] (вместо того, чтобы делать db.contents [...] все время); объект только данные (атрибут) действительно действительно является dict. Я хочу добавить несколько методов в базу данных (например, get_image_by_name()
или get_image_by_code()
), и только переопределить __init__()
, потому что база данных изображений определяется каталогом, которая ее содержит.
В резюме может возникнуть (практический) вопрос: что является хорошей реализацией для того, что ведет себя как словарь, за исключением того, что его инициализация отличается (она принимает только имя каталога) и что у него есть дополнительные методы?
"Заводы" упоминались во многих ответах. Поэтому я предполагаю, что все это сводится к: вы подклассифицируете dict, переопределяете __init__()
и добавляете методы, или вы пишете функцию (factory), которая возвращает dict, к которой вы добавляете методы? Я склонен предпочесть первое решение, потому что функция factory возвращает объект, тип которого не указывает, что он имеет дополнительные семантики и методы, но что вы думаете?
Изменить 2:
Я собираюсь от всех ответить, что это не очень хорошая идея для подкласса dict, когда новый класс "не является словарем", и в частности, когда его метод __init__
не может принимать те же аргументы, что и dict __init__
(который имеет место в "практическом вопросе" выше). Другими словами, если я правильно понимаю, консенсус представляется следующим: когда вы подклассом, все методы (включая инициализацию) должны иметь одинаковую подпись, как методы базового класса. Это позволяет isinstance (subclass_instance, dict) гарантировать, что subclass_instance.__init__()
может использоваться, например, как dict.__init__()
.
Затем появляется другой практический вопрос: как должен реализоваться класс, похожий на dict, за исключением его метода инициализации? без подкласса? для этого потребуется какой-то докучливый код шаблона, не?
Ответы
Ответ 1
Вероятно, вы должны называть dict.__init__(self)
при подклассификации; на самом деле, вы не знаете, что происходит именно в dict (поскольку оно встроено), и это может варьироваться в разных версиях и реализациях. Не вызывать это может привести к неправильному поведению, поскольку вы не можете знать, где dict держит свои внутренние структуры данных.
Кстати, вы не сказали нам, что хотите делать; если вам нужен класс с поведением dict (mapping), и вам действительно не нужен диктофон (например, нет кода, делающего isinstance(x, dict)
в любом месте вашего программного обеспечения, как и должно быть), вам, вероятно, лучше использовать UserDict.UserDict
или UserDict.DictMixin
, если вы находитесь на python <= 2.5 или collections.MutableMapping
, если вы находитесь на python >= 2.6. Это обеспечит вашему классу отличное поведение диктата.
EDIT: я прочитал в другом комментарии, что вы не переопределяете какой-либо метод dict! Тогда нет никакого смысла в подклассе вообще, не делайте этого.
def createImageDb(directory):
d = {}
# do something to fill in the dict
return d
EDIT 2: вы хотите наследовать от dict, чтобы добавлять новые методы, но вам не нужно переопределять какие-либо. Чем может быть хороший выбор:
class MyContainer(dict):
def newmethod1(self, args):
pass
def newmethod2(self, args2):
pass
def createImageDb(directory):
d = MyContainer()
# fill the container
return d
Кстати, какие методы вы добавляете? Вы уверены, что создали хорошую абстракцию? Возможно, вам лучше использовать класс, который определяет методы, которые вам нужны, и использовать внутри него "нормальный" файл.
Factory func:
http://en.wikipedia.org/wiki/Factory_method_pattern
Это просто способ делегирования конструкции экземпляра функции вместо переопределения/изменения ее конструкторов.
Ответ 2
Обычно вы должны вызвать базовый класс '__init__
, поэтому зачем делать исключение здесь?
Либо не переопределяйте __init__
, либо если вам нужно переопределить __init__
базовый класс вызова __init__
, если вы беспокоитесь о аргументах, просто передайте * args, ** kwargs или ничего, если вы хотите, чтобы пустой dict eg
class MyDict(dict):
def __init__(self, *args, **kwargs ):
myparam = kwargs.pop('myparam', '')
dict.__init__(self, *args, **kwargs )
Мы не должны предполагать, что baseclass делает или не делает, неправильно не вызывать базовый класс __init__
Ответ 3
Остерегайтесь травления при подклассификации dict; это, например, требует __getnewargs__ в 2.7,
и, возможно, __getstate__ __setstate__ в более старых версиях. (Я не знаю, почему.)
class Dotdict( dict ):
""" d.key == d["key"] """
def __init__(self, *args, **kwargs):
dict.__init__( self, *args, **kwargs )
self.__dict__ = self
def __getnewargs__(self): # for cPickle.dump( d, file, protocol=-1)
return tuple(self)
Ответ 4
PEP 372 имеет дело с добавлением упорядоченного dict в модуль коллекций.
Он предупреждает, что "подклассификация dict - это нетривиальная задача, и многие реализации не переопределяют все методы, которые могут привести к неожиданным результатам".
Предлагаемый (и принятый) patch для python3.1 использует __init__
, который выглядит следующим образом:
+class OrderedDict(dict, MutableMapping):
+ def __init__(self, *args, **kwds):
+ if len(args) > 1:
+ raise TypeError('expected at most 1 arguments, got %d' % len(args))
+ if not hasattr(self, '_keys'):
+ self._keys = []
+ self.update(*args, **kwds)
Исходя из этого, похоже, что dict.__init__()
не нужно вызывать.
Изменить: Если вы не переопределяете или не расширяете какие-либо из методов dict
, то я согласен с Аланом Францони: используйте dict factory вместо подкласса:
def makeImageDB(*args,**kwargs):
d = {}
# modify d
return d
Ответ 5
Если вы планируете UserDict
подкласс для чего-то вроде базового типа dict
вы также можете рассмотреть UserDict
из коллекций. UserDict
предназначен для использования в подклассах.