Accéder aux touches dict comme un attribut?

303

Je trouve plus pratique d'accéder aux touches dict comme obj.fooau lieu de obj['foo'], j'ai donc écrit cet extrait:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

Cependant, je suppose qu'il doit y avoir une raison pour laquelle Python ne fournit pas cette fonctionnalité prête à l'emploi. Quelles seraient les mises en garde et les pièges de l'accès aux clés dict de cette manière?

Izz ad-Din Ruhulessin
la source
16
Si vous accédez à des clés codées en dur à partir d'un ensemble limité de taille fixe partout, vous feriez mieux de créer des objets qui les contiennent. collections.namedtupleest très utile pour cela.
6
stackoverflow.com/questions/3031219/… a une solution similaire mais va plus loin
keflavich
1
Trouvé un module pour cela sur github.com/bcj/AttrDict . Je ne sais pas comment cela se compare aux solutions ici et dans les questions connexes.
Matt Wilkie
J'ai également utilisé des hacks similaires, maintenant j'utiliseeasydict.EasyDict
muon
Plus de façons d'accéder aux membres du dictionnaire avec un '.' : stackoverflow.com/questions/2352181/…
Pale Blue Dot

Réponses:

304

La meilleure façon de procéder est:

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

Quelques pros:

  • Ça marche vraiment!
  • Aucune méthode de classe de dictionnaire n'est ombrée (par exemple, .keys()fonctionne très bien. À moins que - bien sûr - vous leur attribuez une valeur, voir ci-dessous)
  • Les attributs et les éléments sont toujours synchronisés
  • Essayer d'accéder à une clé inexistante en tant qu'attribut soulève correctement AttributeErrorau lieu deKeyError

Les inconvénients:

  • Des méthodes telles que .keys()ne pas fonctionner très bien si elles seront écrasés par des données entrantes
  • Provoque une fuite de mémoire dans Python <2.7.4 / Python3 <3.2.3
  • Pylint va bananes avec E1123(unexpected-keyword-arg)etE1103(maybe-no-member)
  • Pour les non-initiés, cela ressemble à de la magie pure.

Une brève explication sur la façon dont cela fonctionne

  • Tous les objets python stockent en interne leurs attributs dans un dictionnaire nommé __dict__.
  • Il n'y a aucune exigence que le dictionnaire interne doive __dict__être "juste un dict simple", donc nous pouvons assigner n'importe quelle sous-classe de dict()au dictionnaire interne.
  • Dans notre cas, nous attribuons simplement l' AttrDict()instance dans laquelle nous instancions (comme nous le sommes __init__).
  • En appelant super()la __init__()méthode, nous nous sommes assurés qu'elle se comporte (déjà) exactement comme un dictionnaire, car cette fonction appelle tout le code d' instanciation du dictionnaire .

Une des raisons pour lesquelles Python ne fournit pas cette fonctionnalité prête à l'emploi

Comme indiqué dans la liste des "inconvénients", cela combine l'espace de noms des clés stockées (qui peuvent provenir de données arbitraires et / ou non fiables!) Avec l'espace de noms des attributs de méthode dict intégrés. Par exemple:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"
Kimvais
la source
1
Pensez-vous que la fuite de mémoire se produirait avec un objet simple comme: >>> classe MyD (objet): ... def init __ (self, d): ... self .__ dict = d
Rafe
Provoque la fuite même en 2,7
pi.
1
Faites cela <= 2.7.3, car c'est ce que j'utilise.
pi.
1
Dans les notes de version 2.7.4, ils mentionnent qu'il a été corrigé (pas avant).
Robert Siemer
1
@viveksinghggits juste parce que vous accédez à des choses via le ., vous ne pouvez pas enfreindre les règles du langage :) Et je ne voudrais AttrDictpas convertir automatiquement les champs contenant de l'espace en quelque chose de différent.
Yurik
125

Vous pouvez avoir tous les caractères de chaîne légaux dans le cadre de la clé si vous utilisez la notation de tableau. Par exemple,obj['!#$%^&*()_']

