Une classe python qui agit comme dict

113

Je veux écrire une classe personnalisée qui se comporte comme dict- donc, j'hérite de dict.

Ma question, cependant, est: Dois-je créer un dictmembre privé dans ma __init__()méthode?. Je ne vois pas l'intérêt de cela, car j'ai déjà le dictcomportement si j'hérite simplement dict.

Quelqu'un peut-il expliquer pourquoi la plupart des extraits d'héritage ressemblent à celui ci-dessous?

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

   # other methods follow

Au lieu du plus simple ...

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

   # other methods follow

En fait, je pense que je soupçonne que la réponse à la question est que les utilisateurs ne peuvent pas accéder directement à votre dictionnaire (c'est-à-dire qu'ils doivent utiliser les méthodes d'accès que vous avez fournies).

Cependant, qu'en est-il de l'opérateur d'accès au tableau []? Comment mettre en œuvre cela? Jusqu'à présent, je n'ai pas vu d'exemple qui montre comment remplacer l' []opérateur.

Donc, si une []fonction d'accès n'est pas fournie dans la classe personnalisée, les méthodes de base héritées fonctionneront sur un dictionnaire différent?

J'ai essayé l'extrait de code suivant pour tester ma compréhension de l'héritage 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]

J'ai eu l'erreur suivante:

KeyError: <id de la fonction intégrée>

Quel est le problème avec le code ci-dessus?

Comment corriger la classe myDictpour pouvoir écrire du code comme celui-ci?

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

[Éditer]

J'ai modifié l'exemple de code ci-dessus pour me débarrasser de l'erreur stupide que j'ai faite avant de me précipiter loin de mon bureau. C'était une faute de frappe (j'aurais dû le repérer dans le message d'erreur).

skyeagle
la source

Réponses:

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}
Ricky Wilson
la source
37
Si vous allez faire une sous dict- classe , vous devez utiliser l'objet lui-même (en utilisant super) au lieu de simplement déléguer à l'instance __dict__- ce qui signifie essentiellement que vous créez deux dictionnaires pour chaque instance.
Aaron Hall
8
self .__ dict__ n'est pas le même que le contenu réel du dictionnaire. Chaque objet python, quel que soit son type, a un _dict__qui contient tous les attributs de l'objet (méthodes, champs, etc.). Vous ne voulez pas jouer avec cela à moins que vous ne vouliez écrire du code qui se modifie lui-même ...
Raik
86

Comme ça

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

Vous pouvez maintenant utiliser les fonctions intégrées, telles dict.get()que self.get().

Vous n'avez pas besoin d'envelopper un fichier caché self._dict. Votre classe est déjà un dict.

S.Lott
la source
3
Ce. Il ne sert à dictrien d'hériter de sans appeler d'abord son constructeur.
sykora
1
Notez que votre héritage dictcontient en fait 2 dict-instance: le premier est le conteneur hérité, et le deuxième est le dict contenant les attributs de classe - vous pouvez éviter cela en utilisant des slots .
ankostis
1
Le __dict__n'est en fait créé que lors de son premier accès, donc tant que les utilisateurs n'essaient pas de l'utiliser, tout va bien. __slots__serait bien cependant.
Aaron Hall
9
Utilisez des espaces après les virgules que vous sauvage! ;-)
James Burke
10

Par souci d'exhaustivité, voici le lien vers la documentation mentionnée par @ björn-pollex pour le dernier Python 2.x (2.7.7 au moment de la rédaction):

Émulation des types de conteneurs

(Désolé de ne pas utiliser la fonction de commentaires, je ne suis tout simplement pas autorisé à le faire par stackoverflow.)

he1ix
la source
3

Le problème avec ce morceau de code:

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

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


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

... est que votre méthode 'add' (... et toute méthode que vous voulez être membre d'une classe) doit avoir un 'self' explicite déclaré comme premier argument, comme:

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

Pour implémenter la surcharge d'opérateurs pour accéder aux éléments par clé, recherchez dans la documentation les méthodes magiques __getitem__et __setitem__.

Notez que comme Python utilise Duck Typing, il peut en fait n'y avoir aucune raison de dériver votre classe dict personnalisée de la classe dict du langage - sans en savoir plus sur ce que vous essayez de faire (par exemple, si vous devez transmettre une instance de ceci classe dans un code quelque part qui se cassera à moins que isinstance(MyDict(), dict) == True), vous feriez peut-être mieux d'implémenter simplement l'API qui rend votre classe suffisamment semblable à un dict et de vous y arrêter.

bgporter
la source
3

Voici une solution alternative:

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

a = AttrDict()
a.a = 1
a.b = 2
张小诚
la source
C'est mauvais car vous ne définissez aucune méthode personnalisée et il existe également d'autres problèmes, comme l'a dit une autre réponse.
Shital Shah
c'est exactement ce dont j'avais besoin. Je vous remercie!
jakebrinkmann le
2

Je ne vois vraiment nulle part la bonne réponse

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

Tout ce que vous avez à faire est de définir le vôtre __init__- c'est vraiment tout ce qu'il y a aussi.

Un autre exemple (un peu plus complexe):

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

Ce dernier exemple est pratique lorsque vous souhaitez intégrer une sorte de logique.

Bref, class GiveYourClassAName(dict)c'est assez pour que votre classe agisse comme un dicton. Toute opération de dict que vous effectuez selfsera comme un dict normal.

Danny Meijer
la source
1

C'est ma meilleure solution. Je l'ai utilisé plusieurs fois.

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

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

Vous pouvez utiliser comme:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])
Madogan
la source
0

N'héritez jamais de dict intégré Python! par exemple, la updateméthode n'est pas utilisée __setitem__, ils font beaucoup pour l'optimisation. Utilisez UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
user2674414
la source
1
Basé sur ce que quelqu'un ne devrait jamais hériter du dict intégré? De la documentation ( docs.python.org/3/library/collections.html#collections.UserDict ): "Le besoin de cette classe a été partiellement supplanté par la possibilité de sous-classer directement à partir de dict; cependant, cette classe peut être plus facile à car le dictionnaire sous-jacent est accessible en tant qu'attribut. " Également sur la même page: Le module collections est "Déprécié depuis la version 3.3, sera supprimé dans la version 3.9: Déplacement des classes de base abstraites de collections vers le module collections.abc." ... qui n'a pas UserDict.
NumesSanguis
Je soupçonne que cela est inspiré par, ou du moins résonne avec, treyhunner.com/2019/04
...