Xpath-подобный запрос для вложенных словарей Python - PullRequest
35 голосов
/ 06 сентября 2011

Есть ли способ определить запрос типа XPath для вложенных словарей Python.

Примерно так:

foo = {
  'spam':'eggs',
  'morefoo': {
               'bar':'soap',
               'morebar': {'bacon' : 'foobar'}
              }
   }

print( foo.select("/morefoo/morebar") )

>> {'bacon' : 'foobar'}

Мне также нужно было выбрать вложенные списки;)

Это можно легко сделать с помощью решения @ 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] Этот вопрос и принятый ответ древние.Более новые ответы могут сделать работу лучше, чем оригинальный ответ.Однако я не проверял их, поэтому не буду менять принятый ответ.

Ответы [ 10 ]

19 голосов
/ 26 сентября 2014

Одной из лучших библиотек, которую мне удалось идентифицировать, которая, кроме того, очень активно разрабатывается, является извлеченный проект из boto: JMESPath . Он обладает очень мощным синтаксисом выполнения действий, которые обычно требуют страниц кода для выражения.

Вот несколько примеров:

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]
13 голосов
/ 12 мая 2013

Существует более простой способ сделать это сейчас.

http://github.com/akesterson/dpath-python

$ easy_install dpath
>>> dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar")

... готово.Или, если вам не нравится возвращать результаты в представление (объединенный словарь, содержащий пути), вместо этого выведите их:

$ easy_install dpath
>>> for (path, value) in dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar", yielded=True)

... и готовоВ этом случае 'value' будет содержать {'bacon': 'foobar'}.

12 голосов
/ 01 января 2014

Существует более новая библиотека jsonpath-rw , поддерживающая синтаксис JSONPATH , но для python словарей и массивов , как вы пожелаете.

Итак, ваш первый пример:

from jsonpath_rw import parse

print( parse('$.morefoo.morebar').find(foo) )

И 2-го:

print( parse("$.morefoo[0].morebar.bacon").find(foo) )

PS: альтернативной более простой библиотекой, также поддерживающей словари, является python-json-pointer с более похожим на XPath синтаксисом.

11 голосов
/ 06 сентября 2011

Не совсем красиво, но вы можете использовать что-то вроде

def xpath_get(mydict, path):
    elem = mydict
    try:
        for x in path.strip("/").split("/"):
            elem = elem.get(x)
    except:
        pass

    return elem

Это не поддерживает такие вещи xpath, как индексы, конечно ... не говоря уже о указанном unutbu / ключевой ловушки.

3 голосов
/ 01 февраля 2018

Если вам нравится краткость:

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)

Конечно, если у вас только есть дикты, то проще:

def xpath(root, path, sch='/'):
    return reduce(lambda acc, nxt: acc[nxt],
                  path.split(sch),
                  root)

Удачи в поиске ошибок в вашем пути; -)

1 голос
/ 08 июля 2018

dict> json> jmespath

Вы можете использовать JMESPath , который является языком запросов для JSON и имеет реализацию Python .

import jmespath # pip install jmespath

data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}

jmespath.search('root.section.item2', data)
Out[42]: 'value2'

Синтаксис запроса jmespath и примеры в реальном времени: http://jmespath.org/tutorial.html

dict> xml> xpath

Другим вариантом будет преобразование ваших словарей в XML с использованием чего-то вроде dicttoxml и затем используйте регулярные выражения XPath, например, через lxml или любую другую предпочитаемую вами библиотеку.

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']
1 голос
/ 06 сентября 2011

Другая альтернатива (помимо предложенной желе ) - это:

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")
1 голос
/ 06 сентября 2011

Есть ли причина для вас, чтобы запросить его так же, как шаблон XPath? Как подсказал комментатор к вашему вопросу, это всего лишь словарь, так что вы можете получить доступ к элементам в виде гнезда. Кроме того, учитывая, что данные представлены в форме JSON, вы можете использовать модуль simplejson для его загрузки и доступа к элементам.

Существует проект JSONPATH , который пытается помочь людям делать то, что вы собираетесь делать противоположно (учитывая XPATH, как сделать его легко доступным через объекты Python), который кажется более полезным.

1 голос
/ 06 сентября 2011

Нужно больше работать над тем, как будет работать XPath-подобный селектор.'/' является действительным словарным ключом, поэтому как будет обрабатываться

foo={'/':{'/':'eggs'},'//':'ham'}

?

foo.select("///")

будет неоднозначным.

0 голосов
/ 24 сентября 2018
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)

Имеет функцию SaveDict (value, var, * arg), которая может даже добавлять списки в dict ...

...