Una classe python che agisce come dict


113

Voglio scrivere una classe personalizzata che si comporti come dict- quindi, sto ereditando da dict.

La mia domanda, tuttavia, è: devo creare un dictmembro privato nel mio __init__()metodo ?. Non vedo il senso di questo, dal momento che ho già il dictcomportamento se eredito semplicemente da dict.

Qualcuno può sottolineare perché la maggior parte degli snippet di eredità assomiglia a quello qui sotto?

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

   # other methods follow

Invece del più semplice ...

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

   # other methods follow

In realtà, penso di sospettare che la risposta alla domanda sia così che gli utenti non possono accedere direttamente al tuo dizionario (cioè devono usare i metodi di accesso che hai fornito).

Tuttavia, per quanto riguarda l'operatore di accesso all'array []? Come implementarlo? Finora non ho visto un esempio che mostra come sovrascrivere l' []operatore.

Quindi se una []funzione di accesso non è fornita nella classe personalizzata, i metodi di base ereditati funzioneranno su un dizionario diverso?

Ho provato il seguente frammento per testare la mia comprensione dell'ereditarietà di 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]

Ho ricevuto il seguente errore:

KeyError: <ID funzione incorporata>

Cosa c'è di sbagliato nel codice sopra?

Come correggo la classe in myDictmodo da poter scrivere codice come questo?

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

[Modificare]

Ho modificato il codice di esempio sopra per eliminare lo stupido errore che ho fatto prima di precipitarmi via dalla scrivania. Era un errore di battitura (avrei dovuto individuarlo dal messaggio di errore).

Risposte:



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
Se stai per una sottoclasse dict, dovresti usare l'oggetto stesso (usando super) invece di delegare semplicemente all'istanza, il __dict__che significa essenzialmente che stai creando due dict per ogni istanza.
Aaron Hall

8
self .__ dict__ non è lo stesso del contenuto effettivo del dizionario. Ogni oggetto Python, indipendentemente dal suo tipo, ha un _dict__che contiene tutti gli attributi dell'oggetto (metodi, campi, ecc.). Tu non vuole pasticciare con questo a meno che non si vuole scrivere il codice che viene modificarsi ...
Raik

86

Come questo

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

Ora puoi utilizzare le funzioni integrate, dict.get()comeself.get() .

Non è necessario avvolgere un file nascosto self._dict. La tua classe è già una regola.


3
Questo. Non ha senso ereditare da dictsenza chiamare prima il suo costruttore.
sykora

1
Nota che la tua eredità dictcontiene effettivamente 2 istanze di dict: il primo è il contenitore ereditato e il secondo è il dict che contiene gli attributi di classe - puoi evitarlo usando gli slot .
ankostis

1
In __dict__realtà viene creato solo al primo accesso, quindi finché gli utenti non tentano di usarlo, va bene. __slots__sarebbe carino però.
Aaron Hall

9
Usa gli spazi dopo le virgole, selvaggio! ;-)
James Burke

10

Per completezza, ecco il collegamento alla documentazione menzionata da @ björn-pollex per l'ultimo Python 2.x (2.7.7 al momento della scrittura):

Emulazione dei tipi di contenitori

(Ci scusiamo per non aver utilizzato la funzione commenti, semplicemente non sono autorizzato a farlo da stackoverflow.)


3

Il problema con questo pezzo di codice:

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

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


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

... è che il tuo metodo 'add' (... e qualsiasi metodo tu voglia essere un membro di una classe) deve avere un esplicito 'self' dichiarato come primo argomento, come:

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

Per implementare l'overloading dell'operatore per accedere agli elementi tramite chiave, cercare nella documentazione i metodi magici __getitem__e __setitem__.

Nota che poiché Python usa Duck Typing, potrebbe non esserci alcun motivo per derivare la tua classe dict personalizzata dalla classe dict del linguaggio - senza sapere di più su ciò che stai cercando di fare (ad esempio, se devi passare un'istanza di questo class in un codice da qualche parte che si romperà a meno che isinstance(MyDict(), dict) == True), potrebbe essere meglio implementare semplicemente l'API che rende la tua classe sufficientemente dict-like e si ferma lì.


3

Ecco una soluzione alternativa:

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

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

Questo è negativo perché non definisci alcun metodo personalizzato e ci sono anche altri problemi come ha detto un'altra risposta.
Shital Shah

questo è esattamente ciò di cui avevo bisogno. Grazie!
jakebrinkmann

2

Non vedo davvero la risposta giusta da nessuna parte

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

Tutto quello che devi fare è definire il tuo __init__ - questo è davvero tutto quello che c'è.

Un altro esempio (un po 'più complesso):

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

Quest'ultimo esempio è utile quando vuoi incorporare un qualche tipo di logica.

Comunque ... in breve class GiveYourClassAName(dict)è sufficiente per far agire la tua classe come un dict. Qualsiasi operazione di dict su cui farai selfsarà proprio come un normale dict.


1

Questa è la mia migliore soluzione. L'ho usato molte volte.

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

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

Puoi usare come:

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

0

Non ereditare mai dal dict integrato di Python! ad esempio il updatemetodo non viene utilizzato __setitem__, fanno molto per l'ottimizzazione. Usa UserDict.

from collections import UserDict

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

1
Sulla base di cosa qualcuno non dovrebbe mai ereditare dal dict incorporato? Dalla documentazione ( docs.python.org/3/library/collections.html#collections.UserDict ): "La necessità di questa classe è stata parzialmente soppiantata dalla possibilità di creare sottoclassi direttamente da dict; tuttavia, questa classe può essere più facile da lavorare perché il dizionario sottostante è accessibile come attributo. " Sempre nella stessa pagina: Il modulo delle collezioni è "Deprecato dalla versione 3.3, sarà rimosso nella versione 3.9: Classi di base astratte delle collezioni spostate nel modulo collections.abc." ... che non ha UserDict.
NumesSanguis

Sospetto che questo sia ispirato, o almeno risuoni con, treyhunner.com/2019/04/…
tripleee
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.