Шесть фич YAML, о которых не знает большинство программистов

YAML  —  это формат файла, обычно используемый для сериализации данных. Существует множество проектов, использующих файлы YAML для настройки, таких как Docker-compose, pre-commit, TravisCI, AWS Cloudformation, ESLint, Kubernetes, Ansible и многие другие. Знание особенностей YAML поможет в работе с ними.

Сначала рассмотрим основы: YAML  —  это надмножество JSON (источник). Каждый корректный файл JSON также корректен как YAML. Это означает, что в вашем распоряжении есть все форматы, которые вы ждете: целые числа, числа с плавающей точкой, строки, булевы значения, null. Также последовательности (sequence) и отображения (map) . В зависимости от языка программирования вы можете заменить последовательность на массив или список, а отображение на словарь.

Обычно это выглядит так:

mysql:
  host: localhost
  user: root
  password: something
preprocessing_queue:  # Line comments are available!
  - name: preprocessing.scale_and_center
    width: 32
    height: 32
  - preprocessing.dot_reduction
use_anonymous: true

Эквивалентная нотация

В YAML есть много эквивалентных способов описания:

list_by_dash:
  - foo
  - bar
list_by_square_bracets: [foo, bar]
map_by_indentation:
  foo: bar
  bar: baz
map_by_curly_braces: {foo: bar, bar: baz}
string_no_quotes: Monty Python
string_double_quotes: "Monty Python"
string_single_quotes: 'Monty Python'
bool_english: yes
bool_english_no: no
bool_python: True
bool_json: true

Тут несколько слов предостережения:

language: no  # ISO 639-1 code for the Norwegian language

Это “no” интерпретируется как false. Писать надо "no" или 'no' .

В общем, я рекомендую использовать true и false точно так же, как для булевых значений в JSON, но YAML поддерживает одиннадцать способов записи булевых значений. При желании применить кавычки для строк я бы также рекомендовал пользоваться , как в JSON. Запомнить “no” все равно нужно, но, по крайней мере, для новичков в YAML файл будет выглядеть более привычно.

Есть и другие примеры, которые столь же коварны:

  • 013 отображается на 11, так как ведущий ноль запускает восьмеричную нотацию.
  • 4:30 отображается на 270. Мне сообщали, что это значение автоматически преобразуется в секунды, поскольку интерпретируется как длительность: 4*60 + 30 = 270 . Интересно, что этот паттерн до сих пор работает с 1:1:1:1:1:1:1:1:4:30.

Длинные строки

disclaimer: >
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In nec urna pellentesque, imperdiet urna vitae, hendrerit
odio. Donec porta aliquet laoreet. Sed viverra tempus fringilla.

Это эквивалентно следующему JSON (перенос строки добавлен для удобства чтения  —  пожалуйста, не обращайте внимания):

