ฉันต้องการให้โหลดของPyYAMLโหลดการแมป (และสั่งการแมป) ลงในประเภท Python 2.7+ OrderedDictแทนวานิลลาdict
และรายการคู่ที่ใช้อยู่ในปัจจุบัน
วิธีที่ดีที่สุดในการทำเช่นนั้นคืออะไร?
ฉันต้องการให้โหลดของPyYAMLโหลดการแมป (และสั่งการแมป) ลงในประเภท Python 2.7+ OrderedDictแทนวานิลลาdict
และรายการคู่ที่ใช้อยู่ในปัจจุบัน
วิธีที่ดีที่สุดในการทำเช่นนั้นคืออะไร?
คำตอบ:
อัปเดต:ใน 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)
โมดูล 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)
from six import iteritems
จากนั้นเปลี่ยนเป็นเพื่อiteritems(data)
ให้ทำงานได้ดีเท่าเทียมกันใน Python 2 & 3
represent_dict
และDEFAULT_MAPPING_TAG
) นี่เป็นเพราะเอกสารไม่สมบูรณ์หรือคุณสมบัติเหล่านี้ไม่ได้รับการสนับสนุนและอาจมีการเปลี่ยนแปลงโดยไม่ต้องแจ้งให้ทราบล่วงหน้า
dict_constructor
คุณจะต้องโทรมิloader.flatten_mapping(node)
ฉะนั้นคุณจะไม่สามารถโหลด<<: *...
(รวมไวยากรณ์)
oyaml
เป็นการแทนที่สำหรับPyYAMLซึ่งรักษาการสั่งซื้อ dict รองรับทั้ง Python 2 และ Python 3 เพียงpip install oyaml
และนำเข้าตามที่แสดงด้านล่าง:
import oyaml as yaml
คุณจะไม่ถูกรบกวนจากการแมปแบบเมาแล้วทำการทิ้ง / โหลด
หมายเหตุ:ฉันเป็นผู้เขียน oyaml
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 แต่มีข้อมูลเพิ่มเติมที่เก็บไว้รอบ ๆ จนกว่าจะถูกทิ้ง (รวมถึงความคิดเห็นที่สงวนไว้!)
CommentedMap
โดยตรง แต่มันใช้งานไม่ได้และOrderedDict
วางได้!!omap
ทุกที่ซึ่งไม่ได้ใช้ง่ายมาก
CommentedMap
ด้วยsafe=True
ในYAML
ซึ่งไม่ได้ทำงาน (ใช้safe=False
งาน) ฉันมีปัญหากับการCommentedMap
ไม่สามารถแก้ไขได้ แต่ฉันไม่สามารถทำซ้ำได้ตอนนี้ ... ฉันจะเปิดคำถามใหม่ถ้าฉันพบปัญหานี้อีกครั้ง
yaml = YAML()
คุณจะได้รับ parser / dumper ไปกลับและนั่นคืออนุพันธ์ของ parser / dumper ที่ปลอดภัยที่รู้เกี่ยวกับ CommentedMap / Seq ฯลฯ
หมายเหตุ : มีไลบรารีตามคำตอบต่อไปนี้ซึ่งใช้กับ 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
สร้างจะยอมรับคู่คีย์และค่าที่ซ้ำได้คุณจะสูญเสียการเข้าถึงรายละเอียดนั้นเมื่อสร้างข้อความแสดงข้อผิดพลาด
add_constructor
ของคุณ __init__
อัปเดต : ห้องสมุดเลิกใช้งานเนื่องจาก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
ในการติดตั้ง 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)
นี่เป็นวิธีง่ายๆที่จะตรวจสอบคีย์ระดับบนสุดซ้ำซ้อนในแผนที่ของคุณ
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])