Я перестал писать на Medium.com и теперь пишу на собственном сайте. Этот пост останется здесь для справки, но он больше не получит обновлений. Вы можете найти его здесь: https://sidneyliebrand.io/blog/the-greatities-and-gotchas-of-yaml

Обновление 08–11–2018: Спасибо, Анатолий Бабения, за указание на функцию синтаксического анализа base 60 в документации docker-compose. Это привело к тому, что я нашел еще один отличный ресурс и добавил его вместе с новым контентом в этот пост.

В этом посте я хочу поговорить о YAML. Как и очень популярный формат JSON, это формат файла, который позволяет хранить данные в структурированном виде. На прошлой неделе я обсуждал с коллегой неожиданное выходное значение при синтаксическом разборе YAML в хэш Ruby. Данные YAML выглядят так:

---
some_key:
  some_other_key: nil

При синтаксическом анализе в Ruby это выглядит так:

{'some_key' => {'some_other_key' => 'nil'}}

И эквивалентный вывод Python:

{'some_key': {'some_other_key': 'nil'}}

Путаница заключалась в значении some_other_key, которое, как мы оба думали, станет nil вместо 'nil'. Я сказал своему коллеге, что если он хочет получить нулевое значение, он может оставить его полностью пустым:

---
some_key:
  some_other_key:

Что действительно приводит к ожидаемому результату в Ruby:

{'some_key' => {'some_other_key' => nil}}

И, конечно же, в Python:

{'some_key': {'some_other_key': None}}

В этот момент нам стало любопытно, я имею в виду, должно быть какое-то значение nil, верно? Итак, мы рискнули в Google и нашли ответ в кратчайшие сроки :) В YAML есть значение nil, оно называется null!

---
some_key:
  some_other_key: null

Также дает ожидаемый результат как для Ruby, так и для Python.

И это было только начало ...

С того момента мне было интересно, что еще есть у YAML. Я написал буквально тысячи строк данных YAML для одной из своих жемчужин, но никогда особо не задумывался, что этот язык действительно может делать.

Я также заметил, что там не так много сообщений YAML, некоторые ресурсы, которые я использовал при сборе информации для этого сообщения:

Поэтому я хотел бы поделиться некоторыми функциями YAML, о которых вы могли не знать, а также рассказать о некоторых различиях между синтаксическими анализаторами YAML (синтаксическими анализаторами Ruby и Python).

Наследование

Одна интересная особенность, которую я впервые увидел при загрузке примера приложения Rails, заключалась в том, что вы можете определять «значения по умолчанию» с помощью якорей. В Rails файл config/database.yml по умолчанию содержит следующее содержимое:

default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
development:
  <<: *default
  database: db/development.sqlite3
test:
  <<: *default
  database: db/test.sqlite3
production:
  <<: *default
  database: db/production.sqlite3

Как видите, есть клавиша default, за которой следует &default. Ключевое слово &default здесь представляет якорь. Затем в другом узле YAML вы можете унаследовать свойства от этого якоря, добавив в этом случае специальный ключ <<, за которым следует *default. Чтобы перезаписать значение по умолчанию, просто добавьте ключ, который вы хотите перезаписать, с новым значением под строкой <<: *default.

Напишите JSON в своем YAML

Еще одна полезная вещь, которую нужно знать, - это то, что вы можете писать JSON внутри YAML, это довольно аккуратно и ожидаемо, поскольку YAML - это надмножество JSON (или, по крайней мере, начиная с версии 1.2).

Следующий YAML:

---
key: {"some": "json"}
another: [1, 2, 3]

Анализируя в Ruby, это приводит к:

{"hello"=>{"some"=>"json"}, "another"=>[1, 2, 3]}

Ключи YAML как символы Ruby

Этого я специально искал, когда начал переписывать одну из своих жемчужин и решил перенести тестовые данные из Ruby в YAML. Мне было любопытно узнать, действительно ли YAML может хранить символы Ruby вместо Strings. Хотя в то время у меня не было тысяч тестов, написанных на YAML, я подумал: «Почему бы и нет?». Ответ заключался в том, что синтаксический анализатор Ruby действительно понимает символы, написанные на YAML, и обрабатывает их как таковые при синтаксическом анализе в Ruby.

---
:my_symbol_key: :or_value

В Ruby это оценивается следующим образом:

{:my_symbol_key=>:or_value}

В то время как тот же YAML анализируется в выходных данных Python:

{':my_symbol_key': ':or_value'}

Я только недавно подумал об этом, если бы я по какой-то причине перенес свой гем на Python, я бы больше не смог «удобно» использовать этот YAML, и для всех, кто хочет использовать YAML драгоценного камня вне Ruby, он будет содержать бесполезные : символы в начале каждого «символа». Так что да, но используйте с осторожностью! Я подумываю переписать YAML моего гема, чтобы использовать только строки вместо символов из-за этой «эксклюзивной» функции Ruby :)

