Подклассы dict: должен ли вызываться dict .__ init __ ()? - PullRequest
30 голосов
/ 09 января 2010

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

Когда подклассы диктуют:

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 […] все время); единственные данные объекта (атрибут) действительно действительно диктат. Я хочу добавить несколько методов в базу данных (например, 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, за исключением метода инициализации? без подклассов? для этого потребуется какой-нибудь надоедливый шаблонный код, не так ли?

Ответы [ 5 ]

15 голосов
/ 09 января 2010

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

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

РЕДАКТИРОВАТЬ: Я прочитал в другом комментарии, что вы не отвергаете ни один из методов 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

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

Фабричный функционал: http://en.wikipedia.org/wiki/Factory_method_pattern

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

11 голосов
/ 09 января 2010

Обычно вы должны вызывать базовый класс '__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__

3 голосов
/ 27 января 2010

Остерегайтесь маринования при подклассе 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)
2 голосов
/ 09 января 2010

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
0 голосов
/ 10 июня 2019

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

...