Як об’єднати масиви YAML?


113

Я хотів би об'єднати масиви в YAML і завантажити їх через ruby ​​-

some_stuff: &some_stuff
 - a
 - b
 - c

combined_stuff:
  <<: *some_stuff
  - d
  - e
  - f

Я хотів би мати комбінований масив як [a,b,c,d,e,f]

Я отримую помилку: не знайшов очікуваного ключа під час аналізу блоку відображення

Як злити масиви в YAML?


6
Чому ви хочете робити це в YAML, а не на мові, якою ви його розбираєте?
Патрік Коллінз

7
щоб вимкнути дублювання дуже великого файлу
yaml

4
Це дуже погана практика. Ви повинні прочитати ямли окремо, скласти масиви в Ruby, а потім записати назад у yaml.
sawa

74
Як намагаються бути сухими поганими практиками?
krak3n

13
@PatrickCollins Я знайшов це запитання, намагаючись зменшити дублювання у моєму файлі .gitlab-ci.yml, і, на жаль, я не маю контролю над аналізатором, який використовує GitLab CI :(
rink.attendant.6

Відповіді:


41

Якщо метою є виконання послідовності команд оболонки, ви можете досягти цього наступним чином:

# note: no dash before commands
some_stuff: &some_stuff |-
    a
    b
    c

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Це еквівалентно:

some_stuff: "a\nb\nc"

combined_stuff:
  - "a\nb\nc"
  - d
  - e
  - f

Я використовував це на своєму gitlab-ci.yml(щоб відповісти на коментар на питання @ rink.attendant.6).


Робочий приклад, який ми використовуємо для підтримки requirements.txtприватних репостів від gitlab:

.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://git@gitlab.com"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts

test:
    image: python:3.7.3
    stage: test
    script:
        - *pip_git
        - pip install -q -r requirements_test.txt
        - python -m unittest discover tests

use the same `*pip_git` on e.g. build image...

де requirements_test.txtмістить напр

-e git+ssh://git@gitlab.com/example/example.git@v0.2.2#egg=example


3
Розумний. Зараз я використовую це в нашому конвеєрі Bitbucket. Спасибі
Даріоп

* Зворотний штрих тут не потрібно, достатньо лише труби в кінці. * Це неповноцінне рішення, оскільки коли завдання не виходить із дуже довгого багаторядкового оператора, не зрозуміло, яка команда не вдалася.
Міна Люк

1
@MinaLuke, поступається порівняно з чим? Жодна з нинішніх відповідей не дає способу з’єднати два пункти, використовуючи лише ямл ... Більше того, у питанні немає нічого, що заявляє, що ОП бажає використовувати це в CI / CD. Нарешті, коли це використовується в CI / CD, ведення журналу залежить тільки від конкретного використовуваного CI / CD, а не від декларації yaml. Отже, якщо нічого, CI / CD, на який ви посилаєтесь, - це той, хто робить погану роботу. Ямл у цій відповіді справедливий і вирішує завдання ОП.
Хорхе

@JorgeLeitao Я думаю, ви використовуєте його для поєднання Правил. Чи можете ви надати працюючий приклад gitlabci? Я спробував щось на основі вашого рішення, але завжди отримую помилку перевірки.
niels

@niels, я додав приклад з робочим прикладом gitlabci. Зауважте, що деякі ІДЕ позначають цю ямлу недійсною, хоча вона не є.
Хорхе Лейтао

26

Оновлення: 2019-07-01 14:06:12

  • Примітка : інша відповідь на це питання була суттєво відредагована з оновленням альтернативних підходів .
    • Ця оновлена ​​відповідь в цій відповіді згадує альтернативне рішення. Він доданий до розділу Див. Також нижче.

Контекст

Ця публікація передбачає такий контекст:

  • пітон 2.7
  • розборник пітона YAML

Проблема

lfender6445 бажає об'єднати два або більше списків у файлі YAML, і ці об’єднані списки з'являться як один сингулярний список при розборі.

Рішення (вирішення)

Це може бути отримано просто шляхом призначення якорів YAML до відображень, де потрібні списки відображаються як дочірні елементи відображень. Однак у цьому є застереження (див. Далі "Підводні камені").

У наведеному нижче прикладі ми маємо три відображення ( list_one, list_two, list_three) і три якоря і псевдоніми , які посилаються на ці відображення , де це необхідно.

Коли файл YAML завантажується в програму, ми отримуємо потрібний список, але він може потребувати невеликих змін після завантаження (див. Підводні камені нижче).

Приклад

Оригінальний файл YAML

  list_one: & id001
   - а
   - б
   - c

  list_two: & id002
   - е
   - f
   - г

  list_three: & id003
   - год
   - я
   - j

  list_combined:
      - * id001
      - * id002
      - * id003

Результат після YAML.safe_load

## list_combined
  [
    [
      "а",
      "б",
      "c"
    ],
    [
      "е",
      "f",
      "г"
    ],
    [
      "h",
      "i",
      "j"
    ]
  ]

Підводні камені

  • такий підхід створює вкладений список списків, який може бути не точно потрібним результатом, але це може бути оброблено за допомогою методу сплющення
  • в звичайні застереження YAML якорів і псевдоніми застосовуються для унікальності та порядку декларування

Висновок

Такий підхід дозволяє створювати об'єднані списки за допомогою функції псевдоніму та якоря YAML.

Хоча вихідним результатом є вкладений список списків, це можна легко перетворити за допомогою flattenметоду.

Дивитися також

Оновлений альтернативний підхід від @Anthon

Приклади flattenспособу


21

Це не спрацює:

  1. Злиття підтримується лише специфікаціями YAML для відображень, а не для послідовностей

  2. ви повністю змішуєте речі, маючи ключ злиття, << за яким слід роздільник ключ / значення :та значення, яке є еталонним, а потім продовжуйте зі списком на тому ж рівні відступу

Це неправильно YAML:

combine_stuff:
  x: 1
  - a
  - b

Тож ваш синтаксис прикладу навіть не мав би сенсу як пропозиція щодо розширення YAML.

Якщо ви хочете зробити щось на зразок об'єднання декількох масивів, ви можете розглянути такий синтаксис:

combined_stuff:
  - <<: *s1, *s2
  - <<: *s3
  - d
  - e
  - f

де s1, s2, s3є якорями на послідовностях (не показані) , які ви хочете об'єднати в нову послідовність і потім мати d, eі f прикладені до цього. Але YAML вирішує цю структуру глибиною спочатку, тому під час обробки ключа злиття немає реального контексту. Для вас немає масиву / списку, до якого можна було б долучити оброблене значення (приєднану послідовність).

Ви можете прийняти підхід, запропонований @dreftymac, але це має величезний недолік, який вам потрібно якось знати, які вкладені послідовності вирівняти (тобто знаючи "шлях" від кореня завантаженої структури даних до батьківської послідовності), або що ви рекурсивно ходите по завантаженій структурі даних, шукаючи вкладені масиви / списки і без розбору вирівнювати їх усі.

Кращим рішенням IMO було б використовувати теги для завантаження структур даних, які роблять для вас вирівнювання. Це дозволяє чітко позначати, що потрібно вирівняти, а що ні, і надає повний контроль над тим, чи відбувається це вирівнювання під час завантаження чи це робиться під час доступу. Який вибрати - це питання простоти впровадження та ефективності в часі та місцях зберігання. Це той самий компроміс, який потрібно здійснити для реалізації ключової функції об'єднання, і не існує єдиного рішення, яке завжди є найкращим.

Наприклад, моя ruamel.yamlбібліотека використовує грубі сили merge-dicts під час завантаження при використанні свого безпечного завантажувача, що призводить до об'єднання словників, які є нормальними диктовками Python. Це злиття повинно здійснюватися наперед і дублює дані (неефективне місце), але швидко шукає значення. Використовуючи навантажувач із зворотним ходом, ви хочете мати змогу скинути злиття без накиду, тому їх потрібно тримати окремо. Диктування, подібна до структури даних, завантаженої в результаті завантаження в зворотній бік, є просторовим, але повільнішим у доступі, оскільки для цього потрібно спробувати і знайти ключ, який не знайдений у самому диктаті в злиттях (і це не кешовано, так що потрібно робити щоразу). Звичайно такі міркування не дуже важливі для відносно невеликих файлів конфігурації.


Далі реалізується схема подібного злиття для списків у python, використовуючи об’єкти з тегом, flatten які на ходу рекурсують у елементи, що є списками та позначеними тегами toflatten. За допомогою цих двох тегів ви можете мати файл YAML:

l1: &x1 !toflatten
  - 1 
  - 2
l2: &x2
  - 3 
  - 4
m1: !flatten
  - *x1
  - *x2
  - [5, 6]
  - !toflatten [7, 8]

(використання послідовностей стилю потоку проти блоку є абсолютно довільним і не впливає на завантажений результат).

При ітерації над елементами, які є значенням для ключа, m1це "повторюється" у послідовності, позначені тегом toflatten, але відображає інші списки (псевдонім чи ні) як один елемент.

Один з можливих способів за допомогою коду Python досягти цього:

import sys
from pathlib import Path
import ruamel.yaml

yaml = ruamel.yaml.YAML()


@yaml.register_class
class Flatten(list):
   yaml_tag = u'!flatten'
   def __init__(self, *args):
      self.items = args

   @classmethod
   def from_yaml(cls, constructor, node):
       x = cls(*constructor.construct_sequence(node, deep=True))
       return x

   def __iter__(self):
       for item in self.items:
           if isinstance(item, ToFlatten):
               for nested_item in item:
                   yield nested_item
           else:
               yield item


@yaml.register_class
class ToFlatten(list):
   yaml_tag = u'!toflatten'

   @classmethod
   def from_yaml(cls, constructor, node):
       x = cls(constructor.construct_sequence(node, deep=True))
       return x



data = yaml.load(Path('input.yaml'))
for item in data['m1']:
    print(item)

який виводить:

1
2
[3, 4]
[5, 6]
7
8

Як ви бачите, що ви бачите, у послідовності, яка потребує вирівнювання, ви можете використовувати псевдонім до послідовності з тегом, або ви можете використовувати позначену послідовність. YAML не дозволяє:

- !flatten *x2

, тобто позначає прив'язану послідовність, оскільки це по суті перетворить її в іншу структуру даних.

Використовувати явні теги - IMO краще, ніж робити магію, як і для клавіш злиття YAML <<. Якщо нічого іншого, вам зараз доведеться пройти обручі, якщо у вас випадково є файл YAML з відображенням, який містить ключ, <<який ви не хочете діяти як ключ злиття, наприклад, коли ви робите відображення операторів C на їх описи англійською (або якоюсь іншою природною мовою).


9

Якщо вам потрібно лише об'єднати один елемент у список, який ви можете зробити

fruit:
  - &banana
    name: banana
    colour: yellow

food:
  - *banana
  - name: carrot
    colour: orange

яка врожайність

fruit:
  - name: banana
    colour: yellow

food:
  - name: banana
    colour: yellow
  - name: carrot
    colour: orange

-4

Ви можете об'єднати відображення, а потім перетворити їхні ключі в список за таких умов:

  • якщо ви використовуєте шаблони jinja2 та
  • якщо порядок товару не важливий
some_stuff: &some_stuff
 a:
 b:
 c:

combined_stuff:
  <<: *some_stuff
  d:
  e:
  f:

{{ combined_stuff | list }}

Що не так у цій відповіді? Я не заперечую проти молодих, якщо вони аргументовані. Я відповідатиму людям, які можуть цим скористатися.
sm4rk0

3
Ймовірно, тому, що ця відповідь покладається на шаблон шаблону jinja2, коли питання вимагає зробити це в yml. jinja2 вимагає середовища Python, яке є контрпродуктивним, якщо ОП намагається БУТИ. Крім того, багато інструментів CI / CD не приймають шаблону шаблону.
Хорхе

Дякую @JorgeLeitao Що має сенс. Я навчився YAML та Jinja2 разом, розробляючи іграшки та шаблони Ansible і не можу думати один про одного
sm4rk0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.