Многострочные строки? YAML тебя поддержит!

Еще одна тема, часто обсуждаемая в языках программирования в целом, - это обработка многострочных строк, разные языки имеют разные решения одной и той же проблемы. У YAML есть два собственных решения. Символ вертикальной черты (|) и знак «больше» (>).

Обозначение трубы, также называемое «буквенным блоком»:

literal: |
    This block of text will be the value of the 'literal' key,
    with line breaks being preserved.

    It continues until de-dented, leading indentation is
    stripped.

        Any lines that are 'more-indented' keep the rest
        of their indentation -
        these lines will be indented by 4 spaces.

Знак «больше», также называемый «сложенный блок»:

folded: >
    This block of text will be the value of 'folded', but this
    time, all newlines will be replaced with a single space.

    Blank lines, like above, are converted to a newline character.

        'More-indented' lines keep their newlines, too -
        this text will appear over two lines.

Оба фрагмента взяты отсюда. Этот пост также содержит множество других замечательных примеров YAML, которые вам обязательно стоит посмотреть!

Цитированные строки, прочь!

В отличие от своего друга JSON, YAML не возражает, если вы не помещаете строки в кавычки. Следующее будет выводить именно то, что вы ожидаете:

some_key: with a string value

В Ruby и Python результаты одинаковы (вывод в Ruby):

{"some_key"=>"with a string value"}

Ключи также не нужно указывать в кавычках, поэтому удаление _ из some_key приведет к следующему как в Ruby, так и в Python (вывод в Ruby):

{"some key"=>"with a string value"}

Хотя это упрощает копирование определенных значений, YAML пытается уметь разбираться в некоторых (даже в большем, чем вы думаете) из них. Когда присутствует ключ со значением yes, Yes, YES, on, On или ON, результирующее значение при синтаксическом анализе этого YAML будет логическим. То же самое верно для значений no, No, NO, off, Off и OFF.

В следующем примере показан синтаксис Ruby, но Python 3.6 проанализировал его точно так же.

# All the following equal true
YAML.load("key: Yes")
YAML.load("key: yes")
YAML.load("key: YES")
YAML.load("key: on")
YAML.load("key: On")
YAML.load("key: ON")
# => {"key"=>true}
# All the following equal false
YAML.load("key: no")
YAML.load("key: No")
YAML.load("key: NO")
YAML.load("key: off")
YAML.load("key: Off")
YAML.load("key: OFF")
# => {"key"=>false}

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

Ценности кастинга

Если вы хотите убедиться, что ключ имеет значение определенного типа, вы можете явно приводить значения: key: !!str 0.5 = ›{"key" => "0.5"} как в Ruby, так и в Python. Точно так же key: !!float '0.5' = ›{"key" => 0.5}.

Некоторые парсеры фактически реализуют теги для конкретного языка. Их можно использовать для создания конкретных структур данных для данного языка:

---
key: !!python/tuple [1, 2]

Результат на Python:

{key: (1, 2)}

Что меня ДЕЙСТВИТЕЛЬНО удивило, так это то, что парсер Ruby вместо этого превратил его в массив:

{"key" => [1, 2]}

Поэтому я подумал про себя: «Что, если я заменю‘ !! python / tuple ’на‘ !! ruby ​​/ array? ’». Итак, я продолжил и обновил фрагмент:

---
key: !!ruby/array [1, 2]

И, как и ожидалось, Ruby возвращает правильный результат:

{"key" => [1, 2]}

С другой стороны, у нашего друга Python есть некоторые проблемы:

...snipped...
yaml.constructor.ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:ruby/array'
  in "<unicode string>", line 1, column 6:
    key: !!ruby/array [1, 2]

В приведенном выше примере мы видим, что синтаксический анализатор Python выдает ошибку, поскольку не может найти правильный конструктор для тега. Когда Ruby находит тег для конкретного языка, который не знает, как использовать, он просто игнорируется. Я думаю, что у обоих языков разные точки зрения, где Python более «требователен» к тому, какой тип YAML вы ему скармливаете, а Ruby старается «смягчить» ваш опыт, когда это возможно.

Так что спасибо Ruby (по крайней мере, MRI Ruby) за поддержку и лечение этих типов Pythonic, как если бы они были вашими собственными ❤

Целочисленное обозначение

Это небольшой, и часть нескольких языков программирования, чтобы улучшить читаемость больших целых / двоичных чисел. YAML позволяет использовать _ символов для «группировки» чисел, например 1000000000 против 1_000_000_000. Я думаю, что последний во много раз читабельнее, и поэтому я считаю, что YAML заслуживает почетного упоминания за то, что включает в себя этот потрясающий подвиг! 👍

Шестидесятеричные числа?

