Trong Python, làm thế nào bạn có thể tải ánh xạ YAML dưới dạng OrderedDicts?


128

Tôi muốn tải trình tải của PyYAML để tải ánh xạ (và ánh xạ đã ra lệnh) vào Python 2.7+ OrderedDict , thay vì vanilla dictvà danh sách các cặp mà nó hiện đang sử dụng.

Cách tốt nhất để làm điều đó là gì?

Câu trả lời:


147

Cập nhật: Trong python 3.6+ có thể bạn hoàn toàn không cần OrderedDictdo triển khai chính tả mới đã được sử dụng trong pypy một thời gian (mặc dù hiện tại được coi là chi tiết triển khai CPython).

Cập nhật: Trong python 3.7+, bản chất bảo quản thứ tự chèn của các đối tượng dict đã được khai báo là một phần chính thức của đặc tả ngôn ngữ Python , xem Có gì mới trong Python 3.7 .

Tôi thích giải pháp của @James vì sự đơn giản của nó. Tuy nhiên, nó thay đổi yaml.Loaderlớp toàn cầu mặc định , có thể dẫn đến các tác dụng phụ rắc rối. Đặc biệt, khi viết mã thư viện đây là một ý tưởng tồi. Ngoài ra, nó không trực tiếp làm việc với yaml.safe_load().

May mắn thay, giải pháp có thể được cải thiện mà không cần nỗ lực nhiều:

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)

Để tuần tự hóa, tôi không biết khái quát hóa rõ ràng, nhưng ít nhất điều này không nên có bất kỳ tác dụng phụ nào:

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 - cảm ơn bạn rất nhiều vì điều này, nó đã cứu tôi rất nhiều rắc rối.
Nobilis

2
Việc triển khai này phá vỡ các thẻ hợp nhất YAML, BTW
Randy

1
@Randy Cảm ơn. Tôi đã không chạy trong kịch bản đó trước đây, nhưng bây giờ tôi đã thêm một bản sửa lỗi để xử lý vấn đề này (tôi hy vọng).
Coldfix

9
@ArneBabenhauserheide Tôi không chắc liệu PyPI có đủ ngược dòng hay không, nhưng hãy xem ruamel.yaml (Tôi là tác giả của điều đó) nếu bạn nghĩ vậy.
Anthon

1
@Anthon Thư viện ruamel.yaml của bạn hoạt động rất tốt. Cảm ơn vì điều đó.
Jan Vlcinsky

56

Mô-đun yaml cho phép bạn chỉ định 'đại diện' tùy chỉnh để chuyển đổi các đối tượng Python thành văn bản và 'hàm tạo' để đảo ngược quy trình.

_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
bất kỳ lời giải thích cho câu trả lời này?
Shuman

1
Hoặc thậm chí tốt hơn from six import iteritemsvà sau đó thay đổi nó để iteritems(data)nó hoạt động tốt như nhau trong Python 2 & 3.
Midnighter

5
Điều này dường như đang sử dụng các tính năng không có giấy tờ của PyYAML ( represent_dictDEFAULT_MAPPING_TAG). Đây có phải là do tài liệu không đầy đủ, hoặc các tính năng này không được hỗ trợ và có thể thay đổi mà không cần thông báo trước?
aldel

3
Lưu ý rằng dict_constructorbạn sẽ cần gọi loader.flatten_mapping(node)hoặc bạn sẽ không thể tải <<: *...(cú pháp hợp nhất)
Anthony Sottile

@ brice-m-dempsey bạn có thể thêm bất kỳ ví dụ nào về cách sử dụng mã của bạn không? Nó dường như không hoạt động trong trường hợp của tôi (Python 3.7)
schaffe

53

Tùy chọn 2018:

oyamllà một sự thay thế thả xuống cho PyYAML để duy trì trật tự chính tả. Cả Python 2 và Python 3 đều được hỗ trợ. Chỉ pip install oyamlvà nhập như hình dưới đây:

import oyaml as yaml

Bạn sẽ không còn cảm thấy khó chịu bởi các ánh xạ bị vặn khi đổ / tải.

Lưu ý: Tôi là tác giả của oyaml.


1
Cảm ơn vì điều này! Vì một số lý do, ngay cả với Python 3.8, thứ tự không được tôn trọng với PyYaml. oyaml đã giải quyết điều này cho tôi ngay lập tức.
John Smith Tùy chọn

26

Tùy chọn 2015 (và sau này):

ruamel.yaml là sự thay thế cho PyYAML (từ chối trách nhiệm: Tôi là tác giả của gói đó). Giữ nguyên thứ tự ánh xạ là một trong những điều được thêm vào trong phiên bản đầu tiên (0.1) vào năm 2015. Nó không chỉ giữ trật tự từ điển của bạn, mà còn giữ nguyên các bình luận, tên neo, thẻ và không hỗ trợ YAML 1.2 đặc điểm kỹ thuật (phát hành năm 2009)

