ใน Python คุณจะโหลดการแมป YAML เป็น OrderedDicts ได้อย่างไร


128

ฉันต้องการให้โหลดของPyYAMLโหลดการแมป (และสั่งการแมป) ลงในประเภท Python 2.7+ OrderedDictแทนวานิลลาdictและรายการคู่ที่ใช้อยู่ในปัจจุบัน

วิธีที่ดีที่สุดในการทำเช่นนั้นคืออะไร?

คำตอบ:


147

อัปเดต:ใน python 3.6+ คุณอาจไม่ต้องการOrderedDictเลยเนื่องจากการปรับใช้ dict ใหม่ที่มีการใช้งานใน pypy บางครั้ง (แม้ว่าจะพิจารณารายละเอียดการใช้งาน CPython ในตอนนี้)

ปรับปรุง:ในหลาม 3.7+, ธรรมชาติแทรกการสั่งซื้อการเก็บรักษาของ Dict วัตถุได้รับการประกาศให้เป็นส่วนหนึ่งอย่างเป็นทางการของสเปคภาษา 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
@ อันธพาลห้องสมุด 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)ให้ทำงานได้ดีเท่าเทียมกันใน Python 2 & 3
Midnighter

5
ดูเหมือนว่าจะใช้คุณสมบัติที่ไม่มีเอกสารของ PyYAML ( represent_dictและDEFAULT_MAPPING_TAG) นี่เป็นเพราะเอกสารไม่สมบูรณ์หรือคุณสมบัติเหล่านี้ไม่ได้รับการสนับสนุนและอาจมีการเปลี่ยนแปลงโดยไม่ต้องแจ้งให้ทราบล่วงหน้า
aldel

3
โปรดทราบว่าdict_constructorคุณจะต้องโทรมิloader.flatten_mapping(node)ฉะนั้นคุณจะไม่สามารถโหลด<<: *...(รวมไวยากรณ์)
Anthony Sottile

@ brice-m-dempsey คุณสามารถเพิ่มตัวอย่างวิธีการใช้รหัสของคุณได้อย่างไร ดูเหมือนว่าจะไม่ทำงานในกรณีของฉัน (Python 3.7)
schaffe

53

2018 ตัวเลือก:

oyamlเป็นการแทนที่สำหรับPyYAMLซึ่งรักษาการสั่งซื้อ dict รองรับทั้ง 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 (ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้เขียนของแพ็คเกจนั้น) การรักษาลำดับของการแมปเป็นหนึ่งในสิ่งที่เพิ่มเข้ามาในเวอร์ชั่นแรก (0.1) ย้อนกลับไปในปี 2558 ไม่เพียง แต่จะรักษาลำดับของพจนานุกรมของคุณเท่านั้น แต่ยังรักษาความคิดเห็นยึดชื่อแท็กและสนับสนุน 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ที่ทำหน้าที่เหมือน dict แต่มีข้อมูลเพิ่มเติมที่เก็บไว้รอบ ๆ จนกว่าจะถูกทิ้ง (รวมถึงความคิดเห็นที่สงวนไว้!)


ค่อนข้างดีถ้าคุณมีไฟล์ YAML อยู่แล้ว แต่คุณจะใช้โครงสร้าง Python ได้อย่างไร ฉันลองใช้CommentedMapโดยตรง แต่มันใช้งานไม่ได้และOrderedDictวางได้!!omapทุกที่ซึ่งไม่ได้ใช้ง่ายมาก
โฮลท์

ฉันไม่แน่ใจว่าทำไม CommentedMap ไม่ได้ผลสำหรับคุณ คุณสามารถโพสต์คำถามด้วยรหัส (ย่อขนาด) ของคุณและแท็กมัน ruamel.yaml ได้หรือไม่? ด้วยวิธีนี้ฉันจะได้รับแจ้งและตอบ
Anthon

ขอโทษนะฉันคิดว่ามันเป็นเพราะผมพยายามที่จะบันทึกCommentedMapด้วยsafe=TrueในYAMLซึ่งไม่ได้ทำงาน (ใช้safe=Falseงาน) ฉันมีปัญหากับการCommentedMapไม่สามารถแก้ไขได้ แต่ฉันไม่สามารถทำซ้ำได้ตอนนี้ ... ฉันจะเปิดคำถามใหม่ถ้าฉันพบปัญหานี้อีกครั้ง
โฮลท์

คุณควรใช้yaml = YAML()คุณจะได้รับ parser / dumper ไปกลับและนั่นคืออนุพันธ์ของ parser / dumper ที่ปลอดภัยที่รู้เกี่ยวกับ 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

ตัวอย่างการใช้: orders_dict = yaml.load ('' 'b: 1 a: 2' '', Loader = OrderedDictYAMLLoader) # orders_dict = OrderedDict ([('b', 1), ('a', 2)] การแก้ไขโพสต์ของฉันถูกปฏิเสธดังนั้นโปรดแก้ตัวว่าไม่มีการจัดรูปแบบ
พันเอก Panic

นี้แบ่งการดำเนินงานในการโหลดของประเภทการทำแผนที่ได้รับคำสั่ง ในการแก้ไขปัญหานี้คุณสามารถลบสายที่สองไปยังวิธีการ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)

ฉันไม่ทราบว่าเป็นผู้แต่งเดียวกันหรือไม่ แต่ลองดูyodlที่ gitHub
นาย B

3

ในการติดตั้ง For PyYaml ของฉันสำหรับ Python 2.7 ฉันได้อัปเดต __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

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