Question

J'ai une structure de données qui consiste essentiellement en un dictionnaire imbriqué. Disons que cela ressemble à ceci:

{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

Maintenant, maintenir et créer ceci est assez pénible; chaque fois que j'ai un nouvel état / comté / profession, je dois créer les dictionnaires de couche inférieure via des blocs try / catch odieux. De plus, je dois créer des itérateurs imbriqués ennuyants si je veux passer en revue toutes les valeurs.

Je pourrais aussi utiliser des n-uplets comme clés, comme par exemple:

{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

Cela rend les itérations sur les valeurs très simples et naturelles, mais il est plus difficile, du point de vue syntaxique, de faire des choses comme des agrégations et de regarder des sous-ensembles du dictionnaire (par exemple, si je veux juste aller état par état).

En gros, parfois, je veux considérer un dictionnaire imbriqué comme un dictionnaire simple, et parfois, je le considère même comme une hiérarchie complexe. Je pourrais envelopper tout cela dans une classe, mais il semblerait que quelqu'un l'ait déjà fait. Alternativement, il semble qu'il y ait des constructions syntaxiques très élégantes pour faire cela.

Comment pourrais-je mieux faire cela?

Addendum: Je suis au courant de setdefault () mais la syntaxe n'est pas vraiment claire. De même, chaque sous-dictionnaire que vous créez doit toujours avoir setdefault () défini manuellement.

Était-ce utile?

La solution

  

Quel est le meilleur moyen d'implémenter des dictionnaires imbriqués en Python?

Implémentez __ manquant __ dans une sous-classe dict pour définir et renvoyer une nouvelle instance.

Cette approche est disponible (et documentée) depuis Python 2.5. et (particulièrement précieux pour moi) cela imprime joliment comme un dict normal , au lieu de l’impression moche d’un disque par défaut autovivifié:

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)() # retain local pointer to value
        return value                     # faster to return than dict lookup

(Remarque: self [clé] se trouve à gauche de l'affectation, il n'y a donc pas de récursivité ici.)

et dites que vous avez des données:

data = {('new jersey', 'mercer county', 'plumbers'): 3,
        ('new jersey', 'mercer county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'programmers'): 81,
        ('new jersey', 'middlesex county', 'salesmen'): 62,
        ('new york', 'queens county', 'plumbers'): 9,
        ('new york', 'queens county', 'salesmen'): 36}

Voici notre code d'utilisation:

vividict = Vividict()
for (state, county, occupation), number in data.items():
    vividict[state][county][occupation] = number

Et maintenant:

>>> import pprint
>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

Critique

Une critique de ce type de conteneur est que si l'utilisateur mal orthographié une clé, notre code pourrait échouer en silence:

>>> vividict['new york']['queens counyt']
{}

Et maintenant, nous aurions un comté mal orthographié dans nos données:

>>> pprint.pprint(vividict, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36},
              'queens counyt': {}}}

Explication:

Nous fournissons simplement une autre instance imbriquée de notre classe Vividict chaque fois qu'une clé est accédée mais est manquante. (Le renvoi de l’affectation de valeur est utile car il nous évite d’appeler en plus le getter sur le dict, et nous ne pouvons malheureusement pas le renvoyer tel quel.)

Notez que ce sont les mêmes sémantiques que la réponse la plus votée, mais en deux fois moins de lignes de code - l'implémentation de nosklo:

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

Démonstration d'utilisation

Vous trouverez ci-dessous un exemple d'utilisation simple de ce dict pour créer une structure de dict imbriquée à la volée. Cela peut rapidement créer une arborescence hiérarchique aussi complète que vous le souhaitez.

import pprint

class Vividict(dict):
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

d = Vividict()

d['foo']['bar']
d['foo']['baz']
d['fizz']['buzz']
d['primary']['secondary']['tertiary']['quaternary']
pprint.pprint(d)

Quelles sorties:

{'fizz': {'buzz': {}},
 'foo': {'bar': {}, 'baz': {}},
 'primary': {'secondary': {'tertiary': {'quaternary': {}}}}}

