J'ai le fichier YAML suivant nommé input.yaml
:
cities:
1: [0,0]
2: [4,0]
3: [0,4]
4: [4,4]
5: [2,2]
6: [6,2]
highways:
- [1,2]
- [1,3]
- [1,5]
- [2,4]
- [3,4]
- [5,4]
start: 1
end: 4
Je le charge en utilisant PyYAML et j'imprime le résultat comme suit:
import yaml
f = open("input.yaml", "r")
data = yaml.load(f)
f.close()
print(data)
Le résultat est la structure de données suivante:
{ 'cities': { 1: [0, 0]
, 2: [4, 0]
, 3: [0, 4]
, 4: [4, 4]
, 5: [2, 2]
, 6: [6, 2]
}
, 'highways': [ [1, 2]
, [1, 3]
, [1, 5]
, [2, 4]
, [3, 4]
, [5, 4]
]
, 'start': 1
, 'end': 4
}
Comme vous pouvez le voir, chaque ville et autoroute est représentée sous forme de liste. Cependant, je veux qu'ils soient représentés comme un tuple. Par conséquent, je les convertis manuellement en tuples en utilisant des compréhensions:
import yaml
f = open("input.yaml", "r")
data = yaml.load(f)
f.close()
data["cities"] = {k: Tuple(v) for k, v in data["cities"].items()}
data["highways"] = [Tuple(v) for v in data["highways"]]
print(data)
Cependant, cela semble être un hack. Existe-t-il un moyen de demander à PyYAML de les lire directement sous forme de tuples au lieu de listes?
Je ne qualifierais pas ce que vous avez fait de hacky pour ce que vous essayez de faire. D'après ma compréhension, votre approche alternative consiste à utiliser des balises spécifiques à python dans votre fichier YAML afin qu'il soit représenté de manière appropriée lors du chargement du fichier yaml. Cependant, cela vous oblige à modifier votre fichier yaml qui, s'il est énorme, va probablement être assez irritant et pas idéal.
Regardez le PyYaml doc qui illustre encore cela. En fin de compte, vous voulez placer un !!python/Tuple
devant votre structure que vous souhaitez représenter comme telle. Pour prendre vos échantillons de données, il souhaite:
FICHIER YAML:
cities:
1: !!python/Tuple [0,0]
2: !!python/Tuple [4,0]
3: !!python/Tuple [0,4]
4: !!python/Tuple [4,4]
5: !!python/Tuple [2,2]
6: !!python/Tuple [6,2]
highways:
- !!python/Tuple [1,2]
- !!python/Tuple [1,3]
- !!python/Tuple [1,5]
- !!python/Tuple [2,4]
- !!python/Tuple [3,4]
- !!python/Tuple [5,4]
start: 1
end: 4
Exemple de code:
import yaml
with open('y.yaml') as f:
d = yaml.load(f.read())
print(d)
Qui produira:
{'cities': {1: (0, 0), 2: (4, 0), 3: (0, 4), 4: (4, 4), 5: (2, 2), 6: (6, 2)}, 'start': 1, 'end': 4, 'highways': [(1, 2), (1, 3), (1, 5), (2, 4), (3, 4), (5, 4)]}
Selon la provenance de votre entrée YAML, votre "hack" est une bonne solution, surtout si vous utilisez yaml.safe_load()
au lieu de la fonction non sécurisée yaml.load()
. Si seules les séquences "feuilles" de votre fichier YAML doivent être des tuples, vous pouvez le faire ¹:
import pprint
import ruamel.yaml
from ruamel.yaml.constructor import SafeConstructor
def construct_yaml_Tuple(self, node):
seq = self.construct_sequence(node)
# only make "leaf sequences" into tuples, you can add dict
# and other types as necessary
if seq and isinstance(seq[0], (list, Tuple)):
return seq
return Tuple(seq)
SafeConstructor.add_constructor(
u'tag:yaml.org,2002:seq',
construct_yaml_Tuple)
with open('input.yaml') as fp:
data = ruamel.yaml.safe_load(fp)
pprint.pprint(data, width=24)
qui imprime:
{'cities': {1: (0, 0),
2: (4, 0),
3: (0, 4),
4: (4, 4),
5: (2, 2),
6: (6, 2)},
'end': 4,
'highways': [(1, 2),
(1, 3),
(1, 5),
(2, 4),
(3, 4),
(5, 4)],
'start': 1}
si vous devez ensuite traiter plus de matériel où la séquence doit être à nouveau des listes "normales", utilisez:
SafeConstructor.add_constructor(
u'tag:yaml.org,2002:seq',
SafeConstructor.construct_yaml_seq)
¹ Cela a été fait en utilisant ruamel.yaml un analyseur YAML 1.2, dont je suis l'auteur. Vous devriez pouvoir faire de même avec l'ancien PyYAML si vous n'avez besoin que de prendre en charge YAML 1.1 et/ou ne pouvez pas mettre à niveau pour une raison quelconque
J'ai rencontré le même problème que la question et je n'ai pas été trop satisfait des deux réponses. En parcourant la documentation de pyyaml, j'ai trouvé vraiment deux méthodes intéressantes yaml.add_constructor
Et yaml.add_implicit_resolver
.
Le résolveur implicite résout le problème d'avoir à baliser toutes les entrées avec !!python/Tuple
En faisant correspondre les chaînes avec une expression régulière. Je voulais également utiliser la syntaxe Tuple, donc écrire Tuple: (10,120)
au lieu d'écrire une liste Tuple: [10,120]
Qui est ensuite convertie en Tuple, j'ai personnellement trouvé cela très ennuyeux. Je ne voulais pas non plus installer de bibliothèque externe. Voici le code:
import yaml
import re
# this is to convert the string written as a Tuple into a python Tuple
def yml_Tuple_constructor(loader, node):
# this little parse is really just for what I needed, feel free to change it!
def parse_tup_el(el):
# try to convert into int or float else keep the string
if el.isdigit():
return int(el)
try:
return float(el)
except ValueError:
return el
value = loader.construct_scalar(node)
# remove the ( ) from the string
tup_elements = value[1:-1].split(',')
# remove the last element if the Tuple was written as (x,b,)
if tup_elements[-1] == '':
tup_elements.pop(-1)
tup = Tuple(map(parse_tup_el, tup_elements))
return tup
# !Tuple is my own tag name, I think you could choose anything you want
yaml.add_constructor(u'!Tuple', yml_Tuple_constructor)
# this is to spot the strings written as Tuple in the yaml
yaml.add_implicit_resolver(u'!Tuple', re.compile(r"\(([^,\W]{,},){,}[^,\W]*\)"))
Enfin en exécutant ceci:
>>> yml = yaml.load("""
...: cities:
...: 1: (0,0)
...: 2: (4,0)
...: 3: (0,4)
...: 4: (4,4)
...: 5: (2,2)
...: 6: (6,2)
...: highways:
...: - (1,2)
...: - (1,3)
...: - (1,5)
...: - (2,4)
...: - (3,4)
...: - (5,4)
...: start: 1
...: end: 4""")
>>> yml['cities']
{1: (0, 0), 2: (4, 0), 3: (0, 4), 4: (4, 4), 5: (2, 2), 6: (6, 2)}
>>> yml['highways']
[(1, 2), (1, 3), (1, 5), (2, 4), (3, 4), (5, 4)]
Il pourrait y avoir un inconvénient potentiel avec save_load
Par rapport à load
que je n'ai pas testé.