Загружать YAML как вложенные объекты вместо словаря в Python - PullRequest
0 голосов
/ 29 сентября 2018

У меня есть файл конфигурации в YAML, который в настоящее время загружается как словарь с использованием yaml.safe_load.Для удобства написания своего кода я бы предпочел загружать его как набор вложенных объектов.Обращаться к более глубоким уровням словаря неудобно и затрудняет чтение кода.

Пример:

import yaml
mydict = yaml.safe_load("""
a: 1
b:
- q: "foo"
  r: 99
  s: 98
- x: "bar"
  y: 97
  z: 96
c:
  d: 7
  e: 8
  f: [9,10,11]
""")

В настоящее время я обращаюсь к таким элементам, как

mydict["b"][0]["r"]
>>> 99

Что я хотел бы иметь, так это получить доступ к такой же информации, как

mydict.b[0].r
>>> 99

Есть ли способ загрузить YAML как вложенные объекты, подобные этому?Или мне придется свернуть свой собственный класс и рекурсивно перевернуть эти словари во вложенные объекты?Я предполагаю, что namedtuple может сделать это немного проще, но я бы предпочел готовое решение для всего этого.

Ответы [ 3 ]

0 голосов
/ 01 октября 2018

Это можно сделать относительно легко и без изменения входного файла.

Поскольку использование dict PyYAML жестко запрограммировано и не может быть исправлено, вам нужно не только указывать в форме словаКласс, который ведет себя так, как вы хотите, вы также должны пройти через обручи, чтобы PyYAML использовал этот класс.Т.е. измените SafeConstructor, который обычно создает dict для использования этого нового класса, включите его в новый Loader и используйте PyYAML's load, чтобы использовать этот Loader:

import sys
import yaml

from yaml.loader import Reader, Scanner, Parser, Composer, SafeConstructor, Resolver

class MyDict(dict):
   def __getattr__(self, name):
       return self[name]

class MySafeConstructor(SafeConstructor):
   def construct_yaml_map(self, node):
       data = MyDict()
       yield data
       value = self.construct_mapping(node)
       data.update(value)

MySafeConstructor.add_constructor(
  u'tag:yaml.org,2002:map', MySafeConstructor.construct_yaml_map)


class MySafeLoader(Reader, Scanner, Parser, Composer, MySafeConstructor, Resolver):
    def __init__(self, stream):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        MySafeConstructor.__init__(self)
        Resolver.__init__(self)


yaml_str = """\
a: 1
b:
- q: "foo"
  r: 99
  s: 98
- x: "bar"
  y: 97
  z: 96
c:
  d: 7
  e: 8
  f: [9,10,11]
"""

mydict = yaml.load(yaml_str, Loader=MySafeLoader)

print(mydict.b[0].r)

, что дает:

99

Если вам нужно уметь работать с YAML1.2, вы должны использовать ruamel.yaml (заявление об отказе: я автор этого пакета), что немного упрощает приведенное выше описание

import ruamel.yaml

# same definitions for yaml_str, MyDict

class MySafeConstructor(ruamel.yaml.constructor.SafeConstructor):
   def construct_yaml_map(self, node):
       data = MyDict()
       yield data
       value = self.construct_mapping(node)
       data.update(value)

MySafeConstructor.add_constructor(
  u'tag:yaml.org,2002:map', MySafeConstructor.construct_yaml_map)


yaml = ruamel.yaml.YAML(typ='safe')
yaml.Constructor = MySafeConstructor
mydict = yaml.load(yaml_str)

print(mydict.b[0].r)

что также дает:

99

(и если ваш реальный ввод велик, загрузка данных должна быть заметно быстрее)

0 голосов
/ 21 декабря 2018

Нашел удобную библиотеку, которая делает именно то, что мне нужно: https://github.com/Infinidat/munch

import yaml
from munch import Munch
mydict = yaml.safe_load("""
a: 1
b:
- q: "foo"
  r: 99
  s: 98
- x: "bar"
  y: 97
  z: 96
c:
  d: 7
  e: 8
  f: [9,10,11]
""")
mymunch = Munch(mydict)

(Мне пришлось написать простой метод для рекурсивного преобразования всех поддиктов в мунчи, но теперь я могу перемещаться по своим данным, например:

>>> mymunch.b.q
"foo"
0 голосов
/ 30 сентября 2018

Если вы аннотируете корневой узел файла YAML тегом, вы можете определить классы Python, производные от YAMLObject, для работы с этим , как описано в документации PyYAML .

Однако, если вы предпочитаете, чтобы ваш YAML оставался чистым от тегов, вы можете создать вложенные классы самостоятельно (взято из мой ответ на аналогичный вопрос ):

import yaml

class BItem:
    def __init__(self, q, r, s):
        self.q, self.r, self.s = q, r, s

class CItem:
    def __init__(self, raw):
        self.d, self.e, self.f = raw['d'], raw['e'], raw['f']

class Root:
    def __init__(self, raw):
        self.a = raw['a']
        self.b = [BItem(i['q'], i['r'], i['s']) for i in raw['b']]
        self.c = CItem(raw['c'])

mydict = Root(yaml.safe_load("""
a: 1
b:
- q: "foo"
  r: 99
  s: 98
- q: "bar"
  r: 97
  s: 96
c:
  d: 7
  e: 8
  f: [9,10,11]
"""))

Однако этот подходработает только если ваш YAML структурирован однородно.Вы дали гетерогенную структуру, имея поля с разными именами в списке b (q, r, s в первом элементе; x, y, z во втором элементе),Я изменил вход YAML, чтобы иметь одинаковые имена полей, потому что с разными полями этот подход не работает.Я не уверен, является ли ваш YAML гетерогенным или вы просто случайно сделали это для примера.Если ваш YAML на самом деле неоднороден, то доступ к элементам с помощью dict-доступа является единственным жизнеспособным способом с тех пор, что ключи в файле YAML не соответствуют полям класса;они являются записями динамического отображения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...