Et comme le montre la dernière ligne, il s’imprime joliment et permet une inspection manuelle. Mais si vous souhaitez inspecter visuellement vos données, implémentez __ missing __ pour définir une nouvelle instance de sa classe sur la clé et la renvoyer, ce qui constitue une bien meilleure solution.

Autres alternatives, par contraste:

dict.setdefault

Même si le demandeur pense que ce n'est pas propre, je le trouve préférable au Vividict moi-même.

d = {} # or dict()
for (state, county, occupation), number in data.items():
    d.setdefault(state, {}).setdefault(county, {})[occupation] = number

et maintenant:

>>> pprint.pprint(d, width=40)
{'new jersey': {'mercer county': {'plumbers': 3,
                                  'programmers': 81},
                'middlesex county': {'programmers': 81,
                                     'salesmen': 62}},
 'new york': {'queens county': {'plumbers': 9,
                                'salesmen': 36}}}

Une faute d'orthographe échouerait bruyamment et ne surchargerait pas nos données avec de mauvaises informations:

>>> d['new york']['queens counyt']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'queens counyt'

De plus, je pense que setdefault fonctionne très bien lorsqu'il est utilisé dans des boucles et que vous ne savez pas ce que vous obtiendrez pour les clés, mais l'utilisation répétitive devient assez lourde et je ne pense pas que quiconque veuille continuer suivant:

d = dict()

d.setdefault('foo', {}).setdefault('bar', {})
d.setdefault('foo', {}).setdefault('baz', {})
d.setdefault('fizz', {}).setdefault('buzz', {})
d.setdefault('primary', {}).setdefault('secondary', {}).setdefault('tertiary', {}).setdefault('quaternary', {})

Une autre critique est que setdefault nécessite une nouvelle instance, qu’elle soit utilisée ou non. Cependant, Python (ou au moins CPython) est plutôt intelligent pour gérer les nouvelles instances non utilisées et non référencées. Par exemple, il réutilise l'emplacement en mémoire:

>>> id({}), id({}), id({})
(523575344, 523575344, 523575344)

Un défaut auto-vivifié par défaut

Il s'agit d'une implémentation soignée, et l'utilisation dans un script sur lequel vous n'inspectez pas les données serait aussi utile que l'implémentation de __ missing __ :

from collections import defaultdict

def vivdict():
    return defaultdict(vivdict)

Mais si vous avez besoin d'inspecter vos données, les résultats d'un defaultdict auto-vivifié rempli de données de la même manière se présentent comme suit:

>>> d = vivdict(); d['foo']['bar']; d['foo']['baz']; d['fizz']['buzz']; d['primary']['secondary']['tertiary']['quaternary']; import pprint; 
>>> pprint.pprint(d)
defaultdict(<function vivdict at 0x17B01870>, {'foo': defaultdict(<function vivdict 
at 0x17B01870>, {'baz': defaultdict(<function vivdict at 0x17B01870>, {}), 'bar': 
defaultdict(<function vivdict at 0x17B01870>, {})}), 'primary': defaultdict(<function 
vivdict at 0x17B01870>, {'secondary': defaultdict(<function vivdict at 0x17B01870>, 
{'tertiary': defaultdict(<function vivdict at 0x17B01870>, {'quaternary': defaultdict(
<function vivdict at 0x17B01870>, {})})})}), 'fizz': defaultdict(<function vivdict at 
0x17B01870>, {'buzz': defaultdict(<function vivdict at 0x17B01870>, {})})})

Cette sortie est assez inélégante et les résultats sont assez illisibles. La solution généralement donnée est de reconvertir récursivement en dict pour une inspection manuelle. Cette solution non triviale est laissée comme un exercice pour le lecteur.

Performances

Enfin, examinons les performances. Je soustrais les coûts de l'instanciation.

>>> import timeit
>>> min(timeit.repeat(lambda: {}.setdefault('foo', {}))) - min(timeit.repeat(lambda: {}))
0.13612580299377441
>>> min(timeit.repeat(lambda: vivdict()['foo'])) - min(timeit.repeat(lambda: vivdict()))
0.2936999797821045
>>> min(timeit.repeat(lambda: Vividict()['foo'])) - min(timeit.repeat(lambda: Vividict()))
0.5354437828063965
>>> min(timeit.repeat(lambda: AutoVivification()['foo'])) - min(timeit.repeat(lambda: AutoVivification()))
2.138362169265747

