Класс 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.)


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, и остановиться на этом.


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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.