Existe-t-il un moyen de définir une requête de type XPath pour les dictionnaires python imbriqués.
Quelque chose comme ça:
foo = {
'spam':'eggs',
'morefoo': {
'bar':'soap',
'morebar': {'bacon' : 'foobar'}
}
}
print( foo.select("/morefoo/morebar") )
>> {'bacon' : 'foobar'}
Je devais aussi sélectionner des listes imbriquées;)
Cela peut être fait facilement avec la solution de @ jellybean:
def xpath_get(mydict, path):
elem = mydict
try:
for x in path.strip("/").split("/"):
try:
x = int(x)
elem = elem[x]
except ValueError:
elem = elem.get(x)
except:
pass
return elem
foo = {
'spam':'eggs',
'morefoo': [{
'bar':'soap',
'morebar': {
'bacon' : {
'bla':'balbla'
}
}
},
'bla'
]
}
print xpath_get(foo, "/morefoo/0/morebar/bacon")
[EDIT 2016] Cette question et la réponse acceptée sont anciennes. Les nouvelles réponses peuvent faire le travail mieux que la réponse originale. Cependant, je ne les ai pas testées, je ne changerai donc pas la réponse acceptée.
Pas tout à fait beau, mais vous pourriez utiliser qch comme
def xpath_get(mydict, path):
elem = mydict
try:
for x in path.strip("/").split("/"):
elem = elem.get(x)
except:
pass
return elem
Cela ne supporte pas les choses xpath comme les index, bien sûr ... sans parler du /
key trap unutbu indiqué.
L’une des meilleures bibliothèques que j’ai pu identifier, qui, de plus, est très activement développée, est un projet extrait de boto: JMESPath . Il a une syntaxe très puissante pour faire des choses qui prennent normalement des pages de code à exprimer.
Voici quelques exemples:
search('foo | bar', {"foo": {"bar": "baz"}}) -> "baz"
search('foo[*].bar | [0]', {
"foo": [{"bar": ["first1", "second1"]},
{"bar": ["first2", "second2"]}]}) -> ["first1", "second1"]
search('foo | [0]', {"foo": [0, 1, 2]}) -> [0]
Il y a la nouvelle bibliothèque jsonpath-rw qui prend en charge la syntaxe JSONPATH mais pour python dictionnaires et arrays , comme vous le souhaitez.
Donc, votre 1er exemple devient:
from jsonpath_rw import parse
print( parse('$.morefoo.morebar').find(foo) )
Et le 2ème:
print( parse("$.morefoo[0].morebar.bacon").find(foo) )
PS: Une autre bibliothèque plus simple prenant également en charge les dictionnaires est python-json-pointer avec une syntaxe plus semblable à XPath.
Il existe un moyen plus facile de le faire maintenant.
http://github.com/akesterson/dpath-python
$ easy_install dpath
>>> dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar")
... terminé. Ou si vous n'aimez pas obtenir vos résultats dans une vue (dictionnaire fusionné qui conserve les chemins), renvoyez-les-en:
$ easy_install dpath
>>> for (path, value) in dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar", yielded=True)
... et fait. 'valeur' tiendra {{bacon ':' foobar '} dans ce cas.
Si vous préférez la maigreur:
def xpath(root, path, sch='/'):
return reduce(lambda acc, nxt: acc[nxt],
[int(x) if x.isdigit() else x for x in path.split(sch)],
root)
Bien sûr, si vous seulement avez des dict, alors c'est plus simple:
def xpath(root, path, sch='/'):
return reduce(lambda acc, nxt: acc[nxt],
path.split(sch),
root)
Bonne chance pour trouver des erreurs dans votre chemin spec tho ;-)
Il faudrait travailler davantage sur le fonctionnement du sélecteur semblable à XPath. '/'
est une clé de dictionnaire valide.
foo={'/':{'/':'eggs'},'//':'ham'}
être manipulé?
foo.select("///")
serait ambigu.
Une autre alternative (outre celle suggérée par jellybean ) est la suivante:
def querydict(d, q):
keys = q.split('/')
nd = d
for k in keys:
if k == '':
continue
if k in nd:
nd = nd[k]
else:
return None
return nd
foo = {
'spam':'eggs',
'morefoo': {
'bar':'soap',
'morebar': {'bacon' : 'foobar'}
}
}
print querydict(foo, "/morefoo/morebar")
Existe-t-il une raison pour que vous l'interrogiez comme le motif XPath? Comme l'a suggéré le commentateur de votre question, il ne s'agit que d'un dictionnaire, ce qui vous permet d'accéder aux éléments de manière imbriquée. De plus, étant donné que les données sont sous la forme de JSON, vous pouvez utiliser le module simplejson pour les charger et accéder également aux éléments.
Il y a ce projet JSONPATH , qui essaie d'aider les gens à faire l'inverse de ce que vous avez l'intention de faire (étant donné un XPATH, comment le rendre facilement accessible via des objets python), ce qui semble plus utile.
Vous pouvez utiliser JMESPath qui est un langage de requête pour JSON et qui possède une implémentation python .
import jmespath # pip install jmespath
data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}
jmespath.search('root.section.item2', data)
Out[42]: 'value2'
La syntaxe de requête jmespath et les exemples en direct: http://jmespath.org/tutorial.html
Une autre option serait de convertir vos dictionnaires au format XML en utilisant quelque chose comme dicttoxml puis en utilisant des expressions XPath classiques, par exemple via lxml ou la bibliothèque que vous préférez.
from dicttoxml import dicttoxml # pip install dicttoxml
from lxml import etree # pip install lxml
data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}
xml_data = dicttoxml(data, attr_type=False)
Out[43]: b'<?xml version="1.0" encoding="UTF-8" ?><root><root><section><item1>value1</item1><item2>value2</item2></section></root></root>'
tree = etree.fromstring(xml_data)
tree.xpath('//item2/text()')
Out[44]: ['value2']
def Dict(var, *arg, **kwarg):
""" Return the value of an (imbricated) dictionnary, if all fields exist else return "" unless "default=new_value" specified as end argument
Avoid TypeError: argument of type 'NoneType' is not iterable
Ex: Dict(variable_dict, 'field1', 'field2', default = 0)
"""
for key in arg:
if isinstance(var, dict) and key and key in var: var = var[key]
else: return kwarg['default'] if kwarg and 'default' in kwarg else "" # Allow Dict(var, tvdbid).isdigit() for example
return kwarg['default'] if var in (None, '', 'N/A', 'null') and kwarg and 'default' in kwarg else "" if var in (None, '', 'N/A', 'null') else var
foo = {
'spam':'eggs',
'morefoo': {
'bar':'soap',
'morebar': {'bacon' : 'foobar'}
}
}
print Dict(foo, 'morefoo', 'morebar')
print Dict(foo, 'morefoo', 'morebar', default=None)
Avoir une fonction SaveDict (value, var, * arg) pouvant même s’ajouter aux listes de dict ...