Saya ingin mendapatkan loader PyYAML untuk memuat pemetaan (dan memesan pemetaan) ke dalam tipe Python 2.7+ OrderedDict , bukan vanilla dict
dan daftar pasangan yang saat ini digunakan.
Apa cara terbaik untuk melakukan itu?
Saya ingin mendapatkan loader PyYAML untuk memuat pemetaan (dan memesan pemetaan) ke dalam tipe Python 2.7+ OrderedDict , bukan vanilla dict
dan daftar pasangan yang saat ini digunakan.
Apa cara terbaik untuk melakukan itu?
Jawaban:
Pembaruan: Dalam python 3.6+ Anda mungkin tidak perlu OrderedDict
sama sekali karena implementasi dikt baru yang telah digunakan dalam pypy untuk beberapa waktu (meskipun dianggap detail implementasi CPython untuk saat ini).
Pembaruan: Dalam python 3.7+, sifat pelestarian sisipan urutan objek didik telah dinyatakan sebagai bagian resmi dari spesifikasi bahasa Python , lihat What's New In Python 3.7 .
Saya suka solusi @James karena kesederhanaannya. Namun, itu mengubah yaml.Loader
kelas global default , yang dapat menyebabkan efek samping yang merepotkan. Terutama, ketika menulis kode perpustakaan ini adalah ide yang buruk. Juga, itu tidak langsung bekerja dengannya yaml.safe_load()
.
Untungnya, solusinya dapat ditingkatkan tanpa banyak usaha:
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)
Untuk serialisasi, saya tidak tahu generalisasi yang jelas, tetapi setidaknya ini seharusnya tidak memiliki efek samping:
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)
Modul yaml memungkinkan Anda menentukan 'perwakilan' khusus untuk mengonversi objek Python menjadi teks dan 'konstruktor' untuk membalikkan proses.
_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
dan kemudian mengubahnya iteritems(data)
agar berfungsi sama baiknya di Python 2 & 3.
represent_dict
dan DEFAULT_MAPPING_TAG
) yang tidak berdokumen . Apakah ini karena dokumentasinya tidak lengkap, atau apakah fitur-fitur ini tidak didukung dan dapat berubah tanpa pemberitahuan?
dict_constructor
Anda harus menelepon loader.flatten_mapping(node)
atau Anda tidak akan dapat memuat <<: *...
(menggabungkan sintaksis)
oyaml
adalah pengganti drop-in untuk PyYAML yang mempertahankan pemesanan dict. Kedua Python 2 dan Python 3 didukung. Cukup pip install oyaml
, dan impor seperti yang ditunjukkan di bawah ini:
import oyaml as yaml
Anda tidak akan lagi terganggu oleh pemetaan yang kacau saat membuang / memuat.
Catatan: Saya penulis oyaml.
ruamel.yaml adalah setetes untuk PyYAML (penafian: Saya pembuat paket itu). Mempertahankan urutan pemetaan adalah salah satu hal yang ditambahkan dalam versi pertama (0,1) kembali pada tahun 2015. Tidak hanya mempertahankan urutan kamus Anda, itu juga akan mempertahankan komentar, nama jangkar, tag dan tidak mendukung YAML 1.2 spesifikasi (dirilis 2009)
Spesifikasi mengatakan bahwa pemesanan tidak dijamin, tetapi tentu saja ada pemesanan dalam file YAML dan parser yang sesuai hanya bisa berpegang pada itu dan secara transparan menghasilkan objek yang menjaga pemesanan. Anda hanya perlu memilih parser, loader, dan dumper¹ yang tepat:
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)
akan memberimu:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
data
adalah tipe CommentedMap
yang berfungsi seperti dict, tetapi memiliki informasi tambahan yang disimpan hingga dibuang (termasuk komentar yang disimpan!)
CommentedMap
secara langsung tetapi tidak berhasil, dan OrderedDict
menempatkan di !!omap
mana-mana yang tidak ramah pengguna.
CommentedMap
dengan safe=True
di YAML
, yang tidak bekerja (menggunakan safe=False
karya). Saya juga memiliki masalah dengan CommentedMap
tidak dapat dimodifikasi, tetapi saya tidak dapat mereproduksinya sekarang ... Saya akan membuka pertanyaan baru jika saya menemukan masalah ini lagi.
yaml = YAML()
, Anda mendapatkan parser / dumper pulang-pergi dan itu adalah turunan dari parser / dumper aman yang tahu tentang CommentedMap / Seq dll.
Catatan : ada pustaka, berdasarkan jawaban berikut, yang mengimplementasikan CLoader dan CDumper: Phynix / yamlloader
Saya sangat ragu bahwa ini adalah cara terbaik untuk melakukannya, tetapi ini adalah cara saya datang dengan, dan itu berhasil. Juga tersedia sebagai intisari .
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
atribut dalam pesan kesalahan Anda, saya tidak melihat cara yang jelas untuk menyederhanakan loop konstruksi pusat Anda. Jika Anda mencoba memanfaatkan fakta bahwa OrderedDict
konstruktor akan menerima iterable pasangan kunci, nilai, Anda kehilangan akses ke detail itu ketika membuat pesan kesalahan.
add_constructor
dalam __init__
metode Anda .
Pembaruan : perpustakaan tidak digunakan lagi karena yamlloader (yang didasarkan pada yamlordereddictloader)
Saya baru saja menemukan perpustakaan Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) yang dibuat berdasarkan jawaban atas pertanyaan ini dan cukup mudah digunakan:
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
yodl
di github.
Di instalasi PyYaml saya untuk Python 2.7 saya memperbarui __init__.py, constructor.py, dan loader.py. Sekarang mendukung opsi object_pairs_hook untuk memuat perintah. Perbedaan perubahan yang saya buat adalah di bawah ini.
__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)
inilah solusi sederhana yang juga memeriksa kunci tingkat atas yang digandakan di peta Anda.
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])