Как я могу разобрать файл YAML в Python

Как я могу разобрать файл YAML в Python?

12 ответов

Решение

Самый простой и чистый метод без использования заголовков C - это PyYaml:

#!/usr/bin/env python

import yaml

with open("example.yaml", 'r') as stream:
    try:
        print(yaml.load(stream))
    except yaml.YAMLError as exc:
        print(exc)

И это все. Больше информации здесь:

http://pyyaml.org/wiki/PyYAMLDocumentation

Чтение и запись файлов YAML с Python 2+3 (и Unicode)

# -*- coding: utf-8 -*-
import yaml
import io

# Define data
data = {'a list': [1, 42, 3.141, 1337, 'help', u'€'],
        'a string': 'bla',
        'another dict': {'foo': 'bar',
                         'key': 'value',
                         'the answer': 42}}

# Write YAML file
with io.open('data.yaml', 'w', encoding='utf8') as outfile:
    yaml.dump(data, outfile, default_flow_style=False, allow_unicode=True)

# Read YAML file
with open("data.yaml", 'r') as stream:
    data_loaded = yaml.load(stream)

print(data == data_loaded)

Создан YAML файл

a list:
- 1
- 42
- 3.141
- 1337
- help
- €
a string: bla
another dict:
  foo: bar
  key: value
  the answer: 42

Общие окончания файлов

.yml а также .yaml

альтернативы

Для вашего приложения может быть важно следующее:

  • Поддержка другими языками программирования
  • Чтение / запись производительности
  • Компактность (размер файла)

Смотрите также: Сравнение форматов сериализации данных

Если вы предпочитаете создавать конфигурационные файлы, вы можете прочитать мою короткую статью Конфигурационные файлы в Python

Если у вас есть YAML, который соответствует спецификации YAML 1.2 (выпущен в 2009 году), вам следует использовать ruamel.yaml (заявление об отказе от ответственности: я являюсь автором этого пакета). По сути, это расширенный набор PyYAML, который поддерживает большую часть YAML 1.1 (с 2005 года).

Если вы хотите сохранить свои комментарии при циклическом переключении, вам, безусловно, следует использовать ruamel.yaml.

Обновление примера @Jon легко:

import ruamel.yaml as yaml

with open("example.yaml") as stream:
    try:
        print(yaml.safe_load(stream))
    except yaml.YAMLError as exc:
        print(exc)

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

Если вы используете pathlib Path для манипулирования файлами лучше использовать новый API ruamel.yaml:

from ruamel.yaml import YAML
from pathlib import Path

path = Path('example.yaml')
yaml = YAML(typ='safe')
data = yaml.load(path)

Импортируйте модуль yaml и загрузите файл в словарь под названием "my_dict":

import yaml
my_dict = yaml.load(open('filename'))

Это все, что вам нужно. Теперь весь файл yaml находится в словаре my_dict.

Чтобы получить доступ к любому элементу списка в файле YAML следующим образом:

global:
  registry:
    url: dtr-:5000/
    repoPath:
  dbConnectionString: jdbc:oracle:thin:@x.x.x.x:1521:abcd

Вы можете использовать следующий скрипт Python:

import yaml

with open("/some/path/to/yaml.file", 'r') as f:
    valuesYaml = yaml.load(f, Loader=yaml.FullLoader)

print(valuesYaml['global']['dbConnectionString'])

Пример:

defaults.yaml

url: https://www.google.com

environment.py

from ruamel import yaml

data = yaml.safe_load(open('defaults.yaml'))
data['url']

Я использую ruamel.yaml. Подробности и дебаты здесь.

from ruamel import yaml

with open(filename, 'r') as fp:
    read_data = yaml.load(fp)

Использование ruamel.yaml совместимо (с некоторыми простыми разрешимыми проблемами) со старым использованием PyYAML и, как указано в приведенной мной ссылке, используйте

from ruamel import yaml

вместо

import yaml

и это решит большинство ваших проблем.

РЕДАКТИРОВАТЬ: PyYAML не мертв, как оказалось, он просто поддерживается в другом месте.

Я сделал свой собственный сценарий для этого. Не стесняйтесь использовать его, пока вы сохраняете атрибуцию. Скрипт умеет парсить yaml из файла (функция load), разобрать yaml из строки (функция loads) и преобразовать словарь в yaml (функция dumps). Он учитывает все типы переменных.

      # © didlly AGPL-3.0 License - github.com/didlly

def is_float(string: str) -> bool:
    try:
        float(string)
        return True
    except ValueError:
        return False


def is_integer(string: str) -> bool:
    try:
        int(string)
        return True
    except ValueError:
        return False