Đặc tả kỹ thuật nói rằng thứ tự không được đảm bảo, nhưng tất nhiên có thứ tự trong tệp YAML và trình phân tích cú pháp thích hợp chỉ có thể giữ nó và tạo ra một đối tượng trong suốt để giữ trật tự. Bạn chỉ cần chọn đúng trình phân tích cú pháp, trình tải và dumper¹:

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)

sẽ cung cấp cho bạn:

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

datathuộc loại CommentedMapcó chức năng như một lệnh, nhưng có thêm thông tin được lưu giữ cho đến khi bị bỏ đi (bao gồm cả nhận xét được bảo tồn!)


Điều đó thật tuyệt nếu bạn đã có tệp YAML, nhưng bạn làm thế nào để sử dụng cấu trúc Python? Tôi đã thử sử dụng CommentedMaptrực tiếp nhưng nó không hoạt động và OrderedDictđặt !!omapở mọi nơi không thân thiện với người dùng.
Holt

Tôi không chắc tại sao CommentedMap không hoạt động cho bạn. Bạn có thể gửi một câu hỏi với mã (tối thiểu hóa) của bạn và gắn thẻ ruamel.yaml không? Bằng cách đó tôi sẽ được thông báo và trả lời.
Anthon

Xin lỗi, tôi nghĩ rằng đó là bởi vì tôi đã cố gắng để lưu CommentedMapvới safe=Truetrong YAML, mà không làm việc (sử dụng safe=Falsecông trình). Tôi cũng gặp vấn đề với việc CommentedMapkhông thể sửa đổi, nhưng tôi không thể sao chép nó ngay bây giờ ... Tôi sẽ mở một câu hỏi mới nếu tôi gặp lại vấn đề này.
Holt

Bạn nên sử dụng yaml = YAML(), bạn có được trình phân tích cú pháp / máy xúc lật khứ hồi và đó là dẫn xuất của trình phân tích cú pháp / máy xúc lật an toàn biết về CommentedMap / Seq, v.v.
Anthon

14

Lưu ý : có một thư viện, dựa trên câu trả lời sau, cũng thực hiện CLoader và CDumpers: Phynix / yamlloader

Tôi nghi ngờ rất nhiều rằng đây là cách tốt nhất để làm điều đó, nhưng đây là cách tôi nghĩ ra, và nó hoạt động. Cũng có sẵn như là một ý chính .

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

Nếu bạn muốn bao gồm key_node.start_markthuộc tính trong thông báo lỗi của mình, tôi không thấy bất kỳ cách rõ ràng nào để đơn giản hóa vòng lặp xây dựng trung tâm của bạn. Nếu bạn cố gắng sử dụng thực tế là hàm OrderedDicttạo sẽ chấp nhận một cặp khóa, cặp giá trị, bạn sẽ mất quyền truy cập vào chi tiết đó khi tạo thông báo lỗi.
ncoghlan

có ai đã kiểm tra mã này đúng cách chưa? Tôi không thể làm cho nó hoạt động trong ứng dụng của tôi!
Ngày

Cách sử dụng ví dụ: order_dict = yaml.load ('' 'b: 1 a: 2' '', Loader = OrderedDictYAMLLoader) # order_dict = OrderedDict ([('b', 1), ('a', 2)]) Thật không may chỉnh sửa bài viết của tôi đã bị từ chối, vì vậy xin vui lòng thiếu định dạng.
Đại tá Panic

Việc thực hiện này phá vỡ tải các loại ánh xạ được đặt hàng . Để khắc phục điều này, bạn chỉ cần xóa cuộc gọi thứ hai add_constructortrong __init__phương thức của mình .
Ryan

10

Cập nhật : thư viện không được hỗ trợ cho yamlloader (dựa trên bộ tải yamlordereddictloader)

Tôi vừa tìm thấy một thư viện Python ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) được tạo dựa trên câu trả lời cho câu hỏi này và sử dụng khá đơn giản:

import yaml
import yamlordereddictloader

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

Tôi không biết liệu đây có phải là cùng một tác giả hay không, nhưng hãy xem yodltrên github.
Ông B

3

Trong bản cài đặt For PyYaml cho Python 2.7, tôi đã cập nhật __init__.py, constructor.py và loader.py. Bây giờ hỗ trợ tùy chọn object_pairs_hook cho các lệnh tải. Dưới đây là những thay đổi tôi đã thực hiện.

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

Điều này nên được thêm vào thượng nguồn thực sự.
Michael

1
Justed nộp một yêu cầu kéo với những thay đổi của bạn. github.com/yaml/pyyaml/pull/12 Hãy hy vọng hợp nhất.
Michael

Thực sự mong tác giả tích cực hơn, lần cam kết cuối cùng là 4 năm trước. Sự thay đổi này sẽ là một ơn trời đối với tôi.
Đánh dấu LeMoine

-1

đây là một giải pháp đơn giản cũng kiểm tra các khóa cấp cao nhất trùng lặp trong bản đồ của bạn.

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])
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.