Клас пітона, який діє як диктант


113

Я хочу написати користувальницький клас, який поводиться так 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

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

Однак як щодо оператора доступу до масиву []? Як би хтось реалізував це? Поки що я не бачив приклад, який показує, як перекрити []оператора.

Так що якщо a [] функція доступу не передбачена у спеціальному класі, успадковані базові методи будуть працювати в іншому словнику?

Я спробував наступний фрагмент, щоб перевірити своє розуміння спадщини 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, то вам слід використовувати сам об’єкт (використовуючи super), а не просто делегувати до екземпляра __dict__- що по суті означає, що ви створюєте два дикти для кожного примірника.
Aaron Hall

8
self .__ dict__ не збігається з фактичним вмістом словника. Кожен об'єкт python, незалежно від його типу, має _dict__атрибут, який містить усі атрибути об'єкта (методи, поля тощо). Ви не хочете возитися з цим, якщо ви не хочете писати код, що модифікує себе ...
Raik

85

Подобається це

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

Тепер ви можете використовувати вбудовані функції, наприклад, dict.get()як self.get().

Не потрібно загортати приховане self._dict. Ваш клас вже є диктом.


3
Це. Немає сенсу успадковувати, dictне спочатку викликаючи його конструктор.
sykora

1
Зауважте, що ваш успадкований dictфактично містить 2 екземпляри dict: 1-й - це спадковий контейнер, а 2-й - дікт, що містить атрибути class - ви можете уникнути цього, використовуючи слоти .
ankostis

1
__dict__Фактично створюється тільки при його першому зверненні, так що до тих пір , як користувачі не намагаються використовувати його, це прекрасно. __slots__Було б добре, хоча.
Аарон Холл

9
Використовуйте пробіли після коми, які ви дикуєте! ;-)
Джеймс Берк

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 мови - не знаючи більше про те, що ви намагаєтеся зробити (наприклад, якщо вам потрібно передавати екземпляр цього класуйте в якийсь код, де не порушиться, якщо тільки isinstance(MyDict(), dict) == True), можливо, вам буде краще просто застосувати API, який робить ваш клас достатньо виразним і зупиняється на ньому.


3

Ось альтернативне рішення:

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

a = AttrDict()
a.a = 1
a.b = 2

Це погано, тому що ви не визначаєте жодного спеціального методу, а також є інші проблеми, як сказано в іншій відповіді.
Shital Shah

це саме те, що мені було потрібно. Дякую!
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), достатньо, щоб ваш клас діяв як диктант. Будь-яка операція диктанту, яку ви робите, selfбуде подібно до звичайної картини.


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

Ніколи не успадковуйте від вбудованого протоколу Python! наприклад, updateметод не використовується __setitem__, вони роблять багато для оптимізації. Використовуйте UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass

1
Виходячи з того, що хтось ніколи не повинен успадковувати від вбудованого диктату? З документації ( docs.python.org/3/library/collections.html#collections.UserDict ): "Потреба в цьому класі була частково витіснена можливістю підкласу безпосередньо з dict; однак цей клас може бути простішим працювати з тим, що базовий словник доступний як атрибут. " Також на тій же самій сторінці: Модуль колекції "Застаріло з версії 3.3, буде видалено у версії 3.9: Перенесені колекції Анотація базових класів до модуля collection.abc." ... що не має UserDict.
NumesSanguis

Я підозрюю, що це надихає або принаймні резонує з treyhunner.com/2019/04/…
tripleee
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.