2016-07-29 61 views
1

我正致力於將幾個.yaml文件中存在的所有文本置於一個新的單個YAML文件中,該文件將包含可以翻譯成西班牙語的英文翻譯。返回所有鍵以及嵌套字典中的值

每個YAML文件都有很多嵌套文本。我想爲YAML文件中的每個值打印完整的「路徑」,即所有的鍵以及值。下面是一個.yaml文件住在myproject.section.more_information文件的例子輸入:

default: 
    heading: Here’s A Title 
    learn_more: 
     title: Title of Thing 
     url: www.url.com 
     description: description 
     opens_new_window: true 

,這裏是所需的輸出:

myproject.section.more_information.default.heading: Here’s a Title 
myproject.section.more_information.default.learn_more.title: Title of Thing 
mproject.section.more_information.default.learn_more.url: www.url.com 
myproject.section.more_information.default.learn_more.description: description 
myproject.section.more_information.default.learn_more.opens_new_window: true 

這似乎是一個很好的候選人遞歸,所以我已經看過例子this answer

但是,我想保留所有導致給定值的鍵,而不僅僅是一個值中的最後一個鍵。我目前使用PyYAML來讀取/寫入YAML。

關於如何保存每個鍵的任何提示,因爲我繼續檢查該項是否是字典,然後返回與每個值關聯的所有鍵?

回答

0

漫步嵌套字典遞歸遞歸,並將「前綴」交給「路徑」,這樣可以防止您必須對路徑段進行任何操作(如@Prune)所暗示的。

有幾件事情要記住,使這個問題有意思:

  • 因爲你使用多個文件會導致在多個文件中,你需要處理相同的路徑(至少投擲一個錯誤,否則你可能會丟失數據)。在我的例子中,我生成一個值列表。
  • 處理特殊鍵(非字符串(轉換?),空字符串,包含.的鍵)。我的例子報告這些並退出。

使用ruamel.yaml¹示例代碼:

import sys 
import glob 
import ruamel.yaml 
from ruamel.yaml.comments import CommentedMap, CommentedSeq 
from ruamel.yaml.compat import string_types, ordereddict 

class Flatten: 
    def __init__(self, base): 
     self._result = ordereddict() # key to list of tuples of (value, comment) 
     self._base = base 

    def add(self, file_name): 
     data = ruamel.yaml.round_trip_load(open(file_name)) 
     self.walk_tree(data, self._base) 

    def walk_tree(self, data, prefix=None): 
     """ 
     this is based on ruamel.yaml.scalarstring.walk_tree 
     """ 
     if prefix is None: 
      prefix = "" 
     if isinstance(data, dict): 
      for key in data: 
       full_key = self.full_key(key, prefix) 
       value = data[key] 
       if isinstance(value, (dict, list)): 
        self.walk_tree(value, full_key) 
        continue 
       # value is a scalar 
       comment_token = data.ca.items.get(key) 
       comment = comment_token[2].value if comment_token else None 
       self._result.setdefault(full_key, []).append((value, comment)) 
     elif isinstance(base, list): 
      print("don't know how to handle lists", prefix) 
      sys.exit(1) 

    def full_key(self, key, prefix): 
     """ 
     check here for valid keys 
     """ 
     if not isinstance(key, string_types): 
      print('key has to be string', repr(key), prefix) 
      sys.exit(1) 
     if '.' in key: 
      print('dot in key not allowed', repr(key), prefix) 
      sys.exit(1) 
     if key == '': 
      print('empty key not allowed', repr(key), prefix) 
      sys.exit(1) 
     return prefix + '.' + key 

    def dump(self, out): 
     res = CommentedMap() 
     for path in self._result: 
      values = self._result[path] 
      if len(values) == 1: # single value for path 
       res[path] = values[0][0] 
       if values[0][1]: 
        res.yaml_add_eol_comment(values[0][1], key=path) 
       continue 
      res[path] = seq = CommentedSeq() 
      for index, value in enumerate(values): 
       seq.append(value[0]) 
       if values[0][1]: 
        res.yaml_add_eol_comment(values[0][1], key=index) 


     ruamel.yaml.round_trip_dump(res, out) 


