#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
.