code

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

starcafe 2023. 7. 17. 21:17
반응형

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

저는 PyYAML의 로더가 바닐라 대신 Python 2.7+ OrderedDict 유형으로 매핑(및 정렬된 매핑)을 로드하도록 하고 싶습니다.dict현재 사용 중인 쌍 목록입니다.

그렇게 하는 가장 좋은 방법은 무엇입니까?

Python >= 3.6

python 3.6+에서는 특별한 사전 유형 없이 딕트 로딩 순서가 기본적으로 유지되는 것 같습니다.그러나 기본 Dumper는 사전을 키별로 정렬합니다.시작pyyaml 5.1당신은 이것을 패스로 끌 수 있습니다.sort_keys=False:

a = dict(zip("unsorted", "unsorted"))
s = yaml.safe_dump(a, sort_keys=False)
b = yaml.safe_load(s)

assert list(a.keys()) == list(b.keys())  # True

이것은 한동안 pypy에서 사용되었던 새로운 dict 구현으로 인해 작동할 수 있습니다.CPython 3.6에서는 여전히 구현 세부 사항으로 간주되지만, 3.7+에서는 "dict의 삽입 순서 보존 특성이 파이썬 언어 사양의 공식적인 일부로 선언되었습니다."를 참조하십시오.

PyYAML 측에서는 아직 문서화되지 않았기 때문에 안전에 중요한 응용 프로그램에 의존해서는 안 됩니다.

원본 답변(알려진 모든 버전과 호환 가능)

저는 단순성 때문에 @James의 솔루션을 좋아합니다.그러나 기본 글로벌을 변경합니다.yaml.Loader문제가 되는 부작용으로 이어질 수 있는 수업.특히 라이브러리 코드를 작성할 때는 좋지 않은 생각입니다.또한 직접적으로 작동하지 않습니다.yaml.safe_load().

다행히도 솔루션은 큰 노력 없이 개선될 수 있습니다.

import yaml
from collections import OrderedDict

def ordered_load(stream, Loader=yaml.SafeLoader, 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.SafeDumper, **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)

각 경우 사용자 정의 하위 클래스를 전역으로 설정하여 각 호출에서 다시 생성할 필요가 없습니다.

2018년 옵션:

oyaml 명령 순서를 보존하는 PyYAML의 드롭인 대체 제품입니다.Python 2 및 Python 3이 모두 지원됩니다.그저.pip install oyaml및 아래와 같이 가져오기:

import oyaml as yaml

더 이상 덤프/로드 시 잘못된 매핑으로 인해 짜증이 나지 않습니다.

참고: 저는 오얌의 저자입니다.

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)

2015년 이후 옵션:

ruamel.yaml은 PyYAML의 대체품의 감소입니다. (거부자:저는 그 패키지의 작성자입니다.).매핑 순서를 보존하는 것은 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

data이 형의입니다.CommentedMap이것은 딕트처럼 기능하지만, 덤프될 때까지 유지되는 추가 정보를 가지고 있습니다(보존된 코멘트 포함)!

참고: 다음 답변을 기반으로 CLoader 및 CDumper를 구현하는 라이브러리가 있습니다.피닉스/야믈로더

저는 이것이 최선의 방법인지 매우 의심스럽지만, 이것이 제가 생각해낸 방법이고, 효과가 있습니다.요지로도 사용할 수 있습니다.

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

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

저는 방금 파이썬 라이브러리(https://pypi.python.org/pypi/yamlordereddictloader/0.1.1) 를 찾았습니다. 이 라이브러리는 이 질문에 대한 답변을 기반으로 만들어졌으며 사용하기 매우 간단합니다.

import yaml
import yamlordereddictloader

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

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)

지도에서 중복된 최상위 키도 확인할 수 있는 간단한 솔루션이 있습니다.

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])

언급URL : https://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts

반응형