web-dev-qa-db-fra.com

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

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

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

Aussi, j'aimerais accéder aux dict imbriqués de cette façon. Par exemple

mydict.mydict2.val 

ferait référence à 

mydict = { 'mydict2': { 'val': ... } }
177
bodacydo

Vous pouvez le faire en utilisant ce cours que je viens de faire. Avec cette classe, vous pouvez utiliser l'objet Map 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']
101
epool

J'ai toujours gardé ça dans un fichier util. Vous pouvez aussi l'utiliser comme mixin 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'
159
derek73

Installer dotmap via pip

pip install dotmap

Il fait tout ce que vous voulez et les sous-classes dict, ainsi il fonctionne 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 en objets dict:

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à sous la forme dict, vous pouvez le transformer en un DotMap 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 instances enfant DotMap afin que vous puissiez effectuer les opérations suivantes:

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

Comparaison à Bunch

Divulgation complète: je suis le créateur du DotMap . Je l'ai créé parce que Bunch manquait de ces fonctionnalités

  • mémoriser les articles de commande sont ajoutés et itérer dans cet ordre
  • création automatique d'enfants DotMap, ce qui permet de gagner du temps et d'obtenir un code plus propre lorsque vous avez beaucoup de hiérarchie
  • construction à partir d'une dict et conversion récursive de toutes les instances enfant dict en DotMap
81
Chris Redford

Dérivez de dict et implémentez __getattr__ et __setattr__.

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

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

56
Kugel

J'ai essayé ceci:

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

vous pouvez aussi essayer __getattribute__.

faire en sorte que chaque dicture soit un type de dotdict soit suffisant, si vous voulez l'initialiser à partir d'un dictée multicouche, essayez aussi d'implémenter __init__.

16
tdihp

(Fabric) a une très jolie et minimale implémentation . 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 explique un peu la réponse de Kugel: "Dérive de dict et implémente __getattr__ et __setattr__". Maintenant tu sais comment!

12
Dave

Si vous voulez utiliser 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
8
volodymyr

Ne pas L'accès aux attributs et l'indexation sont des choses distinctes en Python et vous ne devriez pas vouloir qu'ils fassent de même. Créez une classe (éventuellement faite 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.

6
Mike Graham

S'appuyant sur la réponse de Kugel et prenant en compte les paroles de prudence de Mike Graham, que se passe-t-il si nous fabriquons une enveloppe? 

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
6
Michael A. Jackson

La langue elle-même ne supporte pas cela, mais parfois, cela reste une exigence utile. Outre la recette Bunch, vous pouvez également écrire une petite méthode permettant d’accéder à un dictionnaire en utilisant une chaîne en pointillé:

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 soutiendrait quelque chose comme ceci:

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

J'aime le Munch et cela donne beaucoup d'options pratiques en plus de l'accès par points. 

munch d'importation 

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

dict_munch = munch.munchify (temp_1)

dict_munch.person.fname

4
Senthil

Pour compléter la réponse d'epool, cette version vous permet d'accéder à n'importe quel dict dedans via l'opérateur dot:

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

Par exemple, foo.bar.baz[1].baba renvoie "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]
3
user4343502
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 on décide de convertir définitivement cette dict en objecte, cela devrait suffire. Vous pouvez créer un objet jetable juste avant d'y accéder. 

d = dict_to_object(d)
3
nehemiah

Utilisez __getattr__, très simple, fonctionne avec 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)

Sortie:

10000
StackOverflow
2
IRSHAD

J'ai fini par essayer les bibliothèques AttrDict et Bunch et je les ai trouvées très ralenties pour mes utilisations. Après un ami et moi avons examiné, nous avons trouvé que la méthode principale pour écrire ces bibliothèques entraîne la récursivité agressive de la bibliothèque à travers un objet imbriqué et la copie de l’objet dictionnaire. Dans cet esprit, nous avons apporté deux modifications essentielles. 1) Nous avons créé des attributs chargés paresseux 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. Lors de l'utilisation de AttrDict ou Bunch, ces deux bibliothèques consommaient à elles seules respectivement 1/2 et 1/3 de mon temps de requête (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 cette fonctionnalité dans votre code, optez pour une solution aussi simple que celle-ci. 

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 la mise en œuvre originale ici de 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 au besoin sur les objets DictProxy ou ListProxy.

1
JayD3e

Un moyen simple d'obtenir un accès par points (mais pas par tableau) consiste à utiliser un objet brut 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ée:

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

J'aimerais lancer ma propre solution sur le ring:

https://github.com/skorokithakis/jsane

Cela vous permet d'analyser JSON dans 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.

0

Cela fonctionne également avec les dessins imbriqués et permet de s'assurer que les dessins ajoutés plus tard 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__
0
Yaniv K.

Cette solution est un raffinement de celle proposée par epool pour répondre à la nécessité pour le PO d’accéder de manière cohérente aux systèmes imbriqués. La solution d'epool n'autorisait pas l'accès aux plans 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

0
deepak

Une solution 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))
0
Yonks Somarl

Pas une réponse directe à la question du PO, mais inspirée par et peut-être utile pour certains .. J'ai créé une solution basée sur les objets en utilisant le __dict__ interne (En aucun cas un code 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"
0
Hedde van der Heide