Мы уже видели странное поведение, когда некоторые строковые значения без кавычек волшебным образом превращались в логические, но это еще не все! YAML анализирует числа в формате ii:jj по основанию 60! Например, в Ruby:

YAML.load("key: 12:30:00")
# => {"key"=>45000}

Хотя результат соответствует спецификации, он чаще всего является нежелательным. Интереснее становится, когда цифра начинается с 0. В Ruby:

YAML.load("key: 01:30:00")
# => {"key"=>5400}

В то время как в Python:

yaml.safe_load("key: 01:30:00")
# => {'key': '01:30:00'}

Ruby, кажется, пытается «исправить» это, обрезая ведущий 0 и разбирая остальное в базе 60, тогда как Python видит, что это значение недопустимо в формате ii:jj. Я не уверен, почему это так, но думаю, это то, о чем мы собираемся поговорить дальше.

Восьмеричные числа

Если ваш YAML содержит целочисленные значения, начинающиеся с 0 и не содержащие цифр больше 7, они будут проанализированы как восьмеричные значения. В Ruby:

# parsed as octal
YAML.load("key: 0123")
# => {"key": 83}
# parsed 'normally'
YAML.load("key: 01238")
# => {"key": "01238"}

В этом случае Python делает то же самое. Чтобы вернуться к предыдущему примеру, я думаю, что Python видит значение 01:30:00 как недопустимое восьмеричное число и поэтому предпочитает анализировать его как строку.

Сложные ключи

Помимо строковых ключей, YAML не будет жаловаться, если вы захотите использовать числа с плавающей запятой:
1.1: hello there = ›{1.1 => "hello there"}, но это все еще простой ключ. Он будет жаловаться на использование списка или хеша в качестве ключа: [1, 2, 3]: hello there = ›error. Парсеры Ruby и Python выдают ошибку при каждой попытке.

Решение - использовать тег для конкретного языка. Это можно использовать для создания ключей, которые представляют собой сложные типы данных, такие как массив Ruby или кортеж Python.

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

---
? !!python/tuple [1, 2]
: hello

В Python это приведет к:

{(1, 2): 'hello'}

Ruby, с другой стороны, не имеет типа «Tuple» (и я не ожидал, что он понимает теги python) и использует то, что наиболее похоже на него, - массив:

{[1, 2] => "hello"}

Так что, хотя это немного неудобно и не очень портативно, все же кое-что полезно знать на всякий случай :)

Комментарии

Мы уже видели, что за чудовищный YAML на самом деле скрывается под капотом, я сам узнал новые вещи во время написания этого поста, поскольку я прогонял каждый пример через Python и Ruby REPL одновременно ( Спасибо tmux pane-synchronization ❤) и это еще не все! Еще одна, казалось бы, тривиальная, но отсутствующая в JSON функция - это # comments, такая же, как эта.

В JSON комментарии не поддерживаются, но, конечно же, YAML поддерживает нас и позволяет нам делать практически все, что мы хотим, комментарий начинается со знака #:

---
some: yaml
# oh noes! A comment
no: problem

И Ruby, и Python просто игнорируют комментарий:

{"key"=>[1, 2], "key2"=>"no problem"}

Резюме

Вкратце, в этом посте описаны следующие особенности:

  • Наследование / значения по умолчанию
  • Напишите JSON в YAML
  • Символы Ruby как ключи
  • Многострочные строки
  • Цитированные строки
  • Ценности кастинга
  • Целочисленное обозначение
  • Шестидесятеричные числа?
  • Восьмеричные числа
  • Сложные ключи
  • Комментарии

YAML, безусловно, универсальный marku… lang… да, неважно :) Но если серьезно, YAML действительно очень универсален, он может делать много вещей, как вы, надеюсь, видели в примерах.

REPLS, используемые для тестирования, были любопытными для Ruby и встроенного REPL Python.
В Ruby использовался синтаксический анализатор Yaml на Ruby (MRI) 2.4.1, а для Python pyyaml использовался на Python 3.6.2. .

Обновление сообщения: в процессе обновления этого сообщения я использовал pry для Ruby (MRI) 2.5.1 и Python (3.6.7), встроенный в REPL. Эти же библиотеки использовались для тестирования.

Заключение

Я считаю, что YAML - это здорово! До сих пор все мои впечатления от YAML были положительными, включая написание тысяч строк или устранение проблемы. Даже писать этот пост было приятно, я просто не торопился, открыл свои любимые REPL с включенной синхронизацией панелей, чтобы сократить ввод текста, и начал собирать информацию и примеры, иногда с побочными эффектами, которых я даже не ожидал, что приводило к интересным результатам.

Я почти уверен, что кое-что упустил, учитывая то, что мы только что наблюдали ранее в разделе «Приведение значений». Вероятно, этих нюансов гораздо больше между различными другими синтаксическими анализаторами.

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

Ваше здоровье!