def load(path: str) -> dict:
    with open(path, "r") as yaml:
        levels = []
        data = {}
        indentation_str = ""

        for line in yaml.readlines():
            if line.replace(line.lstrip(), "") != "" and indentation_str == "":
                indentation_str = line.replace(line.lstrip(), "").rstrip("\n")
            if line.strip() == "":
                continue
            elif line.rstrip()[-1] == ":":
                key = line.strip()[:-1]
                quoteless = (
                    is_float(key)
                    or is_integer(key)
                    or key == "True"
                    or key == "False"
                    or ("[" in key and "]" in key)
                )

                if len(line.replace(line.strip(), "")) // 2 < len(levels):
                    if quoteless:
                        levels[len(line.replace(line.strip(), "")) // 2] = f"[{key}]"
                    else:
                        levels[len(line.replace(line.strip(), "")) // 2] = f"['{key}']"
                else:
                    if quoteless:
                        levels.append(f"[{line.strip()[:-1]}]")
                    else:
                        levels.append(f"['{line.strip()[:-1]}']")
                if quoteless:
                    exec(
                        f"data{''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}[{key}]"
                        + " = {}"
                    )
                else:
                    exec(
                        f"data{''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}['{key}']"
                        + " = {}"
                    )

                continue

            key = line.split(":")[0].strip()
            value = ":".join(line.split(":")[1:]).strip()

            if (
                is_float(value)
                or is_integer(value)
                or value == "True"
                or value == "False"
                or ("[" in value and "]" in value)
            ):
                if (
                    is_float(key)
                    or is_integer(key)
                    or key == "True"
                    or key == "False"
                    or ("[" in key and "]" in key)
                ):
                    exec(
                        f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}[{key}] = {value}"
                    )
                else:
                    exec(
                        f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}['{key}'] = {value}"
                    )
            else:
                if (
                    is_float(key)
                    or is_integer(key)
                    or key == "True"
                    or key == "False"
                    or ("[" in key and "]" in key)
                ):
                    exec(
                        f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}[{key}] = '{value}'"
                    )
                else:
                    exec(
                        f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}['{key}'] = '{value}'"
                    )
    return data


def loads(yaml: str) -> dict:
    levels = []
    data = {}
    indentation_str = ""

    for line in yaml.split("\n"):
        if line.replace(line.lstrip(), "") != "" and indentation_str == "":
            indentation_str = line.replace(line.lstrip(), "")
        if line.strip() == "":
            continue
        elif line.rstrip()[-1] == ":":
            key = line.strip()[:-1]
            quoteless = (
                is_float(key)
                or is_integer(key)
                or key == "True"
                or key == "False"
                or ("[" in key and "]" in key)
            )

            if len(line.replace(line.strip(), "")) // 2 < len(levels):
                if quoteless:
                    levels[len(line.replace(line.strip(), "")) // 2] = f"[{key}]"
                else:
                    levels[len(line.replace(line.strip(), "")) // 2] = f"['{key}']"
            else:
                if quoteless:
                    levels.append(f"[{line.strip()[:-1]}]")
                else:
                    levels.append(f"['{line.strip()[:-1]}']")
            if quoteless:
                exec(
                    f"data{''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}[{key}]"
                    + " = {}"
                )
            else:
                exec(
                    f"data{''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}['{key}']"
                    + " = {}"
                )

            continue

        key = line.split(":")[0].strip()
        value = ":".join(line.split(":")[1:]).strip()

        if (
            is_float(value)
            or is_integer(value)
            or value == "True"
            or value == "False"
            or ("[" in value and "]" in value)
        ):
            if (
                is_float(key)
                or is_integer(key)
                or key == "True"
                or key == "False"
                or ("[" in key and "]" in key)
            ):
                exec(
                    f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}[{key}] = {value}"
                )
            else:
                exec(
                    f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}['{key}'] = {value}"
                )
        else:
            if (
                is_float(key)
                or is_integer(key)
                or key == "True"
                or key == "False"
                or ("[" in key and "]" in key)
            ):
                exec(
                    f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}[{key}] = '{value}'"
                )
            else:
                exec(
                    f"data{'' if line == line.strip() else ''.join(str(i) for i in levels[:line.replace(line.lstrip(), '').count(indentation_str) if indentation_str != '' else 0])}['{key}'] = '{value}'"
                )

    return data


def dumps(yaml: dict, indent="") -> str:
    """A procedure which converts the dictionary passed to the procedure into it's yaml equivalent.

    Args:
        yaml (dict): The dictionary to be converted.

    Returns:
        data (str): The dictionary in yaml form.
    """

    data = ""

    for key in yaml.keys():
        if type(yaml[key]) == dict:
            data += f"\n{indent}{key}:\n"
            data += dumps(yaml[key], f"{indent}  ")
        else:
            data += f"{indent}{key}: {yaml[key]}\n"

    return data


