Chciałbym, aby program ładujący PyYAML załadował mapowania (i uporządkowane mapowania) do typu Python 2.7+ OrderedDict , zamiast wanilii dict
i listy par, których obecnie używa.
Jak najlepiej to zrobić?
źródło
Chciałbym, aby program ładujący PyYAML załadował mapowania (i uporządkowane mapowania) do typu Python 2.7+ OrderedDict , zamiast wanilii dict
i listy par, których obecnie używa.
Jak najlepiej to zrobić?
Aktualizacja: W Pythonie 3.6+ prawdopodobnie nie potrzebujesz OrderedDict
w ogóle ze względu na nową implementację dykta , która jest używana w pypy od jakiegoś czasu (chociaż na razie uważana jest za szczegół implementacji CPythona).
Aktualizacja: W Pythonie 3.7+, natura zachowania kolejności wstawiania obiektów dict została zadeklarowana jako oficjalna część specyfikacji języka Python , zobacz Co nowego w Pythonie 3.7 .
Rozwiązanie @James podoba mi się za jego prostotę. Jednak zmienia domyślną yaml.Loader
klasę globalną , co może prowadzić do kłopotliwych skutków ubocznych. Szczególnie podczas pisania kodu biblioteki jest to zły pomysł. Ponadto nie działa bezpośrednio z yaml.safe_load()
.
Na szczęście rozwiązanie można poprawić bez większego wysiłku:
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)
W przypadku serializacji nie znam oczywistego uogólnienia, ale przynajmniej nie powinno to mieć żadnych skutków ubocznych:
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)
Moduł yaml umożliwia określenie niestandardowych „reprezentantów” do konwersji obiektów Pythona na tekst i „konstruktorów” w celu odwrócenia tego procesu.
_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)
źródło
from six import iteritems
a następnie zmień to naiteritems(data)
tak, aby działało równie dobrze w Pythonie 2 i 3.represent_dict
iDEFAULT_MAPPING_TAG
). Czy to dlatego, że dokumentacja jest niekompletna, czy te funkcje nie są obsługiwane i mogą ulec zmianie bez powiadomienia?dict_constructor
jakość trzeba zadzwonićloader.flatten_mapping(node)
lub nie będzie w stanie załadować<<: *...
(seryjnej składni)Opcja 2018:
oyaml
jest bezpośrednim zamiennikiem PyYAML, który zachowuje porządek dyktowania. Obsługiwane są zarówno Python 2, jak i Python 3. Po prostupip install oyaml
i importuj, jak pokazano poniżej:import oyaml as yaml
Nie będziesz już irytować zepsutymi mapowaniami podczas zrzutu / ładowania.
Uwaga: jestem autorem oyaml.
źródło
Opcja 2015 (i nowsze):
ruamel.yaml to kropla zastępująca PyYAML (zastrzeżenie: jestem autorem tego pakietu). Zachowanie kolejności mapowań było jedną z rzeczy dodanych w pierwszej wersji (0.1) w 2015 roku. Nie tylko zachowuje kolejność Twoich słowników, ale również zachowuje komentarze, nazwy kotwic, tagi i obsługuje YAML 1.2 specyfikacja (wydana 2009)
Specyfikacja mówi, że kolejność nie jest gwarantowana, ale oczywiście istnieje porządek w pliku YAML i odpowiedni parser może to po prostu zatrzymać i przejrzyście wygenerować obiekt, który zachowuje kolejność. Wystarczy wybrać odpowiedni parser, moduł ładujący i wywrotkę¹:
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)
da tobie:
3: abc conf: 10: klm 3: jig # h is missing more: - what - else
data
jest typu,CommentedMap
który działa jak dykt, ale zawiera dodatkowe informacje, które są przechowywane do momentu zrzucenia (w tym zachowany komentarz!)źródło
CommentedMap
bezpośrednio, ale to nie działa iOrderedDict
umieszcza!!omap
wszędzie, co nie jest zbyt przyjazne dla użytkownika.CommentedMap
zsafe=True
inYAML
, który nie działał (używającsafe=False
działa). Miałem również problem zCommentedMap
brakiem możliwości modyfikacji, ale nie mogę tego teraz odtworzyć ... Otworzę nowe pytanie, jeśli ponownie napotkam ten problem.yaml = YAML()
, otrzymujesz parser / zrzut w obie strony, który jest pochodną bezpiecznego parsera / zrzutu, który wie o CommentedMap / Seq itp.Uwaga : istnieje biblioteka oparta na następującej odpowiedzi, która implementuje również CLoader i CDumpers: Phynix / yamlloader
Bardzo wątpię, że to najlepszy sposób, aby to zrobić, ale to jest sposób, w jaki wymyśliłem i działa. Dostępne również w skrócie .
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
źródło
key_node.start_mark
atrybut w komunikacie o błędzie, nie widzę żadnego oczywistego sposobu na uproszczenie centralnej pętli konstrukcyjnej. Jeśli spróbujesz wykorzystać fakt, żeOrderedDict
konstruktor zaakceptuje iterowalne pary klucz, wartość, utracisz dostęp do tego szczegółu podczas generowania komunikatu o błędzie.add_constructor
w swojej__init__
metodzie.Aktualizacja : biblioteka została wycofana na korzyść yamlloadera (opartego na yamlordereddictloader)
Właśnie znalazłem bibliotekę Pythona ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ), która została utworzona na podstawie odpowiedzi na to pytanie i jest dość prosta w użyciu:
import yaml import yamlordereddictloader datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
źródło
yodl
na github.Podczas mojej instalacji For PyYaml dla Pythona 2.7 zaktualizowałem __init__.py, constructor.py i loader.py. Teraz obsługuje opcję object_pairs_hook dla poleceń ładowania. Różnica zmian, które wprowadziłem poniżej.
__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)
źródło
oto proste rozwiązanie, które sprawdza również, czy na mapie nie ma zduplikowanych kluczy najwyższego poziomu.
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])
źródło