Pythonでは、YAMLマッピングをOrderedDictsとしてどのようにロードできますか?


回答:


147

更新: Python 3.6以降では、pypyでしばらく使用されてきOrderedDict新しいdict実装のため、おそらくまったく必要ありません(現時点ではCPython実装の詳細と見なされています)。

更新: Python 3.7以降では、dictオブジェクトの挿入順序の保存の性質がPython言語仕様の正式な部分であると宣言されています。Python3.7 の新機能を参照してください。

私は@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)

3
+1-本当にありがとうございます
Nobilis 2014年

2
この実装はところで、YAMLマージタグを破る
ランディ

1
@ランディありがとう。以前はそのシナリオで実行していませんでしたが、今度はこれを処理するための修正を追加しました(私はそう思います)。
コールドフィックス2014

9
@ArneBabenhauserheide PyPIが十分にアップストリームかどうかはわかりませんが、そうだと思われる場合はruamel.yaml(私はその作成者です)を確認してください。
Anthon

1
@Anthon ruamel.yamlライブラリは非常にうまく機能します。それをありがとう。
Jan Vlcinsky、2015年

56

yamlモジュールを使用すると、Pythonオブジェクトをテキストに変換するカスタム 'representers'とプロセスを逆にする 'constructors'を指定できます。

_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
この答えの説明はありますか?
Shuman

1
あるいは、Python 2と3でも同じように機能from six import iteritemsするiteritems(data)ように変更します
Midnighter

5
これは、PyYAMLの文書化されていない機能を使用しているようです(represent_dictおよびDEFAULT_MAPPING_TAG)。これは、ドキュメントが不完全であるか、これらの機能がサポートされておらず、予告なく変更される可能性があるためですか?
aldel 2017

3
注のためにということdict_constructorを呼び出す必要がありますloader.flatten_mapping(node)か、ロードすることができません<<: *...(マージ構文)
アンソニー・Sottile

@ brice-m-dempseyに、コードの使用方法の例を追加できますか?それは私の場合には動作していないよう(Pythonの3.7)
schaffe

53

2018オプション:

oyamldictの順序を保持するPyYAMLのドロップイン置換です。Python 2とPython 3の両方がサポートされています。だけでpip install oyaml、以下に示すようにインポートします。

import oyaml as yaml

ダンプ/ロード時に、ねじ込まれたマッピングに悩まされることはもうありません。

注:私はoyamlの作成者です。


1
これありがとう!何らかの理由で、Python 3.8を使用した場合でも、PyYamlで順序が尊重されませんでした。oyamlはすぐにこれを解決してくれました。
John Smithオプション

26

2015(以降)オプション:

ruamel.yamlは、PyYAMLの代わりのドロップです(免責事項:私はそのパッケージの作成者です)。マッピングの順序を保持することは、2015年に最初のバージョン(0.1)で追加されたものの1つです。辞書の順序を保持するだけでなく、コメント、アンカー名、タグも保持し、YAML 1.2をサポートします仕様(2009年リリース)

仕様では順序は保証されていませんが、もちろんYAMLファイルには順序があり、適切なパーサーはそれを保持し、順序を維持するオブジェクトを透過的に生成できます。適切なパーサー、ローダー、ダンパーを選択するだけです¹:

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)

あなたに与えるでしょう:

3: abc
conf:
  10: klm
  3: jig       # h is missing
more:
- what
- else

dataCommentedMapdictのように機能するタイプですが、ダンプされるまで保持される追加情報があります(保存されたコメントを含む!)


YAMLファイルが既にある場合は、これは非常に便利ですが、Python構造を使用してどのように行うのですか?私はCommentedMap直接使用してみましたが、機能せず、どこにでもOrderedDict置くことができ、!!omapユーザーフレンドリーではありません。
Holt

CommentedMapが機能しなかった理由がわかりません。(最小化された)コードを使用して質問を投稿し、ruamel.yamlにタグ付けできますか?そのように私は通知され、答えられます。
Anthon

申し訳ありませんが、CommentedMapwithをsafe=Trueで保存しようとしたため、機能しYAMLませんでした(safe=Falseworks を使用)。またCommentedMap、変更できないという問題もありましたが、現在は再現できません...この問題が再び発生した場合は、新しい質問を開きます。
Holt

を使用する必要がyaml = YAML()あります。往復のパーサー/ダンパーを取得します。これは、CommentedMap / Seqなどについて知っている安全なパーサー/ダンパーの派生物です
Anthon

14

:次の回答に基づいて、CLoaderとCDumpersも実装するライブラリがあります。Phynix/ yamlloader

これが最善の方法であるかどうかは疑問ですが、これが私が思いついた方法であり、うまくいきます。要旨としてもご利用いただけます。

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エラーメッセージに属性を含めたい場合、中央の構築ループを単純化する明白な方法はありません。OrderedDictコンストラクターが反復可能なキーと値のペアを受け入れるという事実を利用しようとすると、エラーメッセージを生成するときにその詳細にアクセスできなくなります。
ncoghlan '26

誰かがこのコードを適切にテストしましたか?アプリケーションで機能させることができません!
theAlse

使用例:ordered_dict = yaml.load( '' 'b:1 a:2' ''、Loader = OrderedDictYAMLLoader)#ordered_dict = OrderedDict([( 'b'、1)、( 'a'、2)])残念ながら投稿の編集が拒否されたので、書式が不足していることをお許しください。
大佐パニック

この実装は、順序付けられたマッピングタイプのロードを中断します。これを修正するにadd_constructorは、__init__メソッドの2番目の呼び出しを削除します。
ライアン

10

更新:ライブラリはyamlloaderyamlordereddictloaderに基づいています)のために廃止されました

私はちょうどPythonライブラリを見つけました(この質問への回答に基づいて作成され https://pypi.python.org/pypi/yamlordereddictloader/0.1.1)を使用方法は非常に簡単です。

import yaml
import yamlordereddictloader

datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)

tisが同じ作者かどうかはわかりませんが、yodlgithubで確認してください。
B氏

3

Python 2.7のFor PyYamlインストールで、__ 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)

これは実際には上流に追加する必要があります。
マイケル

1
Justedが変更をプルリクエストに提出しました。github.com/yaml/pyyaml/pull/12マージを期待しましょう。
Michael

作者がもっと活動的になればいいのにと思いますが、最後のコミットは4年前です。この変化は私にとって天の恵みになるでしょう。
Mark LeMoine、2015

-1

これは、マップ内の重複した最上位キーをチェックする簡単なソリューションです。

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])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.