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 dict
và 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ì?
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 dict
và 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:
Cập nhật: Trong python 3.6+ có thể bạn hoàn toàn không cần OrderedDict
do 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.Loader
lớ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)
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)
from six import iteritems
và sau đó thay đổi nó để iteritems(data)
nó hoạt động tốt như nhau trong Python 2 & 3.
represent_dict
và DEFAULT_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?
dict_constructor
bạ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)
oyaml
là 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 oyaml
và 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.
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
data
thuộc loại CommentedMap
có 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!)
CommentedMap
trự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.
CommentedMap
với safe=True
trong YAML
, mà không làm việc (sử dụng safe=False
công trình). Tôi cũng gặp vấn đề với việc CommentedMap
khô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.
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.
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
key_node.start_mark
thuộ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 OrderedDict
tạ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.
add_constructor
trong __init__
phương thức của mình .
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)
yodl
trên github.
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)
đâ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])