print(load("config.yml"))

Пример

config.yml

      level 0 value: 0

level 1:
  level 1 value: 1
  level 2:
    level 2 value: 2

level 1 2:
  level 1 2 value: 1 2
  level 2 2:
    level 2 2 value: 2 2

Выход

      {'level 0 value': 0, 'level 1': {'level 1 value': 1, 'level 2': {'level 2 value': 2}}, 'level 1 2': {'level 1 2 value': '1 2', 'level 2 2': {'level 2 2 value': 2 2}}}
#!/usr/bin/env python

import sys
import yaml

def main(argv):

    with open(argv[0]) as stream:
        try:
            #print(yaml.load(stream))
            return 0
        except yaml.YAMLError as exc:
            print(exc)
            return 1

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

Учитывая вышеупомянутые ответы, все из которых хороши, существует пакет Python, доступный для интеллектуального создания объектов из YAML / JSON / dicts, который активно разрабатывается и расширяется. (полное раскрытие, я являюсь соавтором этого пакета , см. здесь)

Установить:

pip install pickle-rick

Использовать:

Определите строку (или файл) YAML или JSON.

      BASIC:
 text: test
 dictionary:
   one: 1
   two: 2
 number: 2
 list:
   - one
   - two
   - four
   - name: John
     age: 20
 USERNAME:
   type: env
   load: USERNAME
 callable_lambda:
   type: lambda
   load: "lambda: print('hell world!')"
 datenow:
   type: lambda
   import:
     - "from datetime import datetime as dd"
   load: "lambda: print(dd.utcnow().strftime('%Y-%m-%d'))"
 test_function:
   type: function
   name: test_function
   args:
     x: 7
     y: null
     s: hello world
     any:
       - 1
       - hello
   import:
     - "math"
   load: >
     def test(x, y, s, any):
       print(math.e)
       iii = 111
       print(iii)
       print(x,s)
       if y:
         print(type(y))
       else:
         print(y)
       for i in any:
         print(i)

Затем используйте его как объект.

      >> from pickle_rick import PickleRick

>> config = PickleRick('./config.yaml', deep=True, load_lambda=True)

>> config.BASIC.dictionary
{'one' : 1, 'two' : 2}

>> config.BASIC.callable_lambda()
hell world!

Вы можете определять функции Python, загружать дополнительные данные из других файлов или REST API, переменных среды, а затем снова записывать все в YAML или JSON.

Это особенно хорошо работает при создании систем, требующих структурированных файлов конфигурации, или в записных книжках в виде интерактивных структур.

Это примечание по безопасности. Загружайте только те файлы, которым вы доверяете, так как любой код может быть выполнен, поэтому избегайте простой загрузки чего-либо, не зная, каково полное содержимое.

Пакет называется PickleRick и доступен здесь:

read_yaml_file функция, возвращающая все данные в словарь.

      def read_yaml_file(full_path=None, relative_path=None):
   if relative_path is not None:
        resource_file_location_local = ProjectPaths.get_project_root_path() + relative_path
else:
    resource_file_location_local = full_path

with open(resource_file_location_local, 'r') as stream:
    try:
        file_artifacts = yaml.safe_load(stream)
    except yaml.YAMLError as exc:
        print(exc)
return dict(file_artifacts.items())

Предложение: используйте yq (доступно через pip)

Я не уверен, как это не было предложено раньше, но я настоятельно рекомендую использовать yq , который является оболочкой jq для YAML.

yq использует синтаксис, подобный jq, но работает с файлами yaml так же, как и с json.


Примеры:

1 ) Прочитайте значение:

      yq e '.a.b[0].c' file.yaml

2 ) Труба из STDIN:

      cat file.yaml | yq e '.a.b[0].c' -

3) Обновите файл yaml на месте

      yq e -i '.a.b[0].c = "cool"' file.yaml

4) Обновление с использованием переменных среды:

      NAME=mike yq e -i '.a.b[0].c = strenv(NAME)' file.yaml

5) Объединить несколько файлов:

      yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml

6) Несколько обновлений файла yaml:

      yq e -i '
  .a.b[0].c = "cool" |
  .x.y.z = "foobar" |
  .person.name = strenv(NAME)
' file.yaml

(*) Подробнее о том, как парсить поля из yaml с помощью jq-фильтров .


Дополнительные ссылки:

https://github.com/mikefarah/yq/#install

https://github.com/kislyuk/yq

Другие вопросы по тегам