web-dev-qa-db-fra.com

Comment fusionner des tableaux YAML?

Je voudrais fusionner des tableaux en YAML et les charger via Ruby -

some_stuff: &some_stuff
 - a
 - b
 - c

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

Je voudrais que le tableau combiné soit [a,b,c,d,e,f]

Je reçois l'erreur: n'a pas trouvé la clé attendue lors de l'analyse d'un mappage de bloc

Comment fusionner des tableaux dans YAML?

71
lfender6445

Mise à jour: 2019-07-01 14:06:12

  • Note : une autre réponse à cette question a été substantiellement modifiée avec un mise à jour sur les approches alternatives .
    • Cette réponse mise à jour mentionne une alternative à la solution de contournement dans cette réponse. Il a été ajouté à la section Voir aussi ci-dessous.

Le contexte

Ce post suppose le contexte suivant:

  • python 2.7
  • analyseur Python YAML

Problème

lfender6445 souhaite fusionner deux listes ou plus dans un fichier YAML et faire en sorte que ces listes fusionnées apparaissent comme une seule liste au moment de l'analyse.

Solution (solution de contournement)

Ceci peut être obtenu simplement en affectant des ancres YAML aux mappages, où les listes souhaitées apparaissent comme des éléments enfants des mappages. Il y a cependant des mises en garde à ce sujet (voir ci-dessous "Pièges").

Dans l'exemple ci-dessous, nous avons trois mappages (list_one, list_two, list_three) et trois ancres et alias qui font référence à ces mappages, le cas échéant.

Lorsque le fichier YAML est chargé dans le programme, nous obtenons la liste que nous voulons, mais cela peut nécessiter une petite modification après le chargement (voir les pièges ci-dessous).

Exemple

Fichier YAML d'origine

 list_one: & id001 
 - a 
 - b 
 - c 
 
 list_two: & id002 
 - e 
 - f 
 - g 
 
 list_three: & id003 
 - h 
 - i 
 - j 
 
 list_combined: 
 - * id001 
 - * id002 
 - * id003 

Résultat après YAML.safe_load

 ## list_combined 
 
 
 "a", 
 "b", 
 "c" 
, 
 
 "e", 
 "f", 
 "g" 
, 
 
 "h", 
 "i", 
 "j" 
 
 

Pièges

  • cette approche produit une liste imbriquée de listes, qui peut ne pas être la sortie souhaitée exacte, mais cela peut être post-traité en utilisant la méthode flatten
  • les mises en garde habituelles aux ancres et alias YAML s'appliquent pour l'unicité et l'ordre de déclaration

Conclusion

Cette approche permet la création de listes fusionnées en utilisant l'alias et la fonction d'ancrage de YAML.

Bien que le résultat en sortie soit une liste imbriquée de listes, cela peut être facilement transformé à l'aide de la méthode flatten.

Voir également

Approche alternative mise à jour par @Anthon

Exemples de la méthode flatten

18
dreftymac

Ça ne va pas marcher:

  1. la fusion n'est prise en charge que par les spécifications YAML pour les mappages et non pour les séquences

  2. vous mélangez complètement les choses en ayant une clé de fusion << suivi du séparateur clé/valeur : et une valeur qui est une référence , puis continuez avec une liste au même niveau d'indentation

Ce n'est pas correct YAML:

combine_stuff:
  x: 1
  - a
  - b

Ainsi, votre exemple de syntaxe n'aurait même pas de sens en tant que proposition d'extension YAML.

Si vous voulez faire quelque chose comme fusionner plusieurs tableaux, vous voudrez peut-être envisager une syntaxe comme:

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

s1, s2, s3 sont des ancres sur des séquences (non représentées) que vous souhaitez fusionner dans une nouvelle séquence, puis y ajouter d, e et f. Mais YAML résout d'abord ce type de structures en profondeur, il n'y a donc pas de contexte réel disponible pendant le traitement de la clé de fusion. Il n'y a pas de tableau/liste à votre disposition où vous pouvez attacher la valeur traitée (la séquence ancrée) à.