{"disclaimer": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec urna pellentesque, imperdiet urna vitae, hendrerit odio. Donec porta aliquet laoreet. Sed viverra tempus fringilla."}

Строки с несколькими линиями

mail_signature: |
      Martin Thoma
      Tel. +49 123 4567

Эквивалент в JSON выглядит так:

{"mail_signature": "Martin Thoma\nTel. +49 123 4567"}

Обратите внимание: начальные пробелы игнорируются. Первая строка (“Martin Thoma”) определяет количество игнорируемых начальных пробелов.

Якорь

email: &emailAddress "[email protected]"
id: *emailAddress

В JSON это эквивалентно:

{"email": "[email protected]", "id": "[email protected]"}

& определяет переменную EmailAddress значением [email protected]. Затем * указывает, что далее следует имя переменной.

То же самое можно сделать и с отображениями:

foo: &default_settings
  db:
    host: localhost
    name: main_db
    port: 1337
  email:
    admin: [email protected]
prod: *default_settings
dev: *default_settings

В результате получаем:

{ "dev": { "db": {"host":
                  "localhost",
                  "name": "main_db",
                  "port": 1337},
           "email": {"admin": "[email protected]"}},
  "foo": { "db": {"host": "localhost",
                  "name": "main_db",
                  "port": 1337},
           "email": {"admin": "[email protected]"}},
  "prod": { "db": {"host": "localhost",
                   "name": "main_db",
                   "port": 1337},
            "email": {"admin": "[email protected]"}}}

Теперь вам, возможно, захочется ввести пароль для окружений dev и prod. Вы можете сделать это с помощью ключа слияния (merge key) << :

foo: &default_settings
  db:
    host: localhost
    name: main_db
    port: 1337
  email:
    admin: [email protected]
prod:
  <<: *default_settings
  app:
    port: 80
dev: *default_settings

Что в JSON будет эквивалентно:

{ "foo": { "db": {"host": "localhost",
                  "name": "main_db",
                  "port": 1337},
           "email": {"admin": "[email protected]"}},
  "prod": { "app": {"port": 80},
            "db": {"host": "localhost",
                   "name": "main_db",
                   "port": 1337},
            "email": {"admin": "[email protected]"}},
  "dev": { "db": {"host": "localhost",
                  "name": "main_db",
                  "port": 1337},
           "email": {"admin": "[email protected]"}},}

Приведение типов

Двойной восклицательный знак !! имеет в YAML особое значение. Он называется “обработчик вторичных тэгов” и является сокращением для !tag:yaml.org,2002: (источник).

Вы можете выполнять простые преобразования, такие как:

price: !!float 42
id: !!str 42

Или более сложные  —  к примеру, сопоставление с типами Python по умолчанию, которые не указаны непосредственно в YAML:

tuple_example: !!python/tuple
  - 1337
  - 42
set_example: !!set {1337, 42}
date_example: !!timestamp 2020-12-31

Можно прочитать это так:

import yaml
import pprintwith open("example.yaml") as fp:
    data = fp.read()pp = pprint.PrettyPrinter(indent=4)pased = yaml.unsafe_load(data)
pp.pprint(pased)

И получить следующее:

{   'date_example': datetime.date(2020, 12, 31),
    'set_example': {1337, 42},
    'tuple_example': (1337, 42)}

В этом примере используется специфичный для Python тэг !!python/tuple и некоторые стандартные тэги YAML. Здесь можно познакомиться с хорошим обзором PyYAML.

## Стандартные тэги YAML
YAML               Python 3
!!null             None
!!bool             bool
!!int              int
!!float            float
!!binary           bytes
!!timestamp        datetime.datetime
!!omap, !!pairs    list of pairs
!!set              set
!!str              str
!!seq              list
!!map              dict## Python-specific tags
YAML               Python 3
!!python/none      None
!!python/bool      bool
!!python/bytes     bytes
!!python/str       str
!!python/unicode   str
!!python/int       int
!!python/long      int
!!python/float     float
!!python/complex   complex
!!python/list      list
!!python/tuple     tuple
!!python/dict      dict## Complex Python tags
!!python/name:module.name         module.name
!!python/module:package.module    package.module
!!python/object:module.cls        module.cls instance
!!python/object/new:module.cls    module.cls instance
!!python/object/apply:module.f    value of f(...)

Обратите внимание, что загружать нестандартные тэги  —  небезопасно. С помощью !!python/object/apply:module.f есть возможность выполнить произвольный код. В PyYAML для этого понадобится yaml.unsafe_load. Следовательно лучше им не пользоваться. 

Несколько документов в одном YAML

В YAML три тире служат разделителем документов.

foo: bar
---
fizz: buzz

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

import yaml

with open("example.yaml") as fp:
    data = fp.read()

parsed = yaml.safe_load_all(data)  # parsed - это генератор

Преобразовав parsed в список и выведя на экран, вы увидите:

[{'foo': 'bar'}, {'fizz': 'buzz'}]

Обратите внимание: это НЕ альтернативная нотация для списков. Это разные документы.

Генератор статических сайтов Pelican использует этот механизм, чтобы отличить метаданные от контента. Статический генератор сайтов Jekyl тоже пользуется им. И oc process также генерирует такие YAML. 

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи: Martin Thoma, “6 YAML Features most programmers don’t know”

Предыдущая статьяРазработчиком ПО может стать каждый - волшебных эликсиров не требуется
Следующая статьяРеализация интерфейсов в Golang