Ben almak istiyorum PyYAML Python 2.7+ içine yük eşleştirmeleri (ve sıralı eşleme) için 'ın yükleyici OrderedDict yerine vanilya, tip dict
ve çiftleri şu anda kullandığı listesinde.
Bunu yapmanın en iyi yolu nedir?
Ben almak istiyorum PyYAML Python 2.7+ içine yük eşleştirmeleri (ve sıralı eşleme) için 'ın yükleyici OrderedDict yerine vanilya, tip dict
ve çiftleri şu anda kullandığı listesinde.
Bunu yapmanın en iyi yolu nedir?
Yanıtlar:
Güncelleme: python 3.6+ sürümünde, pypy'de bir süredir kullanımda OrderedDict
olan yeni dict uygulaması nedeniyle muhtemelen hiç ihtiyacınız olmayacak (şimdilik CPython uygulaması ayrıntısı olarak düşünülse de).
Güncelleme: Python 3.7+ sürümünde, dikte nesnelerinin ekleme sırası koruma doğası, Python dil spesifikasyonunun resmi bir parçası olarak ilan edildi , bkz . Python 3.7'deki Yenilikler .
@ James'in çözümünü basitliği için seviyorum . Bununla birlikte, yaml.Loader
sorunlu yan etkilere neden olabilecek varsayılan genel sınıfı değiştirir . Özellikle kütüphane kodu yazarken bu kötü bir fikirdir. Ayrıca doğrudan onunla çalışmaz yaml.safe_load()
.
Neyse ki, çözüm çok fazla çaba sarf etmeden geliştirilebilir:
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)
Serileştirme için bariz bir genelleme bilmiyorum ama en azından bunun herhangi bir yan etkisi olmamalı:
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 modülü, Python nesnelerini metne dönüştürmek için özel 'temsilciler' ve işlemi tersine çevirmek için 'yapıcılar' belirlemenize olanak tanır.
_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
ve sonra iteritems(data)
Python 2 ve 3'te eşit derecede iyi çalışması için değiştirin .
represent_dict
ve DEFAULT_MAPPING_TAG
) ' nin belgelenmemiş özelliklerini kullanıyor gibi görünüyor . Bunun nedeni belgelerin eksik olması mı yoksa bu özellikler desteklenmiyor mu ve önceden haber verilmeksizin değiştirilebilir mi?
dict_constructor
aramak gerekir loader.flatten_mapping(node)
veya yüklemek mümkün olmayacaktır <<: *...
(birleştirme sözdizimi)
oyaml
PyYAML için dikte sıralamayı koruyan bir drop-in ikamesidir . Hem Python 2 hem de Python 3 desteklenmektedir. Sadece pip install oyaml
ve aşağıda gösterildiği gibi içe aktarın:
import oyaml as yaml
Artık damping / yükleme sırasında bozulan haritalamalardan rahatsız olmayacaksınız.
Not: Oyaml'ın yazarıyım.
ruamel.yaml , PyYAML'nin yerine geçen bir damladır (sorumluluk reddi: Ben bu paketin yazarıyım). Eşlemelerin sırasını korumak, 2015 yılında ilk sürümde (0.1) eklenen şeylerden biriydi. Yalnızca sözlüklerinizin sırasını korumakla kalmaz, aynı zamanda yorumları, bağlantı adlarını, etiketleri ve YAML 1.2'yi destekler. şartname (2009'da yayınlandı)
Spesifikasyon, sıralamanın garanti edilmediğini söylüyor, ancak elbette YAML dosyasında sipariş var ve uygun ayrıştırıcı bunu tutabilir ve şeffaf bir şekilde sıralamayı tutan bir nesne oluşturabilir. Doğru ayrıştırıcıyı, yükleyiciyi ve damperli kamyonu seçmeniz yeterlidir¹:
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)
sana vereceğim:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
data
tiptedir CommentedMap
bir dict gibi işlev gören, ancak atılmış kadar etrafında tutulur ekstra bilgiler (korunmuş comment dahil!)
CommentedMap
Doğrudan kullanmayı denedim ama işe yaramıyor ve pek kullanıcı dostu olmayan her yere OrderedDict
koyuyor !!omap
.
CommentedMap
ile safe=True
de YAML
(kullanarak iş vermedi ki, safe=False
işleri). Ayrıca CommentedMap
değiştirilebilir olmama konusunda sorun yaşadım, ancak şimdi yeniden oluşturamıyorum ... Bu sorunla tekrar karşılaşırsam yeni bir soru açacağım.
yaml = YAML()
, gidiş-dönüş ayrıştırıcısını / damperini alırsınız ve bu, CommentedMap / Seq vb. Hakkında bilgi sahibi olan güvenli ayrıştırıcının / damperin türevidir.
Not : Aşağıdaki yanıta dayalı olan ve aynı zamanda CLoader ve CDumper'ları da uygulayan bir kitaplık vardır: Phynix / yamlloader
Bunu yapmanın en iyi yolunun bu olduğundan çok şüpheliyim, ama bulduğum yol bu ve işe yarıyor. Ayrıca özet olarak da mevcuttur .
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
Özniteliği hata mesajınıza dahil etmek istiyorsanız , merkezi inşa döngüsünüzü basitleştirmenin açık bir yolunu görmüyorum. OrderedDict
Yapıcının yinelenebilir anahtar, değer çiftlerini kabul edeceği gerçeğini kullanmaya çalışırsanız , hata mesajını oluştururken bu ayrıntıya erişimi kaybedersiniz.
add_constructor
, __init__
yönteminizdeki ikinci çağrıyı kaldırabilirsiniz .
Güncelleme : kitaplık yamlloader lehine kullanımdan kaldırıldı (yamlordereddictloader'a dayalıdır)
Bu sorunun yanıtlarına göre oluşturulmuş ve kullanımı oldukça basit bir Python kitaplığı ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) buldum :
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
yodl
github'a bir bakın.
Python 2.7 için PyYaml kurulumumda __init__.py, constructor.py ve loader.py'yi güncelledim. Artık yükleme komutları için object_pairs_hook seçeneğini destekliyor. Yaptığım değişikliklerin farklılıkları aşağıdadır.
__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)
İşte haritanızda yinelenen üst düzey anahtarları da kontrol eden basit bir çözüm.
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])