En fonction des performances, dict.setdefault fonctionne le mieux. Je le recommande vivement pour le code de production, dans les cas où vous vous souciez de la vitesse d'exécution.

Si vous en avez besoin pour une utilisation interactive (dans un ordinateur portable IPython, par exemple), les performances importent peu. Dans ce cas, j'utiliserais Vividict pour la lisibilité de la sortie. Par rapport à l'objet AutoVivification (qui utilise

Autres conseils

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

Test:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

Sortie:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}

Simplement parce que je n'en ai pas vu un aussi petit, voici un dict imbriqué comme vous le souhaitez, pas de panique:

# yo dawg, i heard you liked dicts                                                                      
def yodict():
    return defaultdict(yodict)

Vous pouvez créer un fichier YAML et le lire en utilisant PyYaml .

Étape 1: créez un fichier YAML, "employment.yml":

new jersey:
  mercer county:
    pumbers: 3
    programmers: 81
  middlesex county:
    salesmen: 62
    programmers: 81
new york:
  queens county:
    plumbers: 9
    salesmen: 36

Étape 2: lisez-le en Python

import yaml
file_handle = open("employment.yml")
my_shnazzy_dictionary = yaml.safe_load(file_handle)
file_handle.close()

et maintenant my_shnazzy_dictionary a toutes vos valeurs. Si vous avez besoin de le faire à la volée, vous pouvez créer le YAML en tant que chaîne et l'insérer dans yaml.safe_load (...) .

Comme vous avez une conception en étoile, vous pouvez la structurer davantage comme une table relationnelle et moins comme un dictionnaire.

import collections

class Jobs( object ):
    def __init__( self, state, county, title, count ):
        self.state= state
        self.count= county
        self.title= title
        self.count= count

