파이썬에서 어떻게 YAML 매핑을 OrderedDicts로로드 할 수 있습니까?


답변:


147

업데이트 : python 3.6 이상에서는 한동안 pypy에서 사용 된 새로운 dict 구현OrderedDict 으로 인해 전혀 필요하지 않을 것입니다 (지금은 CPython 구현 세부 사항을 고려했지만).

업데이트 : python 3.7 이상에서 dict 객체의 삽입 순서 보존 특성은 Python 언어 사양의 공식 부분으로 선언되었습니다 ( Python 3.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

2
이 구현은 YAML 병합 태그 인 BTW
Randy

1
@ 랜디 감사합니다. 나는 그 시나리오에서 전에 실행하지 않았지만 이제는 이것을 처리하기위한 수정 사항을 추가했습니다 (바람직합니다).
coldfix

9
@ArneBabenhauserheide PyPI가 충분히 업스트림인지 확실하지 않지만 ruamel.yaml (저는 저자 임)을 살펴보십시오 .
Anthon

1
@Anthon ruamel.yaml 라이브러리는 매우 잘 작동합니다. 고마워
Jan Vlcinsky

56

yaml 모듈을 사용하면 사용자 정의 '표현 자'를 지정하여 Python 객체를 텍스트로 변환하고 '생성자'를 사용하여 프로세스를 되돌릴 수 있습니다.

_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
또는 더 나은 from six import iteritems다음 iteritems(data)파이썬 2 & 3에서도 동일하게 작동하도록 변경하십시오.
Midnighter

5
이것은 PyYAML ( represent_dictDEFAULT_MAPPING_TAG) 의 문서화되지 않은 기능을 사용하는 것 같습니다 . 문서가 불완전하거나 이러한 기능이 지원되지 않고 예고없이 변경 될 수 있기 때문입니까?
aldel

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
감사합니다! 어떤 이유로 파이썬 3.8에서도 PyYaml에서는 순서가 존중되지 않았습니다. oyaml이이 문제를 즉시 해결했습니다.
John Smith 옵션

26

2015 이상 옵션 :

ruamel.yamlPyYAML의 대체품입니다 (면책 조항 : 해당 패키지의 저자입니다). 매핑 순서를 유지하는 것은 2015 년에 첫 번째 버전 (0.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 파일이 이미 있다면 꽤 좋지만 파이썬 구조를 사용하여 어떻게합니까? 나는 사용하여 시도 CommentedMap직접하지만,하지 작업 및 수행 OrderedDict!!omap매우 사용하기 쉬운되지 않습니다 사방을.
홀트

CommentedMap이 왜 작동하지 않는지 잘 모르겠습니다. (최소화 된) 코드로 질문을 게시하고 ruamel.yaml에 태그를 지정할 수 있습니까? 그렇게하면 나는 통보를 받고 대답 할 것이다.
Anthon

미안 해요, 난을 저장하려고 있기 때문에 생각 CommentedMapsafe=True에서 YAML(사용 작동하지 않았다 어떤 safe=False일을). 또한 CommentedMap수정할 수없는 문제가 있었지만 지금 재현 할 수 없습니다.이 문제가 다시 발생하면 새로운 질문을 드리겠습니다.
홀트

를 사용해야합니다. 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

누구 든지이 코드를 올바르게 테스트 했습니까? 응용 프로그램에서 작동시킬 수 없습니다!
theAlse

사용법 예 : ordered_dict = yaml.load ( '' 'b : 1 a : 2' '', Loader = OrderedDictYAMLLoader) # ordered_dict = OrderedDict ([( 'b', 1), ( 'a', 2)]) 불행히도 게시물 수정이 거부되었으므로 형식이 잘못되었습니다.
대령 패닉

이 구현은로드를 중단합니다 정렬 된 매핑 유형의 합니다 . 이 문제를 해결하려면 메서드 add_constructor에서 두 번째 호출을 제거하면 __init__됩니다.
Ryan

10

업데이트 : 라이브러리는 yamlloader 를 위해 더 이상 사용되지 않습니다 ( yamlordereddictloader 를 기반으로 함)

방금 이 질문에 대한 답변을 기반으로 작성되었으며 사용하기 매우 쉬운 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에서 확인하십시오 .
Mr. 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)

이것은 실제로 업스트림에 추가되어야합니다.
Michael

1
방금 변경 사항에 대한 풀 요청을 제출했습니다. github.com/yaml/pyyaml/pull/12 병합을 희망합시다.
Michael

저자가 더 활동 적이기를 바랍니다. 지난 커밋은 4 년 전이었습니다. 이 변화는 나에게 신의 선물이 될 것입니다.
Mark LeMoine

-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.