Hery
la source
1
@Izkata oui. chose drôle à propos de SE qu'il y a généralement une «question principale», c'est-à-dire. titre et une «question du bas», peut-être parce que SE n'aime pas entendre «le titre dit tout»; les «mises en garde» étant celle du bas ici.
n611x007
2
Non pas que JavaScript soit un exemple particulièrement bon de langage de programmation, mais les objets dans JS prennent en charge à la fois l'accès aux attributs et la notation de tableau, ce qui permet une commodité pour le cas commun et une solution de remplacement générique pour les symboles qui ne sont pas des noms d'attributs légaux.
André Caron
@Izkata Comment cela répond-il à la question. Cette réponse dit simplement que les clés peuvent avoir n'importe quel nom.
Melab
4
@Melab La question est What would be the caveats and pitfalls of accessing dict keys in this manner?(en tant qu'attributs), et la réponse est que la plupart des caractères montrés ici ne seraient pas utilisables.
Izkata
83

De cette autre question SO il y a un excellent exemple d'implémentation qui simplifie votre code existant. Que diriez-vous:

class AttributeDict(dict): 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

Beaucoup plus concis et ne laisse aucune place à une corruption supplémentaire dans votre __getattr__et __setattr__fonctionne à l'avenir.

slacy
la source
Pourriez-vous appeler AttributeDict.update ou AttributeDict.get en utilisant cette méthode?
Dor
13
Vous devez garder à l'esprit que si vous ajoutez de nouveaux attributs au moment de l'exécution, ils ne sont pas ajoutés au dict lui-même mais à l' attribut dict . Par exemple d = AttributeDict(foo=1). d.bar = 1l'attribut bar est stocké dans l' attribut dict mais pas dans le dict lui-même. l'impression dne montre que l'élément foo.
P3trus
7
+1 parce que cela fonctionne parfaitement pour autant que je sache. @GringoSuave, @Izkata, @ P3trus Je demande à quiconque prétend qu'il échoue de montrer un exemple qui ne fonctionne pas d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Fonctionne pour moi!
Dave Abrahams
4
@DaveAbrahams Lisez la question complète et regardez les réponses de Hery, Ryan et TheCommunistDuck. Il ne s'agit pas de savoir comment procéder, mais des problèmes qui peuvent survenir .
Izkata
6
Vous devez fournir une __getattr__méthode qui déclenche un AttributeErrorsi l'attribut donné n'existe pas, sinon des choses comme getattr(obj, attr, default_value)ne fonctionnent pas (c'est-à-dire ne retournent pas default_valuesi attrn'existe pas obj)
jcdude
83

Où je réponds à la question posée

Pourquoi Python ne le propose-t-il pas prêt à l'emploi?

Je soupçonne que cela a à voir avec le Zen de Python : "Il devrait y avoir une - et de préférence une seule - façon évidente de le faire." Cela créerait deux façons évidentes d'accéder aux valeurs des dictionnaires: obj['key']et obj.key.

Mises en garde et pièges

Il s'agit notamment d'un possible manque de clarté et de confusion dans le code. c'est-à-dire que ce qui suit pourrait être déroutant pour quelqu'un d' autre qui va maintenir votre code à une date ultérieure, ou même pour vous, si vous n'y revenez pas pendant un certain temps. Encore une fois, de Zen : "La lisibilité compte!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

Si dest instancié ou KEY est défini ou d[KEY] est attribué loin de l'endroit où il d.spamest utilisé, cela peut facilement entraîner une confusion quant à ce qui est fait, car ce n'est pas un idiome couramment utilisé. Je sais que cela pourrait me confondre.

De plus, si vous modifiez la valeur de la KEYmanière suivante (mais que vous manquez de changer d.spam), vous obtenez maintenant:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

OMI, ne vaut pas l'effort.

Autres éléments

Comme d'autres l'ont noté, vous pouvez utiliser n'importe quel objet lavable (pas seulement une chaîne) comme clé de dictée. Par exemple,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 

est légal, mais

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
  File "<stdin>", line 1
  d.(2, 3)
    ^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>> 

n'est pas. Cela vous donne accès à toute la gamme de caractères imprimables ou d'autres objets hachables pour vos clés de dictionnaire, que vous n'avez pas lorsque vous accédez à un attribut d'objet. Cela rend possible une magie telle qu'une métaclasse d'objet mis en cache, comme la recette du livre de recettes Python (Ch. 9) .

Où j'éditorialise

Je préfère l'esthétique de spam.eggsOver spam['eggs'](je pense que ça a l'air plus propre), et j'ai vraiment commencé à avoir envie de cette fonctionnalité quand j'ai rencontré le namedtuple. Mais la commodité de pouvoir faire ce qui suit l'emporte.

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

Ceci est un exemple simple, mais je me retrouve souvent à utiliser des dict dans des situations différentes de celles que j'utiliserais la obj.keynotation (c'est-à-dire lorsque j'ai besoin de lire les préférences dans un fichier XML). Dans d'autres cas, lorsque je suis tenté d'instancier une classe dynamique et de gifler certains attributs pour des raisons esthétiques, je continue à utiliser un dict pour la cohérence afin d'améliorer la lisibilité.

Je suis sûr que l'OP a résolu cela depuis longtemps à sa satisfaction, mais s'il veut toujours cette fonctionnalité, alors je lui suggère de télécharger l'un des packages de pypi qui le fournit:

  • Bunch est celui que je connais le mieux. Sous-classe dedict, vous avez donc toutes ces fonctionnalités.
  • AttrDict semble également être assez bon, mais je ne le connais pas aussi bien et je n'ai pas parcouru la source avec autant de détails que j'ai Bunch .
  • Addict est activement maintenu et fournit un accès semblable à attr et plus encore.
  • Comme indiqué dans les commentaires de Rotareti, Bunch est obsolète, mais il existe un fork actif appelé Munch .

Cependant, afin d'améliorer la lisibilité de son code, je lui recommande fortement de ne pas mélanger ses styles de notation. S'il préfère cette notation, il doit simplement instancier un objet dynamique, y ajouter les attributs souhaités et l'appeler un jour:

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}


Où je mets à jour, pour répondre à une question de suivi dans les commentaires

Dans les commentaires (ci-dessous), Elmo demande:

Et si vous voulez aller plus loin? (en référence au type (...))

Bien que je n'aie jamais utilisé ce cas d'utilisation (encore une fois, j'ai tendance à utiliser imbriqué dict, pour des raisons de cohérence), le code suivant fonctionne:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}
Doug R.
la source
1
Bunch est obsolète, mais il y a une fourchette active: github.com/Infinidat/munch
Rotareti
@Rotareti - Merci pour l'avertissement! Ce n'est pas une fonctionnalité que j'utilise, donc je ne le savais pas.
Doug R.
Et si vous voulez aller plus loin? (en référence au type (...))
Ole Aldric
6
Le python est comme un parapluie inversé tenu haut sous de fortes pluies. Tout semble intelligent et génial pour commencer, après un certain temps, il commence à devenir lourd, puis soudainement, vous lisez des trucs de gourou intégrés sur SE et le tout revient avec toute la charge utile sur vos épaules. Pendant que vous êtes encore trempé, vous vous sentez plus léger et tout est si clair et rafraîchi.
Ole Aldric
19

Vous pouvez extraire une classe de conteneur pratique de la bibliothèque standard:

from argparse import Namespace

pour éviter d'avoir à copier autour des bits de code. Pas d'accès au dictionnaire standard, mais facile à récupérer si vous le voulez vraiment. Le code dans argparse est simple,

class Namespace(_AttributeHolder):
    """Simple object for storing attributes.

    Implements equality by attribute names and values, and provides a simple
    string representation.
    """

    def __init__(self, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    __hash__ = None

    def __eq__(self, other):
        return vars(self) == vars(other)

    def __ne__(self, other):
        return not (self == other)

    def __contains__(self, key):
        return key in self.__dict__
lindyblackburn
la source
2
PLUS 1 pour référencer une bibliothèque standard, qui répond au premier commentaire de l'OP.
Gordon Bean
4
Python inclut une classe plus rapide (implémentée en C) pour ce cas: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André
18

Et si vous vouliez une clé qui était une méthode, comme __eq__ou __getattr__?

Et vous ne pourriez pas avoir une entrée qui ne commence pas par une lettre, donc l'utilisation 0343853comme clé est supprimée.

Et si vous ne vouliez pas utiliser de chaîne?

Le canard communiste
la source
En effet, ou par exemple d'autres objets comme clés. Cependant, je classerais l'erreur en tant que «comportement attendu» - avec ma question, je visais davantage l'inattendu.
Izz ad-Din Ruhulessin
pickle.dumputilisations__getstate__
Cees Timmerman
12

les tuples peuvent être utilisés des touches dict. Comment accéderiez-vous au tuple dans votre construction?

De plus, namedtuple est une structure pratique qui peut fournir des valeurs via l'accès aux attributs.

Senthil Kumaran
la source
7
L'inconvénient des couples nommés est qu'ils sont immuables.
Izz ad-Din Ruhulessin
10
Certains diraient qu'être immuable n'est pas un bug mais une caractéristique des tuples.
ben auteur
9

Que diriez-vous de Prodict , la petite classe Python que j'ai écrite pour les gouverner tous :)

De plus, vous obtenez l'achèvement automatique du code , les instanciations d'objets récursifs et la conversion de type automatique !

Vous pouvez faire exactement ce que vous avez demandé:

p = Prodict()
p.foo = 1
p.bar = "baz"

Exemple 1: indication de type

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

code automatique terminé

Exemple 2: conversion de type automatique

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>
Ramazan Polat
la source
2
s'installe sur python2 via pip, mais ne fonctionne pas sur python2
Ant6n
2
@ Ant6n nécessite python 3.6+ en raison des annotations de type
Ramazan Polat
8

Cela ne fonctionne pas en général. Toutes les clés de dict valides ne font pas des attributs adressables ("la clé"). Vous devrez donc être prudent.

Les objets Python sont tous essentiellement des dictionnaires. Je doute donc qu'il y ait beaucoup de performances ou d'autres pénalités.

tallseth
la source
8

Cela ne répond pas à la question d'origine, mais devrait être utile pour les personnes qui, comme moi, se retrouvent ici lors de la recherche d'une bibliothèque qui fournit cette fonctionnalité.

Addict, c'est une grande bibliothèque pour cela: https://github.com/mewwts/addict, il prend en charge de nombreuses préoccupations mentionnées dans les réponses précédentes.

Un exemple de la documentation:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}

Avec addict:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'
gonz
la source
8

Je me suis retrouvé à me demander quel était l'état actuel des "clés dict comme attr" dans l'écosystème python. Comme l'ont souligné plusieurs commentateurs, ce n'est probablement pas quelque chose que vous voulez rouler à partir de zéro , car il y a plusieurs pièges et pistolets, dont certains sont très subtils. De plus, je ne recommanderais pas d'utiliser Namespacecomme classe de base, j'ai été dans cette voie, ce n'est pas joli.

Heureusement, il existe plusieurs packages open source fournissant cette fonctionnalité, prêts à être installés! Malheureusement, il existe plusieurs packages. Voici un synopsis, en décembre 2019.

Contenders (le plus récent commit to master | #commits | #contribs | coverage%):

Plus entretenu ou sous-entretenu:

  • treedict (2014-03-28 | 95 | 2 |?%)
  • groupe (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Je recommande actuellement munch ou addict . Ils ont le plus de validations, de contributeurs et de versions, suggérant une base de code open source saine pour chacun. Ils ont le fichier readme.md le plus net, une couverture à 100% et un ensemble de tests de bonne qualité.

Je n'ai pas de chien dans cette course (pour l'instant!), En plus d'avoir roulé mon propre code dict / attr et perdu une tonne de temps parce que je n'étais pas au courant de toutes ces options :). Je peux contribuer à Addict / Munch à l'avenir car je préférerais voir un paquet solide plutôt qu'un tas de paquets fragmentés. Si vous les aimez, contribuez! En particulier, on dirait que munch pourrait utiliser un badge codecov et addict pourrait utiliser un badge de version python.

pros de la toxicomanie:

  • initialisation récursive (foo.abc = 'bar'), les arguments de type dict deviennent addict.Dict

toxicomane contre:

  • ombres typing.Dictsi vousfrom addict import Dict
  • Pas de vérification des clés. En raison de l'autorisation d'initiation récursive, si vous mal orthographiez une clé, vous créez simplement un nouvel attribut, plutôt que KeyError (merci AljoSt)

munch pros:

  • dénomination unique
  • fonctions ser / de intégrées pour JSON et YAML

grignoter les inconvénients:

  • pas d'init récursive / ne peut initier qu'une attr à la fois

Où j'éditorialise

Il y a plusieurs lunes, lorsque j'utilisais des éditeurs de texte pour écrire du python, sur des projets avec seulement moi ou un autre développeur, j'aimais le style de dict-attrs, la possibilité d'insérer des clés en déclarant simplement foo.bar.spam = eggs. Maintenant, je travaille en équipe et j'utilise un IDE pour tout, et je me suis éloigné de ce type de structures de données et de typage dynamique en général, en faveur de l'analyse statique, des techniques fonctionnelles et des indices de type. J'ai commencé à expérimenter cette technique, en sous-classant Pstruct avec des objets de ma propre conception:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Cela vous donne un objet qui se comporte toujours comme un dict, mais vous permet également d'accéder à des clés comme des attributs, d'une manière beaucoup plus rigide. L'avantage ici est que je (ou les malheureux consommateurs de votre code) sais exactement quels champs peuvent et ne peuvent pas exister, et l'EDI peut compléter automatiquement les champs. Sous-classer également vanilla dictsignifie que la sérialisation json est facile. Je pense que la prochaine évolution de cette idée serait un générateur de protobuf personnalisé qui émet ces interfaces, et une bonne option est que vous obtenez des structures de données multilingues et IPC via gRPC presque gratuitement.

Si vous décidez d'aller avec des attr-dicts, il est essentiel de documenter quels champs sont attendus, pour votre propre santé mentale (et celle de vos coéquipiers).

N'hésitez pas à éditer / mettre à jour ce post pour le garder récent!

DeusXMachina
la source
2
un gros inconvénient est qu'il ne lèvera addictpas d'exceptions lorsque vous orthographiez mal un attribut, car il en retournera un nouveau Dict(cela est nécessaire pour que foo.abc = 'bar' fonctionne).
AljoSt
5

Voici un bref exemple d'enregistrements immuables utilisant la fonction intégrée collections.namedtuple:

def record(name, d):
    return namedtuple(name, d.keys())(**d)

et un exemple d'utilisation:

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)
Ben Usman
la source
5

Juste pour ajouter de la variété à la réponse, sci-kit learn a implémenté ceci comme Bunch:

class Bunch(dict):                                                              
    """ Scikit Learn's container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 6                                                                 
    >>> b['c']                                                                  
    6                                                                           
    """                                                                         

    def __init__(self, **kwargs):                                               
        super(Bunch, self).__init__(kwargs)                                     

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

    def __dir__(self):                                                          
        return self.keys()                                                      

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

    def __setstate__(self, state):                                              
        pass                       

Tout ce dont vous avez besoin est d'obtenir les méthodes setattret getattr- les getattrvérifications des clés dict et le passage à la vérification des attributs réels. Il setstaets'agit d'un correctif pour le correctif de décapage / décapage des "grappes" - en cas de désintérêt, consultez https://github.com/scikit-learn/scikit-learn/issues/6196

user2804865
la source
3

Pas besoin d'écrire les vôtres car setattr () et getattr () existent déjà.

L'avantage des objets de classe entre probablement en jeu dans la définition et l'héritage des classes.

David W
la source
3

J'ai créé cela en fonction de l'entrée de ce fil. Cependant, je dois utiliser odict, j'ai donc dû remplacer get et définir attr. Je pense que cela devrait fonctionner pour la majorité des utilisations spéciales.

L'utilisation ressemble à ceci:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])

# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])

# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

La classe:

class OrderedAttrDict(odict.OrderedDict):
    """
    Constructs an odict.OrderedDict with attribute access to data.

    Setting a NEW attribute only creates it on the instance, not the dict.
    Setting an attribute that is a key in the data will set the dict data but 
    will not create a new instance attribute
    """
    def __getattr__(self, attr):
        """
        Try to get the data. If attr is not a key, fall-back and get the attr
        """
        if self.has_key(attr):
            return super(OrderedAttrDict, self).__getitem__(attr)
        else:
            return super(OrderedAttrDict, self).__getattr__(attr)


    def __setattr__(self, attr, value):
        """
        Try to set the data. If attr is not a key, fall-back and set the attr
        """
        if self.has_key(attr):
            super(OrderedAttrDict, self).__setitem__(attr, value)
        else:
            super(OrderedAttrDict, self).__setattr__(attr, value)

C'est un modèle assez cool déjà mentionné dans le fil, mais si vous voulez simplement prendre un dict et le convertir en un objet qui fonctionne avec la saisie semi-automatique dans un IDE, etc.:

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d
Rafe
la source
3

Apparemment, il existe maintenant une bibliothèque pour cela - https://pypi.python.org/pypi/attrdict - qui implémente cette fonctionnalité exacte ainsi que la fusion récursive et le chargement json. Ça vaut peut-être le coup d'oeil.

Yurik
la source
3

C'est ce que j'utilise

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)
mujjiga
la source
C'est une bonne réponse rapide et sale. Ma seule observation / commentaire est que je pense que le constructeur nommé tuple acceptera une liste de chaînes, donc votre solution peut être simplifiée (je pense) pour:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen
2

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!'
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
1
Notez qu'il peut masquer des dictméthodes, par exemple: m=Map(); m["keys"] = 42; m.keys()give TypeError: 'int' object is not callable.
bfontaine
@bfontaine L'idée est d'être une sorte de field/attributeet non un method, mais si vous attribuez une méthode à la place un nombre, vous pouvez accéder à cette méthode avec m.method().
epool
2

Permettez-moi de publier une autre implémentation, qui s'appuie sur la réponse de Kinvais, mais intègre des idées de AttributeDict proposées dans http://databio.org/posts/python_AttributeDict.html .

L'avantage de cette version est qu'elle fonctionne également pour les dictionnaires imbriqués:

class AttrDict(dict):
    """
    A class to convert a nested Dictionary into an object with key-values
    that are accessible using attribute notation (AttrDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    # Inspired by:
    # http://stackoverflow.com/a/14620633/1551810
    # http://databio.org/posts/python_AttributeDict.html

    def __init__(self, iterable, **kwargs):
        super(AttrDict, self).__init__(iterable, **kwargs)
        for key, value in iterable.items():
            if isinstance(value, dict):
                self.__dict__[key] = AttrDict(value)
            else:
                self.__dict__[key] = value
kadee
la source
1
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __name__ == '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.sun
h_vm
la source
1

La solution est:

DICT_RESERVED_KEYS = vars(dict).keys()


class SmartDict(dict):
    """
    A Dict which is accessible via attribute dot notation
    """
    def __init__(self, *args, **kwargs):
        """
        :param args: multiple dicts ({}, {}, ..)
        :param kwargs: arbitrary keys='value'

        If ``keyerror=False`` is passed then not found attributes will
        always return None.
        """
        super(SmartDict, self).__init__()
        self['__keyerror'] = kwargs.pop('keyerror', True)
        [self.update(arg) for arg in args if isinstance(arg, dict)]
        self.update(kwargs)

    def __getattr__(self, attr):
        if attr not in DICT_RESERVED_KEYS:
            if self['__keyerror']:
                return self[attr]
            else:
                return self.get(attr)
        return getattr(self, attr)

    def __setattr__(self, key, value):
        if key in DICT_RESERVED_KEYS:
            raise AttributeError("You cannot set a reserved name as attribute")
        self.__setitem__(key, value)

    def __copy__(self):
        return self.__class__(self)

    def copy(self):
        return self.__copy__()
Bruno Rocha - rochacbruno
la source
1

Quelles seraient les mises en garde et les pièges de l'accès aux clés de dict de cette manière?

Comme @Henry le suggère, une des raisons pour lesquelles l'accès en pointillé ne peut pas être utilisé dans les dict est qu'il limite les noms de clés dict aux variables valides pour python, limitant ainsi tous les noms possibles.

Les exemples suivants expliquent pourquoi l'accès en pointillé ne serait pas utile en général, étant donné un dict d:

Validité

Les attributs suivants ne seraient pas valides en Python:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation 
d.3 * x                           # expressions  

Style

Les conventions PEP8 imposeraient une contrainte douce sur la dénomination des attributs:

A. Noms de mots-clés réservés (ou fonction intégrée):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

Si le nom d'un argument de fonction entre en conflit avec un mot clé réservé, il est généralement préférable d'ajouter un seul trait de soulignement de fin ...

B. La règle de l'affaire méthodes et les noms de variables :

Les noms de variables suivent la même convention que les noms de fonction.

d.Firstname
d.Country

Utilisez les règles de dénomination des fonctions: en minuscules avec des mots séparés par des traits de soulignement si nécessaire pour améliorer la lisibilité.


Parfois, ces préoccupations sont soulevées dans des bibliothèques comme pandas , ce qui permet d'accéder en pointillé aux colonnes DataFrame par leur nom. Le mécanisme par défaut pour résoudre les restrictions de dénomination est également la notation matricielle - une chaîne entre crochets.

Si ces contraintes ne s'appliquent pas à votre cas d'utilisation, il existe plusieurs options sur les structures de données à accès pointillé .

pylang
la source
1

Vous pouvez utiliser dict_to_obj https://pypi.org/project/dict-to-obj/ Il fait exactement ce que vous avez demandé

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True
Alon Barad
la source
1
C'est une bonne forme à mettre .ideaet tout fichier spécifique à l'utilisateur ou généré par IDE dans votre fichier .gitignore.
DeusXMachina
1

Ce n'est pas une «bonne» réponse, mais je pensais que c'était astucieux (il ne gère pas les dictés imbriqués sous leur forme actuelle). Enveloppez simplement votre dict dans une fonction:

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

Vous avez maintenant une syntaxe légèrement différente. Pour accéder aux éléments dict comme le font les attributs f.key. Pour accéder aux éléments dict (et aux autres méthodes dict) de la manière habituelle, faites f()['key']et nous pouvons facilement mettre à jour le dict en appelant f avec des arguments de mots clés et / ou un dictionnaire

Exemple

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
... 
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

Et voilà. Je serai heureux si quelqu'un suggère des avantages et des inconvénients de cette méthode.

DylanYoung
la source
0

Comme l'a noté Doug, il existe un package Bunch que vous pouvez utiliser pour obtenir la obj.keyfonctionnalité. En fait, il existe une nouvelle version appelée

NeoBunch

Il a cependant une excellente fonctionnalité pour convertir votre dict en objet NeoBunch via sa fonction neobunchify . J'utilise beaucoup de modèles Mako et transmettre des données en tant qu'objets NeoBunch les rend beaucoup plus lisibles, donc si vous finissez par utiliser un dict normal dans votre programme Python mais que vous voulez la notation par points dans un modèle Mako, vous pouvez l'utiliser de cette façon:

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

Et le modèle Mako pourrait ressembler à:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor
mizu
la source
Le lien vers NeoBunch est 404
DeusXMachina
0

La façon la plus simple est de définir une classe appelons-la namespace. qui utilise l'objet dict .update () sur le dict. Ensuite, le dict sera traité comme un objet.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Elmahy
la source