Подкласс dict: должен ли dict ._вызывается _init__()?

#python #dictionary #inheritance

#python #подкласс #словарь #инициализация

Вопрос:

Вот двойной вопрос, с теоретической частью и практической:

При создании подкласса 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 был пустым). Это правильно?

Редактировать:

Более практический вопрос, стоящий за фундаментальным вопросом выше, заключается в следующем. Я думал о подклассе dict, потому что я бы использовал синтаксис db[…] довольно часто (вместо того, чтобы постоянно выполнять db.contents[…] ); единственные данные (атрибут) объекта действительно являются dict . Я хочу добавить несколько методов в базу данных (например get_image_by_name() , или get_image_by_code() , например) и переопределить только __init__() , потому что база данных изображений определяется каталогом, который ее содержит.

В целом, (практический) вопрос может быть следующим: какова хорошая реализация для чего-то, что ведет себя как словарь, за исключением того, что его инициализация отличается (оно принимает только имя каталога) и что у него есть дополнительные методы?

«Фабрики» упоминались во многих ответах. Итак, я предполагаю, что все сводится к следующему: вы подклассируете dict , переопределяете __init__() и добавляете методы или вы пишете (фабричную) функцию, которая возвращает dict , к которой вы добавляете методы? Я склонен предпочесть первое решение, потому что фабричная функция возвращает объект, тип которого не указывает на то, что он имеет дополнительную семантику и методы, но что вы думаете?

Редактировать 2:

Я понял из ответа каждого, что не стоит подклассировать dict, когда новый класс «не является словарем», и, в частности, когда его __init__ метод не может принимать те же аргументы, что и dict __init__ (что имеет место в «практическом вопросе» выше). Другими словами, если я правильно понимаю, консенсус, похоже, таков: при создании подкласса все методы (включая инициализацию) должны иметь ту же сигнатуру, что и методы базового класса. Это позволяет isinstance(subclass_instance, dict) гарантировать, что subclass_instance.__init__() его можно использовать dict.__init__() , например.

Затем возникает еще один практический вопрос: как должен быть реализован класс, который похож на dict, за исключением его метода инициализации? без создания подклассов? для этого потребуется какой-то надоедливый шаблонный код, нет?

Комментарии:

1. Заводская функция — это правильный путь. Если вам нужно настроить поведение экземпляра , вы можете создать подкласс. Если вы просто хотите переопределить инициализацию , вам не нужно ничего подклассировать, потому что ваши экземпляры не отличаются от стандартных. Помните, что init считается частью интерфейса не экземпляра, а класса.

2. Насколько я понимаю, для этой проблемы было бы лучше добавить __getitem__ метод в ваш ImageDB вместо подкласса dict , потому что это не dict . Это позволяет вам делать то, что вы хотите, без использования всех тех методов, pop() которые кажутся неподходящими для вашего класса.

3. @gs: Хорошая мысль о pop; это действительно, по крайней мере, на данный момент, не имеет значения (содержимое базы данных определяется только при инициализации). Я думаю, что действительно лучше, чтобы реализация точно соответствовала необходимым функциям.

Ответ №1:

Вероятно, вам следует вызывать dict.__init__(self) при создании подкласса; на самом деле, вы не знаете, что именно происходит в dict (поскольку он встроенный), и это может варьироваться в зависимости от версий и реализаций. Его невыполнение может привести к неправильному поведению, поскольку вы не можете знать, где dict хранит свои внутренние структуры данных.

Кстати, вы не сказали нам, что вы хотите сделать; если вам нужен класс с поведением dict (отображение), и вам действительно не нужен dict (например isinstance(x, dict) , в вашем программном обеспечении нигде нет кода, как и должно быть), вам, вероятно, лучше использоватьили UserDict.UserDict UserDict.DictMixin , если вы используете python <= 2.5, или collections.MutableMapping , если вы используете python > = 2.6 . Это обеспечит вашему классу отличное поведение dict .

РЕДАКТИРОВАТЬ: я прочитал в другом комментарии, что вы не переопределяете ни один из методов dict! Тогда вообще нет смысла создавать подклассы, не делайте этого.

 def createImageDb(directory):
    d = {}
    # do something to fill in the dict
    return d
  

РЕДАКТИРОВАТЬ 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
  

