簡體   English   中英

如何在自定義 PyYAML 構造函數中處理遞歸?

[英]How do I handle recursion in a custom PyYAML constructor?

PyYAML 可以處理常規 Python 對象中的循環圖。 例如:

片段 #1。

class Node: pass
a = Node()
b = Node()
a.child = b
b.child = a
# We now have the cycle a->b->a
serialized_object  = yaml.dump(a)
object = yaml.load(serialized_object)

這段代碼成功了,很明顯有一些機制可以在加載序列化對象時防止無限遞歸。 當我編寫自己的 YAML 構造函數時如何利用它?

例如,假設Node是一個具有瞬態字段foobar以及非瞬態字段child 只有child應該將其放入 yaml 文檔中。 我希望這樣做:

片段#2。

def representer(dumper, node):
  return dumper.represent_mapping("!node", {"child": node.child})

def constructor(loader, data):
  result = Node()
  mapping = loader.construct_mapping(data)
  result.child = mapping["child"]
  return result

yaml.add_representer(Node, representer)
yaml.add_constructor("!node", constructor)

# Retry object cycle a->b->a from earlier code snippet
serialized_object  = yaml.dump(a)
print serialized_object
object = yaml.load(serialized_object)

但它失敗了:

&id001 !node
child: !node
  child: *id001

yaml.constructor.ConstructorError: found unconstructable recursive node:
  in "<string>", line 1, column 1:
    &id001 !node

我明白為什么了。 我的構造函數不是為遞歸而構建的。 它需要在完成構造父對象之前返回子對象,並且當子對象和父對象是同一個對象時會失敗。

但很明顯 PyYAML 具有解決這個問題的圖遍歷,因為 Snippet #1 有效。 也許有一次構建所有對象,第二次填充它們的字段。 我的問題是,我的自定義構造函數如何與這些機制相關聯?

這個問題的答案將是理想的。 但是如果答案是我不能用自定義構造函數來做到這一點,並且有一個不太理想的替代方案(例如將YAMLObject類混合到我的Node類中),那么這個答案也將不勝感激。

對於可能涉及遞歸(映射/字典、序列/列表、對象)的復雜類型,構造函數無法一次性創建對象。 因此,應yield在所構造的對象constructor()函數,然后更新后that¹任何值:

def constructor(loader, data):
    result = Node()
    yield result
    mapping = loader.construct_mapping(data)
    result.child = mapping["child"]

這擺脫了錯誤。

¹我不認為這在任何地方都有記錄,如果我沒有仔細查看py/constructor.py ,同時將 PyYAML 升級到ruamel.yaml ,我就不會知道如何做到這一點。 典型案例:閱讀Luke源碼

我對 PyYaml 的第一印象是它試圖將某種程度的接口/行為保持為 JSON(轉儲/加載)。

我學習並欣賞 JSON 功能,因為我很容易將 JSON 讀入動態構造的類型。 然而,我對 JSON 格式本身存在問題,尤其是缺乏對多行字符串、注釋和可讀性的支持。

使用 PyYAML 我發現將 yaml 反序列化為類型非常困難。 似乎有很多我沒有時間/興趣學習的箍要跳過。 考慮以下將 JSON 反序列化為類型的代碼:

with open(file) as filereader: json.load(filereader, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

通過使用對象加載鈎子,我可以將字典轉換為命名元組。 現在 pyyaml 非常擅長將 yaml 轉換為字典。 我最終應用了這個 hack,我從 yamlfile -> 字典 -> json 字符串 -> 對象,如下所示:

json.loads(json.dumps(yaml.load(filereader)), object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

這一行通過中間 json 轉換將 yaml 文件讀入類型化對象。 在我的情況下,這是一個值得的黑客,因為替代方案要復雜得多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM