Accedere alle chiavi di dict come un attributo?


303

Trovo più conveniente accedere alle chiavi di dict come obj.fooinvece di obj['foo'], quindi ho scritto questo frammento:

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

Tuttavia, suppongo che ci debba essere qualche motivo per cui Python non fornisce questa funzionalità immediatamente. Quali sarebbero le avvertenze e le insidie ​​dell'accesso alle chiavi del dict in questo modo?


16
Se accedi a chiavi codificate da un set limitato di dimensioni fisse ovunque, potresti essere meglio creare oggetti che li contengono. collections.namedtupleè molto utile per questo.

6
stackoverflow.com/questions/3031219/… ha una soluzione simile ma fa un passo ulteriore
keflavich

1
Trovate un modulo per questo su github.com/bcj/AttrDict . Non so come si confronta con le soluzioni qui e nelle domande correlate.
matt wilkie,

Ho anche usato hack simili, ora lo usoeasydict.EasyDict
muon

Altri modi per accedere ai membri del dizionario con un '.' : stackoverflow.com/questions/2352181/…
Pale Blue Dot

Risposte:


304

Il modo migliore per farlo è:

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

Alcuni professionisti:

  • Funziona davvero!
  • Nessun metodo di classe del dizionario è ombreggiato (ad esempio, .keys()funziona bene. A meno che, ovviamente, non gli assegni un valore, vedi sotto)
  • Gli attributi e gli elementi sono sempre sincronizzati
  • Il tentativo di accedere alla chiave inesistente come un attributo genera correttamente AttributeErroranzichéKeyError

Contro:

  • Metodi come .keys()saranno non funzionano bene se ottengono sovrascritti dai dati in arrivo
  • Provoca una perdita di memoria in Python <2.7.4 / Python3 <3.2.3
  • Pylint va sulle banane con E1123(unexpected-keyword-arg)eE1103(maybe-no-member)
  • Per i non iniziati sembra pura magia.

Una breve spiegazione su come funziona

  • Tutti gli oggetti Python memorizzano internamente i loro attributi in un dizionario chiamato __dict__.
  • Non è necessario che il dizionario interno __dict__debba essere "solo un semplice dettato", quindi possiamo assegnare qualsiasi sottoclasse dict()al dizionario interno.
  • Nel nostro caso semplicemente assegniamo l' AttrDict()istanza che stiamo istanziando (così come siamo __init__).
  • Chiamando super()il __init__()metodo abbiamo fatto in modo che (già) si comportasse esattamente come un dizionario, poiché quella funzione chiama tutto il codice di istanza del dizionario .

Uno dei motivi per cui Python non fornisce questa funzionalità pronta all'uso

Come notato nell'elenco "contro", questo combina lo spazio dei nomi delle chiavi memorizzate (che possono provenire da dati arbitrari e / o non attendibili!) Con lo spazio dei nomi degli attributi del metodo dict incorporato. Per esempio:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"

1
Pensi che la perdita del memorry si verificherebbe con un oggetto semplice come: >>> class MyD (object): ... def init __ (self, d): ... self .__ dict = d
Rafe

Provoca la perdita anche in 2.7
pi.

1
Rendi <= 2.7.3, poiché è quello che sto usando.
pi.

1
Nelle note di rilascio 2.7.4 lo menzionano risolto (non prima).
Robert Siemer,

1
@viveksinghggits solo perché stai accedendo alle cose tramite il ., non puoi infrangere le regole del linguaggio :) E non vorrei AttrDictconvertire automagicamente i campi contenenti spazio in qualcosa di diverso.
Yurik,

125

È possibile avere tutti i caratteri stringa legali come parte della chiave se si utilizza la notazione di array. Per esempio,obj['!#$%^&*()_']


1
@Izkata sì. cosa divertente di SE che di solito c'è una "domanda principale". titolo e una "domanda di fondo", forse perché a SE non piace ascoltare "il titolo dice tutto"; le "avvertenze" sono qui in basso.
n611x007

2
JavaScript non è un esempio particolarmente valido del linguaggio di programmazione, ma gli oggetti in JS supportano sia l'accesso agli attributi sia la notazione dell'array, il che consente la convenienza per il caso comune e un fallback generico per simboli che non sono nomi di attributi legali.
André Caron,

@Izkata Come risponde alla domanda. Questa risposta dice solo che le chiavi possono avere qualsiasi nome.
Melab,

4
@Melab La domanda è What would be the caveats and pitfalls of accessing dict keys in this manner?(come attributi) e la risposta è che la maggior parte dei caratteri mostrati qui non sarebbe utilizzabile.
Izkata,

83

Da quest'altra domanda SO c'è un ottimo esempio di implementazione che semplifica il tuo codice esistente. Che ne dite di:

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

Molto più conciso e non lascia spazio a extra cruft nel tuo __getattr__e __setattr__funzioni in futuro.


Saresti in grado di chiamare AttributeDict.update o AttributeDict.get usando questo metodo?
Dor,

13
Devi tenere presente che se aggiungi nuovi attributi in fase di esecuzione non vengono aggiunti al dict stesso ma all'attributo dict . Es d = AttributeDict(foo=1). d.bar = 1l'attributo bar è memorizzato all'interno dell'attributo dict ma non nel dict stesso. la stampa dmostra solo l'elemento foo.
P3trus,

7
+1 perché funziona perfettamente per quanto posso dire. @GringoSuave, @Izkata, @ P3trus Chiedo a chiunque sostenga che fallisce mostra un esempio che non funziona d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Funziona per me!
Dave Abrahams,

4
@DaveAbrahams Leggi la domanda completa e guarda le risposte di Hery, Ryan e TheCommunistDuck. Non si chiede come fare, ma per i problemi che possono sorgere .
Izkata,

6
Dovresti fornire un __getattr__metodo che genera un AttributeErrorse l'attributo dato non esiste, altrimenti cose come getattr(obj, attr, default_value)non funzionano (cioè non ritorna default_valuese attrnon esiste obj)
jcdude

83

In cui rispondo alla domanda che è stata posta

Perché Python non lo offre immediatamente?

Sospetto che abbia a che fare con lo Zen di Python : "Dovrebbe esserci un modo - e preferibilmente solo uno - ovvio per farlo." Ciò creerebbe due modi ovvi per accedere ai valori dai dizionari: obj['key']eobj.key .

Avvertenze e insidie

Questi includono la possibile mancanza di chiarezza e confusione nel codice. vale a dire, ciò che segue potrebbe essere fonte di confusione per qualcun altro che sta per mantenere il codice in un secondo momento, o anche per te, se non ci tornerai per un po '. Ancora una volta, dallo Zen : "La leggibilità conta!"

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

Se dè istanziato o KEY è definito o d[KEY] è assegnato lontano da doved.spam viene utilizzato, può facilmente creare confusione su ciò che viene fatto, poiché questo non è un linguaggio comunemente usato. So che avrebbe il potenziale per confondermi.

Inoltre, se si modifica il valore di KEYcome segue (ma manca la modifica d.spam), ora si ottiene:

>>> 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'

IMO, non vale la pena.

Altri oggetti

Come altri hanno già notato, puoi usare qualsiasi oggetto hash (non solo una stringa) come chiave dict. Per esempio,

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

è legale, ma

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

non è. Ciò consente di accedere all'intera gamma di caratteri stampabili o altri oggetti hash per le chiavi del dizionario, che non si hanno quando si accede a un attributo oggetto. Ciò rende possibile una magia come una metaclasse di oggetti memorizzati nella cache, come la ricetta del ricettario di Python (cap. 9) .

In cui editoriale

Preferisco l'estetica di spam.eggsover spam['eggs'](penso che appaia più pulita), e ho davvero iniziato a desiderare questa funzionalità quando ho incontrato il namedtuple. Ma la comodità di essere in grado di fare ciò che segue vince.

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

Questo è un semplice esempio, ma mi trovo spesso ad usare dicts in situazioni diverse rispetto a quelle usate per la obj.keynotazione (ad esempio, quando ho bisogno di leggere le preferenze da un file XML). In altri casi, dove sono tentato di creare un'istanza di una classe dinamica e di schiaffeggiarne alcuni per motivi estetici, continuo a usare un dict per coerenza al fine di migliorare la leggibilità.

Sono sicuro che l'OP ha da tempo risolto questo in modo soddisfacente, ma se vuole ancora questa funzionalità, quindi suggerisco di scaricare uno dei pacchetti da pypi che lo fornisce:

  • Il mazzo è quello con cui ho più familiarità. Sottoclasse didict, quindi hai tutte quelle funzionalità.
  • Anche AttrDict sembra essere abbastanza buono, ma non ho familiarità con esso e non ho guardato attraverso la fonte con tutti i dettagli che ho Bunch .
  • Il tossicodipendente viene attivamente mantenuto e offre un accesso simile e altro ancora.
  • Come notato nei commenti di Rotareti, Bunch è stato deprecato, ma esiste un fork attivo chiamato Munch .

Tuttavia, al fine di migliorare la leggibilità del suo codice, consiglio vivamente di non mescolare i suoi stili di notazione. Se preferisce questa notazione, dovrebbe semplicemente creare un'istanza di un oggetto dinamico, aggiungere gli attributi desiderati e chiamarlo un giorno:

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


In cui aggiorno, per rispondere a una domanda di follow-up nei commenti

Nei commenti (sotto), Elmo chiede:

E se vuoi approfondire? (riferito al tipo (...))

Anche se non ho mai usato questo caso d'uso (di nuovo, tendo a usare nidificato dict, per coerenza), il seguente codice funziona:

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

1
Il mazzo è deprecato, ma è attivo un fork: github.com/Infinidat/munch
Rotareti,

@Rotareti - Grazie per l'heads-up! Questa non è la funzionalità che uso, quindi non ne ero a conoscenza.
Doug R.,

E se vuoi approfondire? (riferendosi al tipo (...))
Ole Aldric

6
Python è come un ombrello rovesciato tenuto alto sotto la pioggia. Tutto sembra intelligente e funky per cominciare, dopo qualche tempo inizia a diventare pesante, poi improvvisamente, leggi alcune cose di guru integrate su SE e l'intera cosa ritorna indietro con l'intero carico utile lungo le spalle. Mentre sei ancora inzuppato ti senti più leggero e tutto è così chiaro e rinfrescato.
Ole Aldric,


19

È possibile estrarre una comoda classe contenitore dalla libreria standard:

from argparse import Namespace

per evitare di dover copiare attorno ai bit di codice. Nessun accesso al dizionario standard, ma è facile recuperarne uno se lo desideri davvero. Il codice in argparse è semplice,

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__

2
PLUS 1 per fare riferimento a una libreria standard, che indirizza il primo commento dell'OP.
Gordon Bean,

4
Python include una classe più veloce (implementata in C) per quel caso: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André

18

E se volessi una chiave che fosse un metodo, come __eq__o __getattr__?

E non saresti in grado di avere una voce che non è iniziata con una lettera, quindi l'utilizzo 0343853come chiave è fuori.

E se non volessi usare una stringa?


Anzi, o per esempio altri oggetti come chiavi. Tuttavia classificherei l'errore da quello come "comportamento previsto" - con la mia domanda miravo più all'imprevisto.
Izz ad-Din Ruhulessin,

pickle.dumputilizza__getstate__
Cees Timmerman il

12

le tuple possono essere usate chiavi dict. Come accederesti alla tupla nel tuo costrutto?

Inoltre, namedtuple è una struttura conveniente che può fornire valori tramite l'accesso all'attributo.


7
Lo svantaggio delle nametuples è che sono immutabili.
Izz ad-Din Ruhulessin,

10
Alcuni direbbero che essere immutabili non è un bug ma una caratteristica delle tuple.
ben autore

9

Che ne dici di Prodict , la piccola classe Python che ho scritto per dominarli tutti :)

Inoltre, ottieni il completamento automatico del codice , le istanze ricorsive degli oggetti e la conversione automatica del tipo !

Puoi fare esattamente quello che hai chiesto:

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

Esempio 1: digitare un suggerimento

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

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

codice automatico completato

Esempio 2: conversione automatica del tipo

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'>

2
si installa su python2 tramite pip, ma non funziona su python2
Ant6n

2
@ Ant6n richiede python 3.6+ a causa delle annotazioni sui tipi
Ramazan Polat

8

Non funziona in generale. Non tutte le chiavi dict valide rendono gli attributi indirizzabili ("la chiave"). Quindi, dovrai stare attento.

Gli oggetti Python sono fondamentalmente dizionari. Quindi dubito che ci sia molta prestazione o altra penalità.


8

Questo non affronta la domanda originale, ma dovrebbe essere utile per le persone che, come me, finiscono qui quando cercano una libreria che fornisce questa funzionalità.

Addict è una grande lib per questo: https://github.com/mewwts/addict si prende cura di molte preoccupazioni menzionate nelle risposte precedenti.

Un esempio dai documenti:

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

Con tossicodipendente:

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

8

Mi sono ritrovato a chiedermi quale sia lo stato attuale di "dict keys as attr" nell'ecosistema Python. Come diversi commentatori hanno sottolineato, questo probabilmente non è qualcosa che si desidera eliminare da zero , poiché ci sono diverse insidie ​​e pistole, alcune delle quali molto sottili. Inoltre, non consiglierei l'uso Namespacecome classe di base, sono stato su quella strada, non è carino.

Fortunatamente, ci sono diversi pacchetti open source che forniscono questa funzionalità, pronti per l'installazione pip! Sfortunatamente, ci sono diversi pacchetti. Ecco una sinossi, a partire da dicembre 2019.

Contender (il più recente si impegna a padroneggiare | #commits | #contribs | copertura%):

Non più mantenuto o sotto-mantenuto:

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

Attualmente raccomando munch o tossicodipendente . Hanno il maggior numero di commit, contributori e rilasci, suggerendo una base di codice open source salutare per ciascuno. Hanno il readme.md più pulito, una copertura del 100% e una serie di test belli.

Non ho un cane in questa gara (per ora!), Oltre ad aver rotolato il mio codice dict / attr e ho perso un sacco di tempo perché non ero a conoscenza di tutte queste opzioni :). Potrei contribuire a tossicodipendente / sgranocchiare in futuro, poiché preferirei vedere un pacchetto solido piuttosto che un gruppo di frammenti. Se ti piacciono, contribuisci! In particolare, sembra che Munch possa usare un badge codecov e il tossicodipendente potrebbe usare un badge versione Python.

professionisti dipendenti:

  • inizializzazione ricorsiva (foo.abc = 'bar'), argomenti simili a dict diventano dipendenti

contro tossicodipendenti:

  • ombre typing.Dictse tufrom addict import Dict
  • Nessun controllo chiave. A causa del consentire l'init ricorsivo, se si scrive male una chiave, si crea semplicemente un nuovo attributo, anziché KeyError (grazie AljoSt)

professionisti munch:

  • denominazione unica
  • funzioni ser / de integrate per JSON e YAML

sgranocchiando:

  • nessun init ricorsivo / solo può avviare un attr alla volta

In cui editoriale

Molte lune fa, quando ho usato gli editor di testo per scrivere Python, su progetti con solo me stesso o un altro sviluppatore, mi piaceva lo stile di dict-attrs, la possibilità di inserire le chiavi semplicemente dichiarando foo.bar.spam = eggs. Ora lavoro in team e utilizzo un IDE per tutto, e mi sono allontanato da questo tipo di strutture dati e tipizzazione dinamica in generale, a favore dell'analisi statica, delle tecniche funzionali e dei suggerimenti sul tipo. Ho iniziato a sperimentare questa tecnica, sottoclassando Pstruct con oggetti di mio disegno:

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']

Questo ti dà un oggetto che si comporta ancora come un dict, ma ti consente anche di accedere a chiavi come attributi, in modo molto più rigido. Il vantaggio qui è che io (o gli sfortunati consumatori del tuo codice) so esattamente quali campi possono e non possono esistere e l'IDE può completare automaticamente i campi. Anche la sottoclasse della vaniglia dictsignifica che la serializzazione json è semplice. Penso che la prossima evoluzione di questa idea sarebbe un generatore di protobuf personalizzato che emette queste interfacce, e un bel vantaggio è che si ottengono strutture dati multilingue e IPC tramite gRPC quasi gratis.

Se decidi di andare con attr-dicts, è essenziale documentare quali campi sono previsti, per la tua sanità mentale (e per i tuoi compagni di squadra).

Sentiti libero di modificare / aggiornare questo post per tenerlo recente!


2
un grande svantaggio addictè che non genererà eccezioni quando si sbaglia l'ortografia di un attributo, poiché restituirà un nuovo Dict(questo è necessario per far funzionare foo.abc = 'bar').
AljoSt

5

Ecco un breve esempio di record immutabili utilizzando il built-in collections.namedtuple:

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

e un esempio di utilizzo:

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

print rec.loss(..)

5

Solo per aggiungere un po 'di varietà alla risposta, sci-kit learn ha implementato questo come 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                       

Tutto ciò di cui hai bisogno è ottenere i metodi setattre getattr: i getattrcontrolli per le chiavi dict e passa alla verifica degli attributi effettivi. Si setstaettratta di una correzione per la correzione di "grappoli" di decapaggio / disfacimento - se presenti, consultare https://github.com/scikit-learn/scikit-learn/issues/6196


3

Non c'è bisogno di scrivere il proprio come setattr () e getattr () già esistono.

Il vantaggio degli oggetti di classe probabilmente entra in gioco nella definizione di classe e nell'ereditarietà.


3

L'ho creato sulla base dell'input di questo thread. Devo usare odict però, quindi ho dovuto scavalcare get e impostare attr. Penso che questo dovrebbe funzionare per la maggior parte degli usi speciali.

L'utilizzo è simile al seguente:

# 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)

Questo è un modello piuttosto interessante già menzionato nel thread, ma se vuoi solo prendere un dict e convertirlo in un oggetto che funziona con il completamento automatico in un IDE, ecc:

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d


3

Questo è quello che uso

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)

Questa è una buona risposta rapida e sporca. La mia unica osservazione / commento è che penso che il costruttore di namedtuple accetterà un elenco di stringhe, quindi la tua soluzione può essere semplificata (penso) per:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen,

2

Puoi farlo usando questa classe che ho appena creato. Con questa classe puoi usare l' Mapoggetto come un altro dizionario (inclusa la serializzazione json) o con la notazione punto. Spero di aiutarti:

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]

Esempi di utilizzo:

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']

1
Si noti che può ombreggiare dictmetodi, ad esempio: m=Map(); m["keys"] = 42; m.keys()TypeError: 'int' object is not callable.
bfontaine,

@bfontaine L'idea è quella di essere un tipo di field/attributee non un method, ma se si assegna un metodo invece un numero è possibile accedere a quel metodo m.method().
epool,

2

Consentitemi di pubblicare un'altra implementazione, che si basa sulla risposta di Kinvais, ma integra le idee di AttributeDict proposte in http://databio.org/posts/python_AttributeDict.html .

Il vantaggio di questa versione è che funziona anche con dizionari nidificati:

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

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

1

La soluzione è:

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__()

1

Quali sarebbero le avvertenze e le insidie ​​dell'accesso alle chiavi del dict in questo modo?

Come suggerisce @Henry, uno dei motivi per cui l'accesso a punti non può essere utilizzato in dicts è che limita i nomi delle chiavi dict alle variabili valide per Python, limitando così tutti i nomi possibili.

I seguenti sono esempi sul perché l'accesso punteggiato non sarebbe utile in generale, dato un detto d:

Validità

I seguenti attributi non sarebbero validi in 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  

Stile

Le convenzioni PEP8 imporrebbero un vincolo soft alla denominazione degli attributi:

A. Nomi di parole chiave riservate (o funzioni integrate):

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

Se il nome di un argomento di funzione si scontra con una parola chiave riservata, in genere è meglio aggiungere un singolo trattino di sottolineatura finale ...

B. La regola del caso su metodi e nomi di variabili :

I nomi delle variabili seguono la stessa convenzione dei nomi delle funzioni.

d.Firstname
d.Country

Utilizzare le regole di denominazione delle funzioni: lettere minuscole con parole separate da caratteri di sottolineatura, se necessario per migliorare la leggibilità.


A volte queste preoccupazioni vengono sollevate in biblioteche come i panda , che consente l'accesso punteggiato alle colonne DataFrame per nome. Il meccanismo predefinito per risolvere le restrizioni di denominazione è anche la notazione di array, una stringa tra parentesi.

Se questi vincoli non si applicano al tuo caso d'uso, ci sono diverse opzioni sulle strutture di dati ad accesso punteggiato .



1

Questa non è una risposta "buona", ma ho pensato che fosse elegante (non gestisce i dadi annidati nella forma attuale). Avvolgi semplicemente il tuo dict in una funzione:

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

Ora hai una sintassi leggermente diversa. Accedere agli articoli dettati come fanno gli attributi f.key. Per accedere agli elementi di dict (e altri metodi di dict) nel modo consueto, f()['key']possiamo aggiornare comodamente il dict chiamando f con argomenti di parole chiave e / o un dizionario

Esempio

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

Ed eccolo qui. Sarò felice se qualcuno suggerirà vantaggi e svantaggi di questo metodo.


0

Come notato da Doug, c'è un pacchetto Bunch che puoi usare per ottenere la obj.keyfunzionalità. In realtà c'è una versione più recente chiamata

NeoBunch

Ha comunque un'ottima funzione per convertire il tuo dict in un oggetto NeoBunch attraverso la sua funzione di neobunchify . Uso molto i modelli Mako e il passaggio dei dati in quanto gli oggetti NeoBunch li rende molto più leggibili, quindi se ti capita di finire usando un normale dict nel tuo programma Python ma vuoi la notazione dei punti in un modello Mako puoi usarlo in questo modo:

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)))

E il modello Mako potrebbe apparire come:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor

Il collegamento a NeoBunch è 404
DeusXMachina,

0

Il modo più semplice è definire una classe chiamiamola Namespace. che utilizza l'oggetto dict .update () sul dict. Quindi, il dict verrà trattato come un oggetto.

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.