facts = [
    Jobs( 'new jersey', 'mercer county', 'plumbers', 3 ),
    ...

def groupBy( facts, name ):
    total= collections.defaultdict( int )
    for f in facts:
        key= getattr( f, name )
        total[key] += f.count

Ce genre de choses peut grandement contribuer à créer une conception semblable à un entrepôt de données sans les frais généraux SQL.

Si le nombre de niveaux d'imbrication est faible, j'utilise collections.defaultdict pour cela:

from collections import defaultdict

def nested_dict_factory(): 
  return defaultdict(int)
def nested_dict_factory2(): 
  return defaultdict(nested_dict_factory)
db = defaultdict(nested_dict_factory2)

db['new jersey']['mercer county']['plumbers'] = 3
db['new jersey']['mercer county']['programmers'] = 81

L'utilisation de defaultdict comme ceci évite beaucoup de setdefault () , get () désordonné, etc.,

.

Il s'agit d'une fonction qui renvoie un dictionnaire imbriqué de profondeur arbitraire:

from collections import defaultdict
def make_dict():
    return defaultdict(make_dict)

Utilisez-le comme ceci:

d=defaultdict(make_dict)
d["food"]["meat"]="beef"
d["food"]["veggie"]="corn"
d["food"]["sweets"]="ice cream"
d["animal"]["pet"]["dog"]="collie"
d["animal"]["pet"]["cat"]="tabby"
d["animal"]["farm animal"]="chicken"

Parcourez tout avec quelque chose comme ceci:

def iter_all(d,depth=1):
    for k,v in d.iteritems():
        print "-"*depth,k
        if type(v) is defaultdict:
            iter_all(v,depth+1)
        else:
            print "-"*(depth+1),v

iter_all(d)

Ceci affiche:

- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken

Vous pourriez éventuellement vouloir faire en sorte que de nouveaux éléments ne puissent pas être ajoutés au dict. Il est facile de convertir récursivement tous ces defaultdict en normaux dict s.

def dictify(d):
    for k,v in d.iteritems():
        if isinstance(v,defaultdict):
            d[k] = dictify(v)
    return dict(d)

Je trouve setdefault plutôt utile; Il vérifie si une clé est présente et l'ajoute sinon:

d = {}
d.setdefault('new jersey', {}).setdefault('mercer county', {})['plumbers'] = 3

setdefault renvoie toujours la clé appropriée. Vous mettez donc à jour les valeurs de ' d ' à la place.

En ce qui concerne l'itération, je suis sûr que vous pourriez écrire un générateur assez facilement s'il n'en existe pas déjà un en Python:

def iterateStates(d):
    # Let's count up the total number of "plumbers" / "dentists" / etc.
    # across all counties and states
    job_totals = {}

    # I guess this is the annoying nested stuff you were talking about?
    for (state, counties) in d.iteritems():
        for (county, jobs) in counties.iteritems():
            for (job, num) in jobs.iteritems():
                # If job isn't already in job_totals, default it to zero
                job_totals[job] = job_totals.get(job, 0) + num

    # Now return an iterator of (job, number) tuples
    return job_totals.iteritems()

# Display all jobs
for (job, num) in iterateStates(d):
    print "There are %d %s in total" % (job, num)

Comme d'autres l'ont suggéré, une base de données relationnelle pourrait vous être plus utile. Vous pouvez utiliser une base de données sqlite3 en mémoire en tant que structure de données pour créer des tables, puis les interroger.

import sqlite3

c = sqlite3.Connection(':memory:')
c.execute('CREATE TABLE jobs (state, county, title, count)')

c.executemany('insert into jobs values (?, ?, ?, ?)', [
    ('New Jersey', 'Mercer County',    'Programmers', 81),
    ('New Jersey', 'Mercer County',    'Plumbers',     3),
    ('New Jersey', 'Middlesex County', 'Programmers', 81),
    ('New Jersey', 'Middlesex County', 'Salesmen',    62),
    ('New York',   'Queens County',    'Salesmen',    36),
    ('New York',   'Queens County',    'Plumbers',     9),
])

# some example queries
print list(c.execute('SELECT * FROM jobs WHERE county = "Queens County"'))
print list(c.execute('SELECT SUM(count) FROM jobs WHERE title = "Programmers"'))

Ceci est juste un exemple simple. Vous pouvez définir des tableaux distincts pour les états, les comtés et les intitulés de poste.

collections.defaultdict peut être sous-classé pour créer un dict imbriqué. Ajoutez ensuite toutes les méthodes d’itération utiles à cette classe.

>>> from collections import defaultdict
>>> class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def walk(self):
        for key, value in self.iteritems():
            if isinstance(value, nesteddict):
                for tup in value.walk():
                    yield (key,) + tup
            else:
                yield key, value


>>> nd = nesteddict()
>>> nd['new jersey']['mercer county']['plumbers'] = 3
>>> nd['new jersey']['mercer county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['programmers'] = 81
>>> nd['new jersey']['middlesex county']['salesmen'] = 62
>>> nd['new york']['queens county']['plumbers'] = 9
>>> nd['new york']['queens county']['salesmen'] = 36
>>> for tup in nd.walk():
    print tup


('new jersey', 'mercer county', 'programmers', 81)
('new jersey', 'mercer county', 'plumbers', 3)
('new jersey', 'middlesex county', 'programmers', 81)
('new jersey', 'middlesex county', 'salesmen', 62)
('new york', 'queens county', 'salesmen', 36)
('new york', 'queens county', 'plumbers', 9)

defaultdict () est votre ami!

Pour un dictionnaire à deux dimensions, vous pouvez faire:

d = defaultdict(defaultdict)
d[1][2] = 3

Pour plus de dimensions, vous pouvez:

d = defaultdict(lambda :defaultdict(defaultdict))
d[1][2][3] = 4

En ce qui concerne "les blocs d'essai / capture odieux":

d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d

donne

{'key': {'inner key': {'inner inner key': 'value'}}}

Vous pouvez utiliser ceci pour convertir votre format de dictionnaire plat en format structuré:

fd = {('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'programmers'): 81,
 ('new jersey', 'middlesex county', 'salesmen'): 62,
 ('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

for (k1,k2,k3), v in fd.iteritems():
    d.setdefault(k1, {}).setdefault(k2, {})[k3] = v

Pour parcourir facilement votre dictionnaire imbriqué, pourquoi ne pas simplement écrire un simple générateur?

def each_job(my_dict):
    for state, a in my_dict.items():
        for county, b in a.items():
            for job, value in b.items():
                yield {
                    'state'  : state,
                    'county' : county,
                    'job'    : job,
                    'value'  : value
                }

Dans ce cas, si vous avez votre dictionnaire imbriqué compilé, itérer dessus devient simple:

for r in each_job(my_dict):
    print "There are %d %s in %s, %s" % (r['value'], r['job'], r['county'], r['state'])

Évidemment, votre générateur peut générer le format de données qui vous est utile.

Pourquoi utilisez-vous des blocs d’attrape pour lire l’arbre? Il est assez facile (et probablement plus sûr) de demander si une clé existe dans un dict avant d'essayer de la récupérer. Une fonction utilisant des clauses de garde pourrait ressembler à ceci:

if not my_dict.has_key('new jersey'):
    return False

nj_dict = my_dict['new jersey']
...

Ou, une méthode peut-être quelque peu verbeuse, consiste à utiliser la méthode get:

value = my_dict.get('new jersey', {}).get('middlesex county', {}).get('salesmen', 0)

Mais pour une manière un peu plus succincte, vous pouvez envisager d'utiliser un collections.defaultdict , qui fait partie de la bibliothèque standard depuis python 2.5.

import collections

def state_struct(): return collections.defaultdict(county_struct)
def county_struct(): return collections.defaultdict(job_struct)
def job_struct(): return 0

my_dict = collections.defaultdict(state_struct)

print my_dict['new jersey']['middlesex county']['salesmen']

Je fais ici des hypothèses sur la signification de votre structure de données, mais il devrait être facile de s’adapter à ce que vous voulez réellement faire.

Vous pouvez utiliser Addict: https://github.com/mewwts/addict

>>> from addict import Dict
>>> my_new_shiny_dict = Dict()
>>> my_new_shiny_dict.a.b.c.d.e = 2
>>> my_new_shiny_dict
{'a': {'b': {'c': {'d': {'e': 2}}}}}

J'aime l'idée d'intégrer cela dans une classe et d'implémenter __ getitem __ et __ setitem __ de sorte qu'ils implémentent un langage de requête simple:

>>> d['new jersey/mercer county/plumbers'] = 3
>>> d['new jersey/mercer county/programmers'] = 81
>>> d['new jersey/mercer county/programmers']
81
>>> d['new jersey/mercer country']
<view which implicitly adds 'new jersey/mercer county' to queries/mutations>

Si vous voulez avoir l’imagination, vous pouvez également implémenter quelque chose comme:

>>> d['*/*/programmers']
<view which would contain 'programmers' entries>

mais surtout je pense qu'une telle chose serait vraiment amusante à mettre en oeuvre: D

À moins que votre jeu de données ne reste assez petit, vous pouvez envisager d'utiliser une base de données relationnelle. Il fera exactement ce que vous voulez: faciliter l’ajout de décomptes, la sélection de sous-ensembles de dénombrements et même l’agrégation de décomptes par État, comté, profession ou toute combinaison de ceux-ci.

class JobDb(object):
    def __init__(self):
        self.data = []
        self.all = set()
        self.free = []
        self.index1 = {}
        self.index2 = {}
        self.index3 = {}

    def _indices(self,(key1,key2,key3)):
        indices = self.all.copy()
        wild = False
        for index,key in ((self.index1,key1),(self.index2,key2),
                                             (self.index3,key3)):
            if key is not None:
                indices &= index.setdefault(key,set())
            else:
                wild = True
        return indices, wild

    def __getitem__(self,key):
        indices, wild = self._indices(key)
        if wild:
            return dict(self.data[i] for i in indices)
        else:
            values = [self.data[i][-1] for i in indices]
            if values:
                return values[0]

    def __setitem__(self,key,value):
        indices, wild = self._indices(key)
        if indices:
            for i in indices:
                self.data[i] = key,value
        elif wild:
            raise KeyError(k)
        else:
            if self.free:
                index = self.free.pop(0)
                self.data[index] = key,value
            else:
                index = len(self.data)
                self.data.append((key,value))
                self.all.add(index)
            self.index1.setdefault(key[0],set()).add(index)
            self.index2.setdefault(key[1],set()).add(index)
            self.index3.setdefault(key[2],set()).add(index)

    def __delitem__(self,key):
        indices,wild = self._indices(key)
        if not indices:
            raise KeyError
        self.index1[key[0]] -= indices
        self.index2[key[1]] -= indices
        self.index3[key[2]] -= indices
        self.all -= indices
        for i in indices:
            self.data[i] = None
        self.free.extend(indices)

    def __len__(self):
        return len(self.all)

    def __iter__(self):
        for key,value in self.data:
            yield key

Exemple:

>>> db = JobDb()
>>> db['new jersey', 'mercer county', 'plumbers'] = 3
>>> db['new jersey', 'mercer county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'programmers'] = 81
>>> db['new jersey', 'middlesex county', 'salesmen'] = 62
>>> db['new york', 'queens county', 'plumbers'] = 9
>>> db['new york', 'queens county', 'salesmen'] = 36

>>> db['new york', None, None]
{('new york', 'queens county', 'plumbers'): 9,
 ('new york', 'queens county', 'salesmen'): 36}

>>> db[None, None, 'plumbers']
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new york', 'queens county', 'plumbers'): 9}

>>> db['new jersey', 'mercer county', None]
{('new jersey', 'mercer county', 'plumbers'): 3,
 ('new jersey', 'mercer county', 'programmers'): 81}

>>> db['new jersey', 'middlesex county', 'programmers']
81

>>>

Modifier: Les dictionnaires sont à présent renvoyés lorsque vous effectuez une requête avec des caractères génériques ( Aucun ). Sinon, utilisez des valeurs uniques.

Vous pouvez utiliser la récursivité dans lambdas et defaultdict, inutile de définir des noms:

a = defaultdict((lambda f: f(f))(lambda g: lambda:defaultdict(g(g))))

Voici un exemple:

>>> a['new jersey']['mercer county']['plumbers']=3
>>> a['new jersey']['middlesex county']['programmers']=81
>>> a['new jersey']['mercer county']['programmers']=81
>>> a['new jersey']['middlesex county']['salesmen']=62
>>> a
defaultdict(<function __main__.<lambda>>,
        {'new jersey': defaultdict(<function __main__.<lambda>>,
                     {'mercer county': defaultdict(<function __main__.<lambda>>,
                                  {'plumbers': 3, 'programmers': 81}),
                      'middlesex county': defaultdict(<function __main__.<lambda>>,
                                  {'programmers': 81, 'salesmen': 62})})})

J'avais l'habitude d'utiliser cette fonction. c'est sûr, rapide, facile à entretenir.

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

Exemple:

>>> from functools import reduce
>>> def deep_get(dictionary, keys, default=None):
...     return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
...
>>> person = {'person':{'name':{'first':'John'}}}
>>> print (deep_get(person, "person.name.first"))
John
>>> print (deep_get(person, "person.name.lastname"))
None
>>> print (deep_get(person, "person.name.lastname", default="No lastname"))
No lastname
>>>

J'ai un truc similaire. J'ai beaucoup de cas où je fais:

thedict = {}
for item in ('foo', 'bar', 'baz'):
  mydict = thedict.get(item, {})
  mydict = get_value_for(item)
  thedict[item] = mydict

Mais aller à plusieurs niveaux en profondeur. C’est le " get (item, {}) " c'est la clé car cela fera un autre dictionnaire s'il n'en existe pas déjà. En attendant, j'ai réfléchi aux moyens de faire face à c'est mieux. En ce moment, il y a beaucoup de

value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)

Alors, au lieu de cela, j'ai fait:

def dictgetter(thedict, default, *args):
  totalargs = len(args)
  for i,arg in enumerate(args):
    if i+1 == totalargs:
      thedict = thedict.get(arg, default)
    else:
      thedict = thedict.get(arg, {})
  return thedict

Qui a le même effet si vous le faites:

value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')

Mieux? Je pense que oui.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top