Comment utiliser un point "." accéder aux membres du dictionnaire?

284

Comment rendre les membres du dictionnaire Python accessibles via un point "."?

Par exemple, au lieu d'écrire mydict['val'], j'aimerais écrire mydict.val.

J'aimerais également accéder aux fichiers imbriqués de cette façon. Par exemple

mydict.mydict2.val 

ferait référence à

mydict = { 'mydict2': { 'val': ... } }
Bodacydo
la source
20
Beaucoup de situations où les gens utilisent des dict imbriqués seraient tout aussi bien ou mieux servies par des dict avec des tuples que des clés, où d[a][b][c]est remplacé par d[a, b, c].
Mike Graham
7
Ce n'est pas magique: foo = {}; foo [1,2,3] = "un, deux, trois!"; foo.keys () => [(1,2,3)]
Bryan Oakley
10
Sensationnel. Wow encore. Je ne savais pas que les tuples pouvaient être des clés de dict. Wow troisième fois.
bodacydo
3
Tout objet qui est "hachable" peut être utilisé comme clé d'un dict. La plupart des objets immuables sont également lavables, mais uniquement si tout leur contenu est lavable. Le code d [1, 2, 3] fonctionne parce que "," est le "créer un opérateur de tuple"; c'est la même chose que d [(1, 2, 3)]. Les parenthèses sont souvent facultatives autour de la déclaration d'un tuple.
Larry Hastings
6
Avez-vous considéré le cas où la clé a un point en elle-même - {"my.key":"value"}? Ou lorsque la clé est un mot clé, comme "de"? Je l'ai envisagé plusieurs fois, et c'est plus de problèmes et de dépannage que d'avantages perçus.
Todor Minakov,

Réponses:

147

Vous pouvez le faire en utilisant cette classe que je viens de créer. Avec cette classe, vous pouvez utiliser l' Mapobjet comme un autre dictionnaire (y compris la sérialisation json) ou avec la notation par points. J'espère vous aider:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

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

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

