Python'da, YAML eşlemelerini OrderedDicts olarak nasıl yükleyebilirsiniz?


Yanıtlar:


147

Güncelleme: python 3.6+ sürümünde, pypy'de bir süredir kullanımda OrderedDictolan 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.Loadersorunlu 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)

3
+1 - bunun için çok teşekkür ederim, beni çok fazla zahmetten kurtardı.
Nobilis

2
Bu uygulama YAML birleştirme etiketlerini bozar, BTW
Randy

1
@Randy Teşekkürler. Bu senaryoda daha önce çalışmadım, ancak şimdi bunun üstesinden gelmek için bir düzeltme ekledim (umarım).
coldfix

9
@ArneBabenhauserheide PyPI'nin yeterince yukarı akışta olup olmadığından emin değilim, ancak eğer öyle olduğunu düşünüyorsanız ruamel.yaml'e (ben bunun yazarıyım) bir göz atın .
Anthon

1
@Anthon ruamel.yaml kitaplığınız çok iyi çalışıyor. Bunun için teşekkürler.
Jan Vlcinsky

56

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)

5
bu cevap için herhangi bir açıklama var mı?
Shuman

1
Veya daha da iyisi from six import iteritemsve sonra iteritems(data)Python 2 ve 3'te eşit derecede iyi çalışması için değiştirin .
Midnighter

5
Bu, PyYAML ( represent_dictve 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?
aldel

3
Not için bunu dict_constructoraramak gerekir loader.flatten_mapping(node)veya yüklemek mümkün olmayacaktır <<: *...(birleştirme sözdizimi)
Anthony Sottile

@ brice-m-dempsey kodunuzu nasıl kullanacağınıza dair bir örnek ekleyebilir misiniz? Benim durumumda çalışmıyor gibi görünüyor (Python 3.7)
schaffe

53

2018 seçeneği:

oyamlPyYAML için dikte sıralamayı koruyan bir drop-in ikamesidir . Hem Python 2 hem de Python 3 desteklenmektedir. Sadece pip install oyamlve 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.


1
Bunun için teşekkür ederim! Bazı nedenlerden dolayı, Python 3.8 ile bile sıraya PyYaml ile uyulmadı. oyaml bunu benim için hemen çözdü.
John Smith İsteğe Bağlı

26

2015 (ve sonrası) seçeneği:

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

datatiptedir CommentedMapbir dict gibi işlev gören, ancak atılmış kadar etrafında tutulur ekstra bilgiler (korunmuş comment dahil!)


Zaten bir YAML dosyanız varsa bu oldukça güzel, ancak bunu bir Python yapısı kullanarak nasıl yaparsınız? CommentedMapDoğrudan kullanmayı denedim ama işe yaramıyor ve pek kullanıcı dostu olmayan her yere OrderedDictkoyuyor !!omap.
Holt

CommentedMap'in neden sizin için çalışmadığından emin değilim. (Minimalize edilmiş) kodunuzla bir soru gönderebilir ve ruamel.yaml olarak etiketleyebilir misiniz? Bu şekilde bilgilendirilecek ve cevap vereceğim.
Anthon

Üzgünüm, kurtarmaya çalıştım çünkü düşünüyorum CommentedMapile safe=Truede YAML(kullanarak iş vermedi ki, safe=Falseişleri). Ayrıca CommentedMapdeğ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.
Holt

Kullanmalısınız 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.
Anthon

14

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. OrderedDictYapı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.
ncoghlan

Bu kodu doğru bir şekilde test eden var mı? Başvurumda çalışmasını sağlayamıyorum!
TheAlse

Örnek Kullanım: order_dict = yaml.load ('' 'b: 1 a: 2' '', Loader = OrderedDictYAMLLoader) # order_dict = OrderedDict ([('b', 1), ('a', 2)]) Maalesef Gönderide yaptığım düzenleme reddedildi, bu yüzden lütfen biçimlendirme eksikliğini affedin.
Albay Panic

Bu uygulama, sıralı eşleme türlerinin yüklenmesini keser . Bunu düzeltmek için add_constructor, __init__yönteminizdeki ikinci çağrıyı kaldırabilirsiniz .
Ryan

10

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)

Yazarın aynı olup olmadığını bilmiyorum ama yodlgithub'a bir bakın.
Bay B

3

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)

Bu aslında yukarı akışa eklenmelidir.
Michael

1
Justed, değişikliklerinizle ilgili bir talepte bulundu. github.com/yaml/pyyaml/pull/12 Birleşme için umut edelim.
Michael

Gerçekten yazarın daha aktif olmasını dilerdim, son taahhüt 4 yıl önceydi. Bu değişiklik benim için bir nimettir.
Mark LeMoine

-1

İş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])
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.