Класс python, который действует как dict

114

Я хочу написать собственный класс, который ведет себя как dict - так что я наследую от dict.

Однако мой вопрос: нужно ли мне создавать закрытый dictчлен в моем __init__()методе? Я не вижу в этом смысла, так как у меня уже естьdict поведение, если я просто наследую от dict.

Может ли кто-нибудь указать, почему большинство фрагментов наследования выглядят так, как показано ниже?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

Вместо более простого ...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

На самом деле, я думаю, что я подозреваю, что ответ на вопрос состоит в том, что пользователи не могут напрямую обращаться к вашему словарю (т.е. они должны использовать методы доступа, которые вы предоставили).

Однако как насчет оператора доступа к массиву []? Как бы это реализовать? Пока я не видел примера, показывающего, как переопределить []оператор.

Так что если [] функция доступа не предоставляется в пользовательском классе, унаследованные базовые методы будут работать в другом словаре?

Я попробовал следующий фрагмент, чтобы проверить свое понимание наследования Python:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

Получила следующую ошибку:

KeyError: <встроенный идентификатор функции>

Что не так с приведенным выше кодом?

Как мне исправить класс, myDictчтобы я мог писать такой код?

md = myDict()
md['id'] = 123

[Редактировать]

Я отредактировал приведенный выше пример кода, чтобы избавиться от глупой ошибки, которую допустил до того, как выскочил из-за стола. Это была опечатка (я должен был заметить это по сообщению об ошибке).

небесный орел
источник

Ответы:

105
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

    def __getitem__(self, key):
        return self.__dict__[key]

    def __repr__(self):
        return repr(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

    def __iter__(self):
        return iter(self.__dict__)

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}
Рики Уилсон
источник
37
Если вы собираетесь создать подкласс dict, вам следует использовать сам объект (using super) вместо того, чтобы просто делегировать его экземпляру __dict__- что по сути означает, что вы создаете два dicts для каждого экземпляра.
Аарон Холл
8
self .__ dict__ не совпадает с фактическим содержимым словаря. У каждого объекта python, независимо от его типа, есть _dict__все атрибуты объекта (методы, поля и т. Д.). Вы не хотите возиться с этим, если вы не хотите писать код, который сам себя
Райк
86

Как это

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Теперь вы можете использовать встроенные функции, например dict.get()as self.get().

Вам не нужно заворачивать скрытое self._dict. Ваш класс уже является диктатом.

С. Лотт
источник
3
Это. Нет смысла наследовать от dictбез предварительного вызова его конструктора.
sykora
1
Обратите внимание, что ваш унаследованный dictфактически содержит 2 dict-instance: 1-й - унаследованный контейнер, а 2-й - dict, содержащий атрибуты класса - вы можете избежать этого, используя слоты .
ankostis
1
__dict__Фактически создается только при его первом обращении, так что до тех пор , как пользователи не пытаются использовать его, это прекрасно. __slots__было бы неплохо.
Аарон Холл
10
Используйте пробелы после запятых, которые вы злили! ;-)
Джеймс Берк
10

Для полноты, вот ссылка на документацию, упомянутую @ björn-pollex для последней версии Python 2.x (2.7.7 на момент написания):

Эмуляция типов контейнеров

(Извините за то, что не использую функцию комментариев, мне просто не разрешено делать это с помощью stackoverflow.)

He1ix
источник
3

Проблема с этим фрагментом кода:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

... в том, что ваш метод 'add' (... и любой метод, который вы хотите быть членом класса) должен иметь явное 'self', объявленное в качестве первого аргумента, например:

def add(self, 'id', 23):

Чтобы реализовать перегрузку оператора для доступа к элементам по ключу, посмотрите в документации волшебные методы __getitem__и __setitem__.

Обратите внимание, что, поскольку Python использует Duck Typing, на самом деле может не быть причин для получения вашего пользовательского класса dict из класса dict языка - без дополнительных сведений о том, что вы пытаетесь сделать (например, если вам нужно передать экземпляр этого class в некоторый код, который не сломается, если только isinstance(MyDict(), dict) == True), вам может быть лучше просто реализовать API, который делает ваш класс достаточно похожим на dict, и остановиться на этом.

bgporter
источник
3

Вот альтернативное решение:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2
张小诚
источник
Это плохо, потому что вы не определяете какой-либо настраиваемый метод, а также есть другие проблемы, как сказал другой ответ.
Шитал Шах
это именно то, что мне нужно. Спасибо!
jakebrinkmann
2

Я действительно нигде не вижу правильного ответа на это

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

Все, что вам действительно нужно сделать, это определить свое собственное __init__- это все, что вам нужно.

Другой пример (немного посложнее):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

Этот последний пример удобен, когда вы хотите встроить какую-то логику.

В любом случае ... короче говоря class GiveYourClassAName(dict), этого достаточно, чтобы ваш класс вел себя как диктатор. Любая операция dict, которую вы выполняете, selfбудет ничем не хуже обычного dict.

Дэнни Мейер
источник
1

Это мое лучшее решение. Я использовал это много раз.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

Вы можете использовать как:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])
мадоган
источник
0

Никогда не наследовать встроенный dict Python! например updateметод не используется __setitem__, они много делают для оптимизации. Используйте UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
user2674414
источник
1
На основании чего никто никогда не должен наследовать от встроенного dict? Из документации ( docs.python.org/3/library/collections.html#collections.UserDict ): «Потребность в этом классе частично устранена возможностью создания подкласса непосредственно из dict; однако этот класс может быть проще работать, потому что базовый словарь доступен как атрибут ". Также на той же странице: Модуль коллекций "Не рекомендуется с версии 3.3, будет удален в версии 3.9: Абстрактные базовые классы коллекций перенесены в модуль collections.abc." ... который не имеет UserDict.
NumesSanguis
Я подозреваю, что это вдохновлено или, по крайней мере, резонирует с treyhunner.com/2019/04/…
tripleee