J'ai une structure de données qui équivaut essentiellement à 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 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).
Fondamentalement, parfois, je veux penser à un dictionnaire imbriqué comme à un dictionnaire plat, et parfois, à 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 faire cela mieux?
Addendum: Je connais setdefault()
mais cela ne permet pas une syntaxe claire. De plus, chaque sous-dictionnaire que vous créez doit toujours avoir défini manuellement setdefault()
.
Quel est le meilleur moyen d'implémenter des dictionnaires imbriqués en Python?
Implémentez __missing__
sur 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écieuse pour moi) elle s’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[key]
est à 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}}}
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 en plus 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': {}}}
Nous fournissons simplement une autre instance imbriquée de notre classe Vividict
chaque fois qu'une clé est accédée mais est manquante. (Renvoyer l'affectation de valeur est utile car cela 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 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
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 est une solution bien meilleure.
dict.setdefault
Bien que le demandeur pense que cela n’est pas propre, je le trouve préférable à 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 n'encombrerait pas nos données 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 un usage répétitif devient assez fastidieux et je ne pense pas que quiconque veuille suivre ce qui suit:
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)
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 devez inspecter vos données, les résultats d'un defaultdict auto-vivifié, rempli de données de la même manière, ressemblent à ceci:
>>> 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.
Enfin, regardons la performance. 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
Sur la base 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é du résultat. Par rapport à l'objet AutoVivification (qui utilise __getitem__
au lieu de __missing__
, créé à cet effet), il est de loin supérieur.
Implémenter __missing__
sur une dict
de sous-classe pour définir et renvoyer une nouvelle instance est légèrement plus difficile que les alternatives, mais présente les avantages suivants:
et comme il est moins compliqué et plus performant que de modifier __getitem__
, il convient de le préférer à cette méthode.
Néanmoins, il présente des inconvénients:
Ainsi, je préfère personnellement setdefault
aux autres solutions et, dans chaque situation, j’ai eu besoin de ce type de comportement.
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
Essai:
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}}}
Juste parce que je n'en ai pas vu d'aussi petit, voici un dict qui est aussi imbriqué que 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 avec PyYaml .
Étape 1: Créez un fichier YAML, "emploi.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 deviez le faire à la volée, vous pouvez créer le YAML sous forme de 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 chose peut faire beaucoup pour 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
Utiliser defaultdict
comme ceci évite beaucoup de setdefault()
, get()
, etc.
C'est une fonction qui retourne 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"
Itérez à travers tout avec quelque chose comme ça:
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)
Cela imprime:
- food
-- sweets
--- ice cream
-- meat
--- beef
-- veggie
--- corn
- animal
-- pet
--- dog
---- labrador
--- cat
---- tabby
-- farm animal
--- chicken
Vous voudrez peut-être éventuellement 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
s en normal 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
très 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.
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
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)
Pour ce qui est des "blocs d'essai/capture odieux":
d = {}
d.setdefault('key',{}).setdefault('inner key',{})['inner inner key'] = 'value'
print d
les rendements
{'key': {'inner key': {'inner inner key': 'value'}}}
Vous pouvez l’utiliser 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
}
Donc, 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'])
Il est évident que votre générateur peut générer le format de données qui vous est utile.
Pourquoi utilisez-vous des blocs d'arrêt 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 voudrez peut-être 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 des hypothèses sur la signification de votre structure de données ici, 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 de placer cela dans une classe et d'implémenter __getitem__
et __setitem__
de telle 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 aussi 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 œuvre: 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: il est facile d’ajouter des comptes, de sélectionner des sous-ensembles de comptes et même d’agréger des comptes par État, comté, profession ou une combinaison de ces éléments.
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
>>>
Édition: Renvoie maintenant les dictionnaires lors de requêtes avec des caractères génériques (None
), et des valeurs uniques autrement.
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, facilement maintenable.
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 une chose similaire va. 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, {})" qui est la clé car il fera un autre dictionnaire s'il n'y en a pas déjà un. En attendant, j'ai réfléchi à des façons de gérer .__ ceci mieux. En ce moment, il y a beaucoup de
value = mydict.get('foo', {}).get('bar', {}).get('baz', 0)
Alors à la place, 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
Ce qui a le même effet si vous faites:
value = dictgetter(mydict, 0, 'foo', 'bar', 'baz')
Meilleur? Je le pense.