Кстати: какие методы вы добавляете? Вы уверены, что создаете хорошую абстракцию? Может быть, вам лучше использовать класс, который определяет нужные вам методы, и использовать для него «обычный» dict внутри.

Заводская функция: http://en.wikipedia.org/wiki/Factory_method_pattern

Это просто способ делегирования построения экземпляра функции вместо переопределения / изменения ее конструкторов.

Комментарии:

1. 1: создание подклассов, когда нет необходимости создавать подклассы, — плохая идея, фабрика намного лучше.

2. Даже если я не переопределяю методы dict, у нового класса есть дополнительные методы,… (Я исследую фабрики, спасибо за указатель!)

3. Я не уверен насчет UserDict: в документации говорится: «Этот модуль также определяет класс UserDict, который действует как оболочка вокруг объектов словаря. Необходимость в этом классе была в значительной степени вытеснена возможностью создания подклассов непосредственно из dict (функция, которая стала доступна, начиная с версии Python 2.2).»

4. хорошо, тогда вы расширяете dict новыми методами, которые можно наследовать от dict , но я бы не советовал переопределять init . Я отредактирую свой пост еще раз.

5. Извините, я переопределяю __init__ (я подробно описал это в последней версии вопроса), но больше ничего…

Ответ №2:

Обычно вы должны вызывать базовый класс ‘ __init__ так зачем делать исключение здесь?

Либо не переопределяйте __init__ , либо, если вам нужно переопределить __init__ вызов базового класса __init__ , если вы беспокоитесь о аргументах, просто передайте *args , ** kwargs или ничего, если вы хотите пустой dict, например

 class MyDict(dict):
    def __init__(self, *args, **kwargs ):
        myparam = kwargs.pop('myparam', '')
        dict.__init__(self, *args, **kwargs )
  

Мы не должны предполагать, что делает или не делает базовый класс, неправильно не вызывать базовый класс __init__

Комментарии:

1. Вызов dict __init__ — это действительно то, что я сейчас делаю. Поскольку похоже, что вызов его без аргументов ничего не дает, мне просто интересно узнать основные факты о Python, которые позволили бы не вызывать его!

2. @EOL, IMO просто неправильно не вызывать инициализацию базового класса, пока не будет очень веской причины поступить иначе

3. @Anurag: Я понимаю вашу точку зрения. Я пытаюсь немного расширить свои знания о Python, и мне было интересно, существует ли такая «очень, очень веская причина» для отказа от вызова dict.__init__(self) (без каких-либо других аргументов) (например, «он никогда ничего не сделает»).).

4. Вы могли бы даже использовать super(MyDict, self).__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 является нетривиальной задачей, и многие реализации не переопределяют все методы должным образом, что может привести к неожиданным результатам».

Предложенный (и принятый) патч для 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, а не подклассы:

 def makeImageDB(*args,**kwargs):
   d = {}
   # modify d
   return d
  

Комментарии:

1. Это интересно. Теперь не вызывать dict.__init__() Python 3.1 безопасно, но как насчет будущего? Поскольку я не переопределяю какой-либо метод в ImageDB, создание подклассов очень безопасно; только инициализация является специальной (она создает dict ).

2. Извините, EOL, я вас не понимаю. На мой взгляд, Python 3.1 — это будущее … 🙂

3. Примите во внимание, что на самом деле делает инициализация. Он обновляет dict со всеми аргументами и ключевыми словами. Это то, что должен будет сделать ваш класс, поэтому вызовите dict . __init__(self, * args, **kwds), вероятно, позаботится об этом за вас, или вам придется вызвать self.update , как это делает OrderedDict .

4. @Tor Valamo: я добавил детали к той функциональности, которую я ищу. По сути, единственными данными, содержащимися в классе, является словарь, и я хотел бы получить к нему доступ напрямую через db[…] вместо db.contents[…] . Объекты никогда не создаются с аргументами, подобными аргументам стандартного dict .

Ответ №5:

Если вы планируете подкласс чего-то вроде dict базового типа, вы также можете рассмотреть коллекции UserDict from . UserDict предназначен для создания подклассов.

Комментарии:

1. Обратите внимание, что в документации говорится: «Необходимость в классе [UserDict] была в значительной степени вытеснена возможностью создания подклассов непосредственно из dict».

2. Спасибо и да, швы, которые UserDict были в основном интересны до Python 2.2, когда вы не могли создавать подклассы dict .