В Python, как вы можете загрузить сопоставления YAML как OrderedDicts?
Я хотел бы получить PyYAMLзагрузчик для загрузки отображений (и упорядоченных отображений) в Python 2.7+ OrderedDict введите вместо ванили dict
и список пар, которые он в настоящее время использует.
Как лучше всего это сделать?
9 ответов
обновление: в python 3.6+ вам, вероятно, не нужно OrderedDict
из-за новая реализация дикт который используется в pypy в течение некоторого времени (хотя на данный момент рассматривается деталь реализации CPython).
обновление: в python 3.7+,природа сохранения порядка вставки объектов dict была объявлена официальной частью спецификации языка Python см. что нового в Python 3.7.
мне нравится @James'решение для своей простоты. Однако он изменяет значение по умолчанию global yaml.Loader
класс, который может привести к хлопотным побочным эффектам. Особенно, при написании библиотечного кода это плохая идея. Кроме того, он напрямую не работает с yaml.safe_load()
.
к счастью, решение может быть улучшено без особых усилий:
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)
для сериализации я не знаю очевидного обобщения, но, по крайней мере, это не должно было любые побочные эффекты:
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)
модуль yaml позволяет указать пользовательские "представители" для преобразования объектов Python в текст и "конструкторы" для обратного процесса.
_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)
2018 вариант:
oyaml
является заменой PyYAML который сохраняет заказ dict. Поддерживаются как Python 2, так и Python 3. Просто pip install oyaml
, и импорт, как показано ниже:
import oyaml as yaml
вы больше не будете раздражены испорченными отображениями при сбросе / загрузке.
Примечание: я автор oyaml.
2015 (и позже) вариант:
ruamel.и YAML является каплей в замене PyYAML (отказ от ответственности: я являюсь автором этого пакета). Сохранение порядка отображений было одной из вещей, добавленных в первой версии (0.1) еще в 2015 году. Он не только сохраняет порядок ваших словарей, но и сохраняет комментарии, имена Привязок, теги и поддерживает спецификацию YAML 1.2 (выпущенную в 2009 году)
спецификация говорит что приказывать не гарантируется, но, конечно, в файле YAML есть порядок, и соответствующий парсер может просто держаться за это и прозрачно генерировать объект, который сохраняет порядок. Вам просто нужно выбрать правильный парсер, загрузчик и dumper1:
import sys
import ruamel.yaml as yaml
yaml_str = """\
3: abc
conf:
10: def
3: gij # h is missing
more:
- what
- else
"""
data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
data['conf'][10] = 'klm'
data['conf'][3] = 'jig'
yaml.dump(data, sys.stdout, Dumper=yaml.RoundTripDumper)
даст вам:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
данные имеют тип CommentedMap, который функционирует как dict, но имеет дополнительную информацию, которая хранится до сброса (включая сохраненный комментарий!)
Примечание: существует библиотека, основанная на следующем ответе, которая реализует также CLoader и CDumpers:Phynix/yamlloader
как GiST.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
обновление библиотека была отменена в пользу yamlloader (который основан на yamlordereddictloader)
Я только что нашел библиотеку Python (https://pypi.python.org/pypi/yamlordereddictloader/0.1.1), который был создан на основе ответов на этот вопрос и довольно прост в использовании:
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
на моей установке для PyYaml для Python 2.7 я обновил __init__.py, constructor.py, и loader.py - ... Теперь поддерживает опцию object_pairs_hook для команд загрузки. Разница в изменениях, которые я сделал ниже.
__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)
есть PyYAML билет на эту тему открыли 5 лет назад. Он содержит некоторые соответствующие ссылки, в том числе ссылку на этот самый вопрос :) я лично схватил gist 317164 и немного изменил его, чтобы использовать OrderedDict из Python 2.7, не включенная реализация (просто заменил класс from collections import OrderedDict
).
вот простое решение, которое также проверяет наличие дублированных ключей верхнего уровня на вашей карте.
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])