Я хотел бы получить PyYAML загрузчик для загрузки сопоставлений (и упорядоченных сопоставлений) в Python 2.7+ OrderedDict вместо ванильного dict
и списка пар, который он использует в настоящее время.
Какой лучший способ сделать это?
Я хотел бы получить PyYAML загрузчик для загрузки сопоставлений (и упорядоченных сопоставлений) в Python 2.7+ OrderedDict вместо ванильного dict
и списка пар, который он использует в настоящее время.
Какой лучший способ сделать это?
Я очень сомневаюсь, что это лучший способ сделать это, но так я и придумал, и он работает. Также доступен как инструмент.
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 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
Обновление: Для python 3.6+ вам, вероятно, ничего не нужно, потому что новая реализация dict (хотя сейчас рассматривается детальность реализации CPython).
Мне нравится @James решение за его простоту. Однако он изменяет глобальный класс 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)
В спецификации указано, что упорядочение не гарантируется, но, конечно, в файле YAML есть упорядочение, и соответствующий синтаксический анализатор может просто удерживать его и прозрачно генерировать объект, который сохраняет порядок. Вам просто нужно выбрать правильный парсер, загрузчик и dumper¹:
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, но содержит дополнительную информацию, которая хранится до тех пор, пока не будет сброшена (включая сохраненный комментарий!)
¹ Это было сделано с помощью ruamel.yaml, из которых я являюсь автором. Это вилка и надмножество PyYAML.
Я только что нашел библиотеку Python (https://pypi.python.org/pypi/yamlordereddictloader/0.1.1), которая была создана на основе ответов на этот вопрос и довольно проста в использовании
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
oyaml
является заменой для PyYAML, который сохраняет заказ диктов. Поддерживаются как Python 2, так и Python 3. Просто pip install oyaml
и импортируйте, как показано ниже:
import oyaml as yaml
Вы больше не будете раздражаться привинченными картами при сбросе/загрузке.
Примечание. Я автор oyaml.
В моей установке для 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])