Vous pouvez adopter l'approche proposée par @dreftymac, mais cela a l'énorme inconvénient que vous devez en quelque sorte savoir quelles séquences imbriquées à aplatir (c'est-à-dire en connaissant le "chemin" de la racine de la structure de données chargée à la séquence parent), ou que vous parcourez récursivement la structure de données chargée à la recherche de tableaux/listes imbriqués et que vous les aplatissiez tous sans discrimination.

Une meilleure solution IMO serait d'utiliser des balises pour charger des structures de données qui font l'aplatissement pour vous. Cela permet d'indiquer clairement ce qui doit être aplati et ce qui ne l'est pas et vous donne un contrôle total sur si cet aplatissement est effectué pendant le chargement ou pendant l'accès. Le choix est une question de facilité de mise en œuvre et d'efficacité dans le temps et l'espace de stockage. C'est le même compromis qui doit être fait pour implémenter la fonction fusion clé et il n'y a pas de solution unique qui soit toujours la meilleure.

Par exemple. mon ruamel.yaml la bibliothèque utilise les fusion-dicts de force brute lors du chargement lors de l'utilisation de son chargeur sécurisé, ce qui se traduit par des dictionnaires fusionnés qui sont normaux Python dicts. Cette fusion doit être effectuée à l'avance, et duplique les données (espace inefficace) mais recherche rapidement des valeurs. Lorsque vous utilisez le chargeur aller-retour, vous voulez pouvoir vider les fusions non fusionnées, elles doivent donc être séparées. Le dict comme la structure de données chargée à la suite de le chargement aller-retour est peu encombrant mais plus lent d'accès, car il doit essayer de rechercher une clé non trouvée dans le dict lui-même dans les fusions (et ce n'est pas mis en cache, donc il doit être fait à chaque fois). bien sûr, ces considérations ne sont pas très importantes pour les fichiers de configuration relativement petits.


Ce qui suit implémente un schéma similaire à la fusion pour les listes dans python utilisant des objets avec la balise flatten qui à la volée se reproduit en éléments qui sont des listes et balisés toflatten En utilisant ces deux balises, vous pouvez avoir un fichier YAML:

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

(l'utilisation de séquences de style flux vs bloc est complètement arbitraire et n'a aucune influence sur le résultat chargé).

Lors de l'itération sur les éléments correspondant à la valeur de la clé m1 ce "se reproduit" dans les séquences marquées avec toflatten, mais affiche les autres listes (aliasées ou non) comme un seul élément.

Une façon possible avec Python code pour y parvenir est:

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)

qui génère:

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

Comme vous pouvez le voir, vous pouvez voir que, dans la séquence qui doit être aplatie, vous pouvez soit utiliser un alias pour une séquence balisée, soit utiliser une séquence balisée. YAML ne vous permet pas de faire:

- !flatten *x2

, c'est-à-dire marquer une séquence ancrée, car cela en ferait essentiellement une structure de données différente.

L'utilisation de balises explicites vaut mieux que d'avoir de la magie comme avec les clés de fusion YAML <<. Si rien d'autre, vous devez maintenant passer par des cercles si vous avez un fichier YAML avec un mappage qui a une clé << que vous ne voulez pas agir comme une clé de fusion, par exemple lorsque vous effectuez un mappage des opérateurs C avec leurs descriptions en anglais (ou dans un autre langage naturel).

11
Anthon

Si vous avez seulement besoin de fusionner un élément dans une liste, vous pouvez le faire

fruit:
  - &banana
    name: banana
    colour: yellow

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

qui donne

fruit:
  - name: banana
    colour: yellow

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

Vous pouvez y parvenir comme suit:

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

combined_stuff:
  - *some_stuff
  - d
  - e
  - f

Je l'utilise sur mon gitlab-ci.yml (pour répondre à @ rink.attendant.6 commentaire sur la question).

1
Jorge Leitão

Vous pouvez fusionner des mappages puis convertir leurs clés en liste, dans les conditions suivantes:

  • si vous utilisez le modèle jinja2 et
  • si l'ordre des articles n'est pas important
some_stuff: &some_stuff
 a:
 b:
 c:

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

{{ combined_stuff | list }}
0
sm4rk0