Vorrei che il caricatore di PyYAML caricasse le mappature (e le mappature ordinate) nel tipo OrderedDict Python 2.7+ , anziché la vaniglia dict
e l'elenco delle coppie attualmente utilizzate.
Qual è il modo migliore per farlo?
Vorrei che il caricatore di PyYAML caricasse le mappature (e le mappature ordinate) nel tipo OrderedDict Python 2.7+ , anziché la vaniglia dict
e l'elenco delle coppie attualmente utilizzate.
Qual è il modo migliore per farlo?
Risposte:
Aggiornamento: in Python 3.6+ probabilmente non è necessario OrderedDict
affatto a causa della nuova implementazione di dict che è stata in uso in pypy per un po 'di tempo (anche se per ora considerato i dettagli dell'implementazione di CPython).
Aggiornamento: in Python 3.7+, la natura di conservazione dell'ordine di inserimento degli oggetti dict è stata dichiarata parte ufficiale delle specifiche del linguaggio Python , vedere Novità di Python 3.7 .
Mi piace la soluzione di @James per la sua semplicità. Tuttavia, cambia la yaml.Loader
classe globale predefinita , che può portare a effetti collaterali fastidiosi. Soprattutto, quando si scrive il codice della libreria questa è una cattiva idea. Inoltre, non funziona direttamente con yaml.safe_load()
.
Fortunatamente, la soluzione può essere migliorata senza troppi sforzi:
import yaml
from collections import OrderedDict
def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict):
class OrderedLoader(Loader):
pass
def construct_mapping(loader, node):
loader.flatten_mapping(node)
return object_pairs_hook(loader.construct_pairs(node))
OrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
construct_mapping)
return yaml.load(stream, OrderedLoader)
# usage example:
ordered_load(stream, yaml.SafeLoader)
Per la serializzazione, non conosco un'ovvia generalizzazione, ma almeno questo non dovrebbe avere effetti collaterali:
def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
class OrderedDumper(Dumper):
pass
def _dict_representer(dumper, data):
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
data.items())
OrderedDumper.add_representer(OrderedDict, _dict_representer)
return yaml.dump(data, stream, OrderedDumper, **kwds)
# usage:
ordered_dump(data, Dumper=yaml.SafeDumper)
Il modulo yaml consente di specificare "rappresentatori" personalizzati per convertire oggetti Python in testo e "costruttori" per invertire il processo.
_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
def dict_representer(dumper, data):
return dumper.represent_dict(data.iteritems())
def dict_constructor(loader, node):
return collections.OrderedDict(loader.construct_pairs(node))
yaml.add_representer(collections.OrderedDict, dict_representer)
yaml.add_constructor(_mapping_tag, dict_constructor)
from six import iteritems
e poi cambiarlo in iteritems(data)
modo che funzioni ugualmente bene in Python 2 e 3.
represent_dict
e DEFAULT_MAPPING_TAG
). Questo perché la documentazione è incompleta o queste funzionalità non sono supportate e sono soggette a modifiche senza preavviso?
dict_constructor
dovrai chiamare loader.flatten_mapping(node)
o non sarai in grado di caricare <<: *...
(unire la sintassi)
oyaml
è un sostituto drop-in per PyYAML che preserva l'ordinamento del dict. Sono supportati sia Python 2 che Python 3. Basta pip install oyaml
importare come mostrato di seguito:
import oyaml as yaml
Non sarai più infastidito dalle mappature sbagliate durante lo scaricamento / caricamento.
Nota: sono l'autore di Oyaml.
ruamel.yaml sostituisce PyYAML (dichiarazione di non responsabilità: sono l'autore di quel pacchetto). Preservare l'ordine delle mappature è stata una delle cose aggiunte nella prima versione (0.1) nel 2015. Non solo preserva l'ordine dei dizionari, ma preserverà anche commenti, nomi di ancoraggio, tag e supporterà YAML 1.2 specifica (rilasciata nel 2009)
La specifica dice che l'ordinamento non è garantito, ma ovviamente c'è l'ordinamento nel file YAML e il parser appropriato può semplicemente trattenerlo e generare in modo trasparente un oggetto che mantiene l'ordinamento. Hai solo bisogno di scegliere il giusto parser, caricatore e dumper¹:
import sys
from ruamel.yaml import YAML
yaml_str = """\
3: abc
conf:
10: def
3: gij # h is missing
more:
- what
- else
"""
yaml = YAML()
data = yaml.load(yaml_str)
data['conf'][10] = 'klm'
data['conf'][3] = 'jig'
yaml.dump(data, sys.stdout)
ti darà:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
data
è di tipo CommentedMap
che funziona come un dict, ma ha informazioni extra che vengono conservate fino a quando non vengono scaricate (incluso il commento conservato!)
CommentedMap
direttamente ma non funziona e OrderedDict
mette !!omap
ovunque che non sia molto user-friendly.
CommentedMap
con safe=True
in YAML
, che non ha funzionato (usando safe=False
lavori). Ho anche avuto problemi a CommentedMap
non essere modificabile, ma non riesco a riprodurlo ora ... Aprirò una nuova domanda se incontro di nuovo questo problema.
yaml = YAML()
, ottieni il parser / dumper di andata e ritorno e che è derivato dal parser / dumper sicuro che conosce CommentedMap / Seq ecc.
Nota : esiste una libreria, basata sulla seguente risposta, che implementa anche CLoader e CDumpers: Phynix / yamlloader
Dubito fortemente che questo sia il modo migliore per farlo, ma questo è il modo in cui mi sono inventato e funziona. Disponibile anche come sostanza .
import yaml
import yaml.constructor
try:
# included in standard lib from Python 2.7
from collections import OrderedDict
except ImportError:
# try importing the backported drop-in replacement
# it's available on PyPI
from ordereddict import OrderedDict
class OrderedDictYAMLLoader(yaml.Loader):
"""
A YAML loader that loads mappings into ordered dictionaries.
"""
def __init__(self, *args, **kwargs):
yaml.Loader.__init__(self, *args, **kwargs)
self.add_constructor(u'tag:yaml.org,2002:map', type(self).construct_yaml_map)
self.add_constructor(u'tag:yaml.org,2002:omap', type(self).construct_yaml_map)
def construct_yaml_map(self, node):
data = OrderedDict()
yield data
value = self.construct_mapping(node)
data.update(value)
def construct_mapping(self, node, deep=False):
if isinstance(node, yaml.MappingNode):
self.flatten_mapping(node)
else:
raise yaml.constructor.ConstructorError(None, None,
'expected a mapping node, but found %s' % node.id, node.start_mark)
mapping = OrderedDict()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=deep)
try:
hash(key)
except TypeError, exc:
raise yaml.constructor.ConstructorError('while constructing a mapping',
node.start_mark, 'found unacceptable key (%s)' % exc, key_node.start_mark)
value = self.construct_object(value_node, deep=deep)
mapping[key] = value
return mapping
key_node.start_mark
attributo nel tuo messaggio di errore, non vedo alcun modo ovvio per semplificare il tuo ciclo di costruzione centrale. Se si tenta di sfruttare il fatto che il OrderedDict
costruttore accetterà un iterabile di coppie chiave, valore, si perde l'accesso a quel dettaglio quando si genera il messaggio di errore.
add_constructor
nel tuo __init__
metodo.
Aggiornamento : la libreria è stata deprecata a favore di yamlloader (che si basa su yamlordereddictloader)
Ho appena trovato una libreria Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) che è stata creata sulla base delle risposte a questa domanda ed è abbastanza semplice da usare:
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
yodl
su Github.
Nella mia installazione di For PyYaml per Python 2.7 ho aggiornato __init__.py, constructor.py e loader.py. Ora supporta l'opzione object_pairs_hook per i comandi di caricamento. La differenza tra le modifiche apportate è di seguito.
__init__.py
$ diff __init__.py Original
64c64
< def load(stream, Loader=Loader, **kwds):
---
> def load(stream, Loader=Loader):
69c69
< loader = Loader(stream, **kwds)
---
> loader = Loader(stream)
75c75
< def load_all(stream, Loader=Loader, **kwds):
---
> def load_all(stream, Loader=Loader):
80c80
< loader = Loader(stream, **kwds)
---
> loader = Loader(stream)
constructor.py
$ diff constructor.py Original
20,21c20
< def __init__(self, object_pairs_hook=dict):
< self.object_pairs_hook = object_pairs_hook
---
> def __init__(self):
27,29d25
< def create_object_hook(self):
< return self.object_pairs_hook()
<
54,55c50,51
< self.constructed_objects = self.create_object_hook()
< self.recursive_objects = self.create_object_hook()
---
> self.constructed_objects = {}
> self.recursive_objects = {}
129c125
< mapping = self.create_object_hook()
---
> mapping = {}
400c396
< data = self.create_object_hook()
---
> data = {}
595c591
< dictitems = self.create_object_hook()
---
> dictitems = {}
602c598
< dictitems = value.get('dictitems', self.create_object_hook())
---
> dictitems = value.get('dictitems', {})
loader.py
$ diff loader.py Original
13c13
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
18c18
< BaseConstructor.__init__(self, **constructKwds)
---
> BaseConstructor.__init__(self)
23c23
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
28c28
< SafeConstructor.__init__(self, **constructKwds)
---
> SafeConstructor.__init__(self)
33c33
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
38c38
< Constructor.__init__(self, **constructKwds)
---
> Constructor.__init__(self)
ecco una semplice soluzione che verifica anche la presenza di chiavi duplicate di livello superiore nella mappa.
import yaml
import re
from collections import OrderedDict
def yaml_load_od(fname):
"load a yaml file as an OrderedDict"
# detects any duped keys (fail on this) and preserves order of top level keys
with open(fname, 'r') as f:
lines = open(fname, "r").read().splitlines()
top_keys = []
duped_keys = []
for line in lines:
m = re.search(r'^([A-Za-z0-9_]+) *:', line)
if m:
if m.group(1) in top_keys:
duped_keys.append(m.group(1))
else:
top_keys.append(m.group(1))
if duped_keys:
raise Exception('ERROR: duplicate keys: {}'.format(duped_keys))
# 2nd pass to set up the OrderedDict
with open(fname, 'r') as f:
d_tmp = yaml.load(f)
return OrderedDict([(key, d_tmp[key]) for key in top_keys])