flatten = Flatten('myproject.section.more_information') 
for file_name in glob.glob('*.yaml'): 
    flatten.add(file_name) 
flatten.dump(sys.stdout) 

如果你有一個額外的輸入文件:

default: 
    learn_more: 
     commented: value # this value has a comment 
     description: another description 

那麼結果是:

myproject.section.more_information.default.heading: Here’s A Title 
myproject.section.more_information.default.learn_more.title: Title of Thing 
myproject.section.more_information.default.learn_more.url: www.url.com 
myproject.section.more_information.default.learn_more.description: 
- description 
- another description 
myproject.section.more_information.default.learn_more.opens_new_window: true 
myproject.section.more_information.default.learn_more.commented: value # this value has a comment 
如果

當然你輸入沒有雙重路徑,你的輸出不會有任何結果名單。

通過使用string_typesordereddictruamel.yaml使這個Python2和Python3兼容(你不指明你正在使用哪個版本)。

ordereddict保留原始的鍵排序,但這當然取決於文件的處理順序。如果你想要的路徑進行排序,只是改變dump()使用:

 for path in sorted(self._result): 

還要注意,在註釋的「註釋」字典條目被保留。


¹ ruamel.yaml是YAML 1.2解析器可以保留上往返註釋和其他數據(PyYAML確實YAML 1.1的大部分地區)。免責聲明:我是ruamel.yaml的作者

+0

對於我的輸入可能有的許多邊緣情況,這是一個非常深思熟慮的解決方案,謝謝。我特別喜歡它保留評論。 – swellactually

0

你想要做的是拼合嵌套字典。這將是一個很好的開始:Flatten nested Python dictionaries, compressing keys

事實上,我認爲如果您只是將sep參數更改爲.,那麼頂級答案中的代碼段將適用於您。

編輯:

檢查這一個工作示例基於鏈接的SO回答http://ideone.com/Sx625B

import collections 

some_dict = { 
    'default': { 
     'heading': 'Here’s A Title', 
     'learn_more': { 
      'title': 'Title of Thing', 
      'url': 'www.url.com', 
      'description': 'description', 
      'opens_new_window': 'true' 
     } 
    } 
} 

def flatten(d, parent_key='', sep='_'): 
    items = [] 
    for k, v in d.items(): 
     new_key = parent_key + sep + k if parent_key else k 
     if isinstance(v, collections.MutableMapping): 
      items.extend(flatten(v, new_key, sep=sep).items()) 
     else: 
      items.append((new_key, v)) 
    return dict(items) 

results = flatten(some_dict, parent_key='', sep='.') 
for item in results: 
    print(item + ': ' + results[item]) 

如果你想爲了,但你需要一個OrderedDict。

+0

超級,感謝發送。我看了一下,輸出看起來很棒。不幸的是我不得不離開我的電腦,但本週末我會花更多時間瀏覽它。 – swellactually

+0

儘管這個鏈接可能回答這個問題,但最好在這裏包含答案的基本部分,並提供供參考的鏈接。如果鏈接的頁面發生更改,則僅鏈接答案可能會失效,甚至在發生其他StackOverflow答案的鏈接時也會發生這種情況。 – Anthon

+1

Gotcha @Anthon,我會解決它。 – Michael

0

保留一個簡單的字符串列表,作爲每個縮進深度處的最新鍵。當您從一行進行到下一行時,只需更改列表末尾的項目即可。當你「out-dent」時,從列表中彈出最後一項。當你縮進時,追加到列表中。

然後,每個你打一個冒號的時候,相應的關鍵項是字符串列表中的串聯,是這樣的:

'.'.join(key_list) 

這是否讓你在一個體面的速度移動?

+0

智能設計。我必須離開電腦,所以現在不能嘗試,但我喜歡這種方法,謝謝! – swellactually

+0

使用字符串列表會使事情過於複雜。無論如何,您必須使用遞歸挖掘嵌套字典,因此只需將當前路徑添加到「前綴」中,並且遞歸會處理您建議手動完成的追加/彈出操作。 – Anthon

相關問題