Exemples d'utilisation:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
# Or
m['new_key'] = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
epool
la source
21
Pour travailler sur Python 3, j'ai mis .iteritems()à jour vers.items()
berto
13
Notez que cela se comportera différemment des attentes courantes en ce sens qu'il n'augmentera pas AttributeErrorsi l'attribut n'existe pas. Au lieu de cela, il reviendra None.
mic_e
Recommandez d'ajouter getstate et setstate afin que la copie profonde et d'autres systèmes puissent la prendre en charge.
user1363990
4
Vous pouvez simplifier votre constructeur pour self.update(*args,**kwargs). Vous pouvez également ajouter __missing__(self,key): value=self[key]= type(self)(); return value. Ensuite, vous pouvez ajouter des entrées manquantes en utilisant la notation par points. Si vous voulez qu'il soit sélectionnable, vous pouvez ajouter __getstate__et__setstate__
Jens Munk
1
Cela ferait hasattr(Map, 'anystring') is true. which means the hasattr would always return True due to overriding __getattr__`
Xiao
266

J'ai toujours gardé cela dans un fichier util. Vous pouvez également l'utiliser comme mixage dans vos propres cours.

class dotdict(dict):
    """dot.notation access to dictionary attributes"""
    __getattr__ = dict.get
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

mydict = {'val':'it works'}
nested_dict = {'val':'nested works too'}
mydict = dotdict(mydict)
mydict.val
# 'it works'

mydict.nested = dotdict(nested_dict)
mydict.nested.val
# 'nested works too'
derek73
la source
5
Réponse très simple, super! Savez-vous par hasard ce que je devrais faire pour que la tabulation soit complétée dans IPython? La classe aurait besoin d'implémenter __dir __ (auto), mais d'une manière ou d'une autre je ne peux pas le faire fonctionner.
andreas-h
8
+1 pour plus de simplicité. mais ne semble pas fonctionner sur les dict imbriqués. d = {'foo': {'bar': 'baz'}}; d = dotdict(d); d.foo.barrenvoie une erreur d'attribut, mais d.foofonctionne correctement.
tmthyjames
2
Oui, cela ne fonctionne pas pour les structures imbriquées complexes.
David
17
@tmthyjames, vous pouvez simplement renvoyer un objet de type dotdict dans la méthode getter pour accéder de manière récursive aux attributs avec la notation par points comme: python class DotDict(dict): """dot.notation access to dictionary attributes""" def __getattr__(*args): val = dict.get(*args) return DotDict(val) if type(val) is dict else val __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__
TMKasun
4
Après l'avoir expérimenté, il semble en geteffet que c'est une mauvaise idée car il reviendra Noneau lieu de
générer
117

Installer dotmapviapip

pip install dotmap

Il fait tout ce que vous voulez et sous-classe dict , il fonctionne donc comme un dictionnaire normal:

from dotmap import DotMap

m = DotMap()
m.hello = 'world'
m.hello
m.hello += '!'
# m.hello and m['hello'] now both return 'world!'
m.val = 5
m.val2 = 'Sam'

En plus de cela, vous pouvez le convertir vers et depuis dict objets:

d = m.toDict()
m = DotMap(d) # automatic conversion in constructor

Cela signifie que si un élément auquel vous souhaitez accéder est déjà en dictforme, vous pouvez le transformer enDotMap pour un accès facile:

import json
jsonDict = json.loads(text)
data = DotMap(jsonDict)
print data.location.city

Enfin, il crée automatiquement de nouvelles DotMapinstances enfants afin que vous puissiez faire des choses comme ceci:

m = DotMap()
m.people.steve.age = 31

Comparaison avec Bunch

Divulgation complète: je suis le créateur du DotMap . Je l'ai créé car il Bunchmanquait ces fonctionnalités

  • mémoriser les articles de la commande sont ajoutés et répéter dans cet ordre
  • enfant automatique DotMapcréation , ce qui fait gagner du temps et rend le code plus propre lorsque vous avez beaucoup de hiérarchie
  • construire à partir d'un dictet convertir récursivement toutes les dictinstances enfants enDotMap
Chris Redford
la source
2
:-) pouvez-vous le faire fonctionner avec des touches qui ont déjà un point dans le nom? {"test.foo": "bar"}peut être consulté via mymap.test.fooCe serait fantastique. Il faudra un peu de régression pour convertir une carte plate en carte profonde, puis lui appliquer DotMap, mais ça vaut le coup!
dlite922
Soigné. Est-il possible de faire fonctionner la liste / complétion des onglets avec les touches du cahier Jupyter? L'accès de style point est le plus précieux pour une utilisation interactive.
Dmitri
@Dmitri Cool product. Je n'en ai jamais entendu parler auparavant, donc je ne sais pas comment faire son travail de saisie semi-automatique. J'accepte que l'utilisation DotMapavec la saisie semi-automatique fonctionne mieux. J'utilise Sublime Text, qui complète automatiquement les mots clés précédemment saisis.
Chris Redford
1
Je trouve qu'il manque une extraction de dictionnaire pour des choses comme **kwargsou c = {**a, **b}. En fait, il échoue discrètement, il se comporte comme un dictionnaire vide lors de l'extraction.
Simon Streicher
@SimonStreicher J'ai testé cela avec m = DotMap(); m.a = 2; m.b = 3; print('{a} {b}'.format(**m));et j'ai obtenu l'attendu 2 3. Si vous avez un cas cassé prouvé qui fonctionne dict()mais pas DotMap(), veuillez soumettre votre code à l'onglet Problèmes dans GitHub.
Chris Redford
56

Dériver de dict et et mettre en œuvre __getattr__et__setattr__ .

Ou vous pouvez utiliser Bunch qui est très similaire.

Je ne pense pas qu'il soit possible de monkeypatch classe de dict intégré.

Kugel
la source
2
Que signifie exactement monkeypatch? J'en ai entendu parler mais pas utilisé. (Désolé que je pose de telles questions pour les débutants, je ne suis pas encore très bon avec la programmation (je ne suis qu'un étudiant de 2ème année.))
bodacydo
9
Monkeypatching utilise la dynamicité de Python (ou n'importe quel langage) pour changer quelque chose qui serait généralement défini dans le code source. Cela s'applique particulièrement à la modification de la définition des classes après leur création.
Mike Graham
Si vous utilisez beaucoup cette fonctionnalité, méfiez-vous de la vitesse de Bunch. Je l'utilisais assez fréquemment et cela a fini par consommer un tiers de mon temps de demande. Consultez ma réponse pour une explication plus détaillée de cela.
JayD3e
22

Fabric a une implémentation vraiment sympa et minimale . En étendant cela pour permettre un accès imbriqué, nous pouvons utiliser un defaultdict, et le résultat ressemble à ceci:

from collections import defaultdict

class AttributeDict(defaultdict):
    def __init__(self):
        super(AttributeDict, self).__init__(AttributeDict)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        self[key] = value

Utilisez-le comme suit:

keys = AttributeDict()
keys.abc.xyz.x = 123
keys.abc.xyz.a.b.c = 234

Cela développe un peu la réponse de Kugel à "Dériver de dict et et mettre en œuvre __getattr__et __setattr__". Vous savez maintenant comment!

Dave
la source
1
Celui-là est génial!
Thomas Klinger
Agréable d'inclure un dict par défaut - mais cela ne semble fonctionner que lorsque vous démarrez un dict à partir de zéro. Si nous devons convertir un dict existant en un "dotdict" récursivement. Voici une alternative dotdictqui permet de convertir dictrécursivement un objet existant : gist.github.com/miku/…
miku
19

J'ai essayé ceci:

class dotdict(dict):
    def __getattr__(self, name):
        return self[name]

vous pouvez __getattribute__aussi essayer .

faire de chaque dict un type de dotdict serait suffisant, si vous voulez l'initier à partir d'un dict multicouche, essayez __init__aussi de l' implémenter .

tdihp
la source
oups, la réponse de @Kugel est similaire.
tdihp
1
tdihp, j'aime toujours votre réponse parce que je l'ai comprise plus vite - elle a le code réel.
yigal
1
+1 pour le code réel. Mais la suggestion de @ Kugel d'utiliser Bunch est également très bonne.
Dannid
J'ai trouvé utile d'intégrer cela dans une fonction en la plaçant def docdict(name):devant et ensuite `if isinstance (nom, dict): return DotDict (name) return
name`
grand exemple simple .. J'ai étendu cela un peu afin qu'un dict imbriqué soit facilement enchaîné, similaire à @DanielMoskovich, mais renvoie également les nœuds feuilles correctement pour int, string, etc ... ou null s'il n'est pas trouvéclass dotdict(dict): def __getattr__(self, name): if name not in self: return None elif type(self[name]) is dict: return JsonDot(self[name]) else: return self[name]
D Sievers
11

Non. L'accès aux attributs et l'indexation sont des choses distinctes en Python, et vous ne devriez pas vouloir qu'ils fonctionnent de la même manière. Créez une classe (éventuellement celle créée par namedtuple) si vous avez quelque chose qui devrait avoir des attributs accessibles et utilisez la []notation pour obtenir un élément d'un dict.

Mike Graham
la source
Merci d'avoir répondu. Mais jetez un œil à cette question que je viens également de poser: stackoverflow.com/questions/2352252/… Cela semble être une bonne idée à utiliser .au lieu d' []accéder à des structures de données complexes dans les modèles Mako.
bodacydo
2
Je peux voir un cas d'utilisation pour cela; en fait, je l'ai fait il y a seulement quelques semaines. Dans mon cas, je voulais un objet auquel je pourrais accéder aux attributs avec la notation par points. J'ai trouvé très facile d'hériter simplement de dict donc j'obtiens toutes les fonctionnalités de dict intégrées, mais l'interface publique de cet objet utilise la notation par points (c'est essentiellement une interface en lecture seule pour certaines données statiques). Mes utilisateurs sont beaucoup plus satisfaits de 'foo.bar' que de 'foo ["bar"]' et je suis heureux de pouvoir utiliser les fonctionnalités du type de données dict.
Bryan Oakley
10
Vous connaissez déjà un bon style Python: nous vous disons, ne prétendez pas que les valeurs d'un dict sont des attributs. C'est une mauvaise pratique. Par exemple, que faire si vous souhaitez stocker une valeur avec le même nom qu'un attribut existant d'un dict, comme "items" ou "get" ou "pop"? Probablement quelque chose de déroutant. Alors ne le fais pas!
Larry Hastings
5
Oups, j'ai oublié des attributs comme 'items', 'get' ou 'pop. Merci d'avoir cité cet exemple important!
bodacydo
5
@Gabe, cela fait longtemps ... mais je pense que cela vaut la peine d'être dit. Ce n'est pas "assez bon en JS": c'est "assez horrible en JS". Cela devient amusant lorsque vous stockez des clés / attr qui ont le même nom que d'autres attributs importants dans la chaîne prototypique.
bgusach
11

Si vous souhaitez décaper votre dictionnaire modifié, vous devez ajouter quelques méthodes d'état aux réponses ci-dessus:

class DotDict(dict):
    """dot.notation access to dictionary attributes"""
    def __getattr__(self, attr):
        return self.get(attr)
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

    def __getstate__(self):
        return self

    def __setstate__(self, state):
        self.update(state)
        self.__dict__ = self
volodymyr
la source
Merci pour le commentaire sur le décapage. J'ai été rendu fou par cette erreur et j'ai seulement réalisé que c'était à cause de ce problème!
Shagru
Cela se produit également lorsque vous utilisez copy.deepcopy. Cet ajout est nécessaire.
user1363990
Simplification:__getattr__ = dict.get
martineau
9

En s'appuyant sur la réponse de Kugel et en prenant en compte les mots de prudence de Mike Graham, que se passe-t-il si nous fabriquons un emballage?

class DictWrap(object):
  """ Wrap an existing dict, or create a new one, and access with either dot 
    notation or key lookup.

    The attribute _data is reserved and stores the underlying dictionary.
    When using the += operator with create=True, the empty nested dict is 
    replaced with the operand, effectively creating a default dictionary
    of mixed types.

    args:
      d({}): Existing dict to wrap, an empty dict is created by default
      create(True): Create an empty, nested dict instead of raising a KeyError

    example:
      >>>dw = DictWrap({'pp':3})
      >>>dw.a.b += 2
      >>>dw.a.b += 2
      >>>dw.a['c'] += 'Hello'
      >>>dw.a['c'] += ' World'
      >>>dw.a.d
      >>>print dw._data
      {'a': {'c': 'Hello World', 'b': 4, 'd': {}}, 'pp': 3}

  """

  def __init__(self, d=None, create=True):
    if d is None:
      d = {}
    supr = super(DictWrap, self)  
    supr.__setattr__('_data', d)
    supr.__setattr__('__create', create)

  def __getattr__(self, name):
    try:
      value = self._data[name]
    except KeyError:
      if not super(DictWrap, self).__getattribute__('__create'):
        raise
      value = {}
      self._data[name] = value

    if hasattr(value, 'items'):
      create = super(DictWrap, self).__getattribute__('__create')
      return DictWrap(value, create)
    return value

  def __setattr__(self, name, value):
    self._data[name] = value  

  def __getitem__(self, key):
    try:
      value = self._data[key]
    except KeyError:
      if not super(DictWrap, self).__getattribute__('__create'):
        raise
      value = {}
      self._data[key] = value

    if hasattr(value, 'items'):
      create = super(DictWrap, self).__getattribute__('__create')
      return DictWrap(value, create)
    return value

  def __setitem__(self, key, value):
    self._data[key] = value

  def __iadd__(self, other):
    if self._data:
      raise TypeError("A Nested dict will only be replaced if it's empty")
    else:
      return other
MJ
la source
8

Utilisation SimpleNamespace:

>>> from types import SimpleNamespace   
>>> d = dict(x=[1, 2], y=['a', 'b'])
>>> ns = SimpleNamespace(**d)
>>> ns.x
[1, 2]
>>> ns
namespace(x=[1, 2], y=['a', 'b'])
Dmitry Zotikov
la source
1
Cette approche fonctionne mieux. (avec json chargé à partir du fichier)
ged
Est-ce que cela explique les dict imbriqués?
Mojimi
1
Ne prend pas en charge le Dict imbriqué. docs.python.org/3.3/library/types.html#types.SimpleNamespace
Carson
6

J'aime le Munch et il offre beaucoup d'options pratiques en plus de l'accès aux points.

importer munch

temp_1 = {'person': {'fname': 'senthil', 'lname': 'ramalingam'}}

dict_munch = munch.munchify (temp_1)

dict_munch.person.fname

Senthil
la source
6

Je suis récemment tombé sur la ' Box bibliothèque ' qui fait la même chose.

Commande d'installation: pip install python-box

Exemple:

from box import Box

mydict = {"key1":{"v1":0.375,
                    "v2":0.625},
          "key2":0.125,
          }
mydict = Box(mydict)

print(mydict.key1.v1)

J'ai trouvé qu'il était plus efficace que d'autres bibliothèques existantes comme dotmap, qui génèrent des erreurs de récursivité python lorsque vous avez de gros dict imbriqués.

lien vers la bibliothèque et les détails: https://pypi.org/project/python-box/

Pradip Gupta
la source
5

Utilisation __getattr__, très simple, fonctionne en Python 3.4.3

class myDict(dict):
    def __getattr__(self,val):
        return self[val]


blockBody=myDict()
blockBody['item1']=10000
blockBody['item2']="StackOverflow"
print(blockBody.item1)
print(blockBody.item2)

Production:

10000
StackOverflow
IRSHAD
la source
4

Le langage lui-même ne le prend pas en charge, mais parfois c'est toujours une exigence utile. Outre la recette Bunch, vous pouvez également écrire une petite méthode qui peut accéder à un dictionnaire en utilisant une chaîne en pointillés:

def get_var(input_dict, accessor_string):
    """Gets data from a dictionary using a dotted accessor-string"""
    current_data = input_dict
    for chunk in accessor_string.split('.'):
        current_data = current_data.get(chunk, {})
    return current_data

qui prendrait en charge quelque chose comme ceci:

>> test_dict = {'thing': {'spam': 12, 'foo': {'cheeze': 'bar'}}}
>> output = get_var(test_dict, 'thing.spam.foo.cheeze')
>> print output
'bar'
>>
pbanka
la source
4

Pour s'appuyer sur la réponse d'epool, cette version vous permet d'accéder à n'importe quel dict à l'intérieur via l'opérateur point:

foo = {
    "bar" : {
        "baz" : [ {"boo" : "hoo"} , {"baba" : "loo"} ]
    }
}

Par exemple, foo.bar.baz[1].babarenvoie "loo".

class Map(dict):
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    if isinstance(v, dict):
                        v = Map(v)
                    if isinstance(v, list):
                        self.__convert(v)
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                if isinstance(v, dict):
                    v = Map(v)
                elif isinstance(v, list):
                    self.__convert(v)
                self[k] = v

    def __convert(self, v):
        for elem in xrange(0, len(v)):
            if isinstance(v[elem], dict):
                v[elem] = Map(v[elem])
            elif isinstance(v[elem], list):
                self.__convert(v[elem])

    def __getattr__(self, attr):
        return self.get(attr)

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

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]
touche mon corps
la source
1
Python 3: remplacer iteritems()par items()et xrange()avecrange()
sasawatc
3
def dict_to_object(dick):
    # http://stackoverflow.com/a/1305663/968442

    class Struct:
        def __init__(self, **entries):
            self.__dict__.update(entries)

    return Struct(**dick)

Si l'on décide de convertir définitivement cela dicten objet, cela devrait le faire. Vous pouvez créer un objet jetable juste avant d'y accéder.

d = dict_to_object(d)
nehem
la source
def attr (** kwargs): o = lambda: aucun o .__ dict __. update (** kwargs) return o
throws_exceptions_at_you
2

J'ai fini par essayer les deux AttrDict et Bunchbibliothèques et les a trouvés pour être un moyen de ralentir pour mes utilisations. Après qu'un ami et moi nous y sommes penchés, nous avons constaté que la méthode principale pour écrire ces bibliothèques fait que la bibliothèque récursive agressivement à travers un objet imbriqué et fait des copies de l'objet dictionnaire partout. Dans cet esprit, nous avons apporté deux changements clés. 1) Nous avons créé des attributs chargés paresseusement 2) au lieu de créer des copies d'un objet dictionnaire, nous créons des copies d'un objet proxy léger. Ceci est la mise en œuvre finale. L'augmentation des performances de l'utilisation de ce code est incroyable. Lorsque j'utilise AttrDict ou Bunch, ces deux bibliothèques à elles seules ont consommé respectivement 1/2 et 1/3 de mon temps de demande (quoi !?). Ce code a réduit ce temps à presque rien (quelque part dans la plage de 0,5 ms). Cela dépend bien sûr de vos besoins, mais si vous utilisez un peu cette fonctionnalité dans votre code,

class DictProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    def __getattr__(self, key):
        try:
            return wrap(getattr(self.obj, key))
        except AttributeError:
            try:
                return self[key]
            except KeyError:
                raise AttributeError(key)

    # you probably also want to proxy important list properties along like
    # items(), iteritems() and __len__

class ListProxy(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return wrap(self.obj[key])

    # you probably also want to proxy important list properties along like
    # __iter__ and __len__

def wrap(value):
    if isinstance(value, dict):
        return DictProxy(value)
    if isinstance(value, (tuple, list)):
        return ListProxy(value)
    return value

Voir l'implémentation originale ici par https://stackoverflow.com/users/704327/michael-merickel .

L'autre chose à noter est que cette implémentation est assez simple et n'implémente pas toutes les méthodes dont vous pourriez avoir besoin. Vous devrez les écrire comme requis sur les objets DictProxy ou ListProxy.

JayD3e
la source
0

Je voudrais lancer ma propre solution sur le ring:

https://github.com/skorokithakis/jsane

Il vous permet d'analyser JSON en quelque chose auquel vous pouvez accéder with.attribute.lookups.like.this.r(), principalement parce que je n'avais pas vu cette réponse avant de commencer à travailler dessus.

Stavros Korokithakis
la source
Python est coupable de quelques erreurs de conception simples et embêtantes, l'augmentation KeyErrorest l'une d'entre elles. Quand on accède à la clé qui n'existe pas, tout ce qu'il a à faire est de retourner Nonesimilaire au comportement JS. Je suis un grand fan de l'autovivification à la fois pour la lecture et l'écriture. Votre bibliothèque est la plus proche de l'idéal.
nehem
0

Pas une réponse directe à la question de l'OP, mais inspirée et peut-être utile pour certains .. J'ai créé une solution basée sur les objets en utilisant le __dict__code interne (en aucun cas optimisé)

payload = {
    "name": "John",
    "location": {
        "lat": 53.12312312,
        "long": 43.21345112
    },
    "numbers": [
        {
            "role": "home",
            "number": "070-12345678"
        },
        {
            "role": "office",
            "number": "070-12345679"
        }
    ]
}


class Map(object):
    """
    Dot style access to object members, access raw values
    with an underscore e.g.

    class Foo(Map):
        def foo(self):
            return self.get('foo') + 'bar'

    obj = Foo(**{'foo': 'foo'})

    obj.foo => 'foobar'
    obj._foo => 'foo'

    """

    def __init__(self, *args, **kwargs):
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self.__dict__[k] = v
                    self.__dict__['_' + k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self.__dict__[k] = v
                self.__dict__['_' + k] = v

    def __getattribute__(self, attr):
        if hasattr(self, 'get_' + attr):
            return object.__getattribute__(self, 'get_' + attr)()
        else:
            return object.__getattribute__(self, attr)

    def get(self, key):
        try:
            return self.__dict__.get('get_' + key)()
        except (AttributeError, TypeError):
            return self.__dict__.get(key)

    def __repr__(self):
        return u"<{name} object>".format(
            name=self.__class__.__name__
        )


class Number(Map):
    def get_role(self):
        return self.get('role')

    def get_number(self):
        return self.get('number')


class Location(Map):
    def get_latitude(self):
        return self.get('lat') + 1

    def get_longitude(self):
        return self.get('long') + 1


class Item(Map):
    def get_name(self):
        return self.get('name') + " Doe"

    def get_location(self):
        return Location(**self.get('location'))

    def get_numbers(self):
        return [Number(**n) for n in self.get('numbers')]


# Tests

obj = Item({'foo': 'bar'}, **payload)

assert type(obj) == Item
assert obj._name == "John"
assert obj.name == "John Doe"
assert type(obj.location) == Location
assert obj.location._lat == 53.12312312
assert obj.location._long == 43.21345112
assert obj.location.latitude == 54.12312312
assert obj.location.longitude == 44.21345112

for n in obj.numbers:
    assert type(n) == Number
    if n.role == 'home':
        assert n.number == "070-12345678"
    if n.role == 'office':
        assert n.number == "070-12345679"
Hedde van der Heide
la source
0

Un moyen simple d'obtenir un accès point (mais pas un accès tableau) est d'utiliser un objet simple en Python. Comme ça:

class YourObject:
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

... et utilisez-le comme ceci:

>>> obj = YourObject(key="value")
>>> print(obj.key)
"value"

... pour le convertir en dict:

>>> print(obj.__dict__)
{"key": "value"}
Emil Stenström
la source
0

Cette solution est un raffinement par rapport à celle offerte par epool pour répondre à l'exigence du PO d'accéder aux dict imbriqués de manière cohérente. La solution d'epool ne permettait pas d'accéder aux dict imbriqués.

class YAMLobj(dict):
    def __init__(self, args):
        super(YAMLobj, self).__init__(args)
        if isinstance(args, dict):
            for k, v in args.iteritems():
                if not isinstance(v, dict):
                    self[k] = v
                else:
                    self.__setattr__(k, YAMLobj(v))


    def __getattr__(self, attr):
        return self.get(attr)

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

    def __setitem__(self, key, value):
        super(YAMLobj, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(YAMLobj, self).__delitem__(key)
        del self.__dict__[key]

Avec cette classe, on peut maintenant faire quelque chose comme: A.B.C.D.

deepak
la source
0

Cela fonctionne également avec les dictés imbriqués et garantit que les dictés ajoutés ultérieurement se comportent de la même manière:

class DotDict(dict):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Recursively turn nested dicts into DotDicts
        for key, value in self.items():
            if type(value) is dict:
                self[key] = DotDict(value)

    def __setitem__(self, key, item):
        if type(item) is dict:
            item = DotDict(item)
        super().__setitem__(key, item)

    __setattr__ = __setitem__
    __getattr__ = dict.__getitem__
Yaniv K.
la source
0

La réponse de @ derek73 est très nette , mais elle ne peut pas être décapée ni copiée (en profondeur), et elle revientNone pour les clés manquantes. Le code ci-dessous corrige cela.

Edit: je n'ai pas vu la réponse ci - dessus qui aborde exactement le même point (vote positif). Je laisse la réponse ici pour référence.

class dotdict(dict):
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)
marnix
la source
-1

Une solution un peu délicate

class DotDict(dict):

    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

    def __getattr__(self, key):

        def typer(candidate):
            if isinstance(candidate, dict):
                return DotDict(candidate)

            if isinstance(candidate, str):  # iterable but no need to iter
                return candidate

            try:  # other iterable are processed as list
                return [typer(item) for item in candidate]
            except TypeError:
                return candidate

            return candidate

        return typer(dict.get(self, key))
Yonks Somarl
la source