Klasa Pythona, która działa jak dict


114

Chcę napisać niestandardową klasę, która zachowuje się dicttak, jak - więc dziedziczę z dict.

Moje pytanie brzmi jednak: czy muszę utworzyć członka prywatnego dictw mojej __init__()metodzie ?. Nie widzę w tym sensu, ponieważ mam już dictzachowanie, jeśli po prostu odziedziczę po dict.

Czy ktoś może wskazać, dlaczego większość fragmentów dziedziczenia wygląda tak, jak ten poniżej?

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

   # other methods follow

Zamiast prostszego ...

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

   # other methods follow

Właściwie myślę, że podejrzewam, że odpowiedź na to pytanie jest taka, że ​​użytkownicy nie mogą uzyskać bezpośredniego dostępu do Twojego słownika (tj. Muszą korzystać z podanych przez Ciebie metod dostępu).

A co z operatorem dostępu do tablicy []? Jak można to zaimplementować? Jak dotąd nie widziałem przykładu pokazującego, jak przesłonić []operator.

Więc jeśli []w klasie niestandardowej nie ma funkcji dostępu, dziedziczone metody bazowe będą działać na innym słowniku?

Wypróbowałem następujący fragment kodu, aby sprawdzić, czy rozumiem dziedziczenie w Pythonie:

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]

Otrzymałem następujący błąd:

KeyError: <ID funkcji wbudowanej>

Co jest nie tak z powyższym kodem?

Jak poprawić klasę, myDictaby móc pisać taki kod?

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

[Edytować]

Zedytowałem powyższy przykładowy kod, aby pozbyć się głupiego błędu, który popełniłem, zanim odskoczyłem od biurka. To była literówka (powinienem to zauważyć w komunikacie o błędzie).

Odpowiedzi:



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
Jeśli zamierzasz podklasę dict, powinieneś użyć samego obiektu (używając super) zamiast po prostu delegować do instancji __dict__- co zasadniczo oznacza, że ​​tworzysz dwie dykty dla każdej instancji.
Aaron Hall

8
self .__ dict__ to nie to samo, co rzeczywista zawartość słownika. Każdy obiekt Pythona, niezależnie od jego typu, posiada _dict__atrybut zawierający wszystkie atrybuty obiektu (metody, pola itp.). Zdajesz nie chcą poeksperymentować z tego, chyba że chcesz napisać kod, który modyfikuje się ...
Raik

86

Lubię to

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

Teraz możesz korzystać z wbudowanych funkcji, takich jak dict.get()as self.get().

Nie musisz zawijać ukrytego self._dict. Twoja klasa to już dykt.


3
To. Nie ma sensu dziedziczyć z dictbez uprzedniego wywołania konstruktora.
sykora,

1
Zauważ, że twój dziedziczony w dictrzeczywistości zawiera 2 instancje dyktowania: pierwszy to odziedziczony kontener, a drugi to dykt przechowujący atrybuty klasy - możesz tego uniknąć, używając slotów .
ankostis

1
W __dict__rzeczywistości jest on tworzony tylko przy pierwszym dostępie, więc jeśli użytkownicy nie próbują go używać, jest w porządku. __slots__byłoby jednak miło.
Aaron Hall

9
Użyj spacji po przecinkach, dzikusie! ;-)
James Burke

10

W trosce o kompletność, oto link do dokumentacji wspomnianej przez @ björn-pollex dla najnowszego Pythona 2.x (2.7.7 w momencie pisania):

Emulowanie typów kontenerów

(Przepraszam, że nie używam funkcji komentarzy, po prostu nie mogę tego robić przez stackoverflow.)


3

Problem z tym fragmentem kodu:

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

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


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

... polega na tym, że metoda 'add' (... i każda metoda, która ma być członkiem klasy) musi mieć zadeklarowane jawne 'self' jako pierwszy argument, na przykład:

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

Aby zaimplementować przeciążenie operatora w celu uzyskania dostępu do elementów za pomocą klucza, poszukaj w dokumentacji metod magicznych __getitem__i __setitem__.

Zauważ, że ponieważ Python używa Duck Typing, w rzeczywistości może nie być powodu, aby wyprowadzać twoją niestandardową klasę dict z klasy dict języka - nie wiedząc więcej o tym, co próbujesz zrobić (np. Jeśli musisz przekazać instancję this w jakimś kodzie, który się zepsuje, chyba że isinstance(MyDict(), dict) == True), może lepiej będzie po prostu zaimplementować API, które sprawi, że Twoja klasa będzie wystarczająco podobna do dyktowania, i zatrzymaj się na tym.


3

Oto alternatywne rozwiązanie:

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

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

To jest złe, ponieważ nie definiujesz żadnej niestandardowej metody, a także istnieją inne problemy, jak powiedziała inna odpowiedź.
Shital Shah

to jest dokładnie to, czego potrzebowałem. Dziękuję Ci!
jakebrinkmann

2

Naprawdę nie widzę nigdzie właściwej odpowiedzi na to pytanie

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

Jedyne, co naprawdę musisz zrobić, to zdefiniować własne __init__- to naprawdę wszystko, co jest.

Inny przykład (trochę bardziej złożony):

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"}

Ten ostatni przykład jest przydatny, gdy chcesz osadzić jakąś logikę.

W każdym razie ... w skrócie class GiveYourClassAName(dict)wystarczy, aby twoja klasa zachowywała się jak dyktando. Każda operacja dyktowania, którą wykonujesz, selfbędzie taka sama jak zwykły dykt.


1

To jest moje najlepsze rozwiązanie. Używałem tego wiele razy.

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

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

Możesz użyć takich jak:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])

0

Nigdy nie dziedzicz z wbudowanego w Pythona dyktu! na przykład updatemetoda nie była używana __setitem__, robią dużo dla optymalizacji. Użyj UserDict.

from collections import UserDict

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

1
Opierając się na tym, czego nikt nie powinien nigdy dziedziczyć po wbudowanym dyktowaniu? Z dokumentacji ( docs.python.org/3/library/collections.html#collections.UserDict ): „Potrzeba tej klasy została częściowo zastąpiona możliwością tworzenia podklas bezpośrednio z dict; jednak ta klasa może być łatwiejsza do pracować, ponieważ słownik źródłowy jest dostępny jako atrybut. " Również na tej samej stronie: Moduł kolekcji jest „Przestarzały od wersji 3.3, zostanie usunięty w wersji 3.9: Przeniesiono abstrakcyjne klasy bazowe kolekcji do modułu collections.abc”.… Który nie ma UserDict.
NumesSanguis

Podejrzewam, że jest to inspirowane lub przynajmniej rezonuje z treyhunner.com/2019/04/ ...
tripleee
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.