无法从PyYAML

时间:2017-02-14 05:03:43

标签: python python-3.x yaml pyyaml

我有一个YAML文件,如下所示:

---
!Frog
    name: tree frog
    colour: green
    friends:
        - !Frog
          name: motorbike frog
        - !Frog
          name: blue arrow frog

一个python程序,它使用PyYAML根据文件创建对象:

import yaml

class Frog():
    def __init__(self, name, colour="", friends=None):
        self.name = name
        self.colour = colour
        self.friends = {}
        if friends != None:
            for f in friends:
                self.friends[f.name] = f
        print("{}'s friends: {}".format(self.name, self.friends))

# Constructor for YAML
def frogConstructor(loader, node) :
    fields = loader.construct_mapping(node)
    return Frog(**fields)

yaml.add_constructor('!Frog', frogConstructor)

f = open("frog.yaml")
loaded = yaml.load(f)

正如您在上面的代码中看到的那样,我尝试从self.friends参数创建friends字典(其中键是青蛙的名字和值)是__init__方法的实际青蛙对象。但是,上面的代码会产生以下输出:

tree frog's friends: {}
motorbike frog's friends: {}
blue arrow frog's friends: {}

正如你所看到的,self.friends字典对于所有三只青蛙都是空的,但树蛙应该有两个朋友。如果我只是制作self.friends = friends,它会按预期工作:self.friends是朋友青蛙的列表。我做错了什么?

1 个答案:

答案 0 :(得分:2)

如果你做self.friends = friends那么事情就不那么奇怪了。您将最初为空的列表分配给self.friends,该列表稍后由YAML解析器附加。

如果您希望在构建Frog()之前填写该列表,则必须为deep=True提供construct_mapping()参数,这样做会确保基础非首先创建-scalar结构以及标量结构。

def frogConstructor(loader, node):
    fields = loader.construct_mapping(node, deep=True)
    return Frog(**fields)

但是你的代码还有一些问题(但没有一个问题禁止上述功能):

  • 只有一个None,因此使用if friends is not None:if friends != None:
  • 更合适
  • yaml.load不安全,因此如果您无法完全控制输入,则可能意味着擦除光盘(或更糟)。 PyYAML并没有警告你(在我的ruamel.yaml解析器中,你必须提供不安全的Loader以防止出现警告信息。)
  • 如果tree frog足够自恋,认为自己是自己的朋友,或者如果其中一位朋友认为tree frog是朋友,那么您可能需要使用锚点和别名来表明(而不是只是在不同的Frog上使用相同的名称,这对你正在使用的简单构造函数不起作用。
  • frogConstructor,作为函数名称,不应该是驼峰大小写,请改用frog_constructor

由于上述原因,我不会使用deep=True参数,而是通过使用两阶段构造函数来寻求更安全,更完整的解决方案:

from ruamel import yaml

class Frog():
    def __init__(self, name):
        self.name = name

    def set_values(self, colour="", friends=None):
        self.colour = colour
        self.friends = {}
        if friends is not None:
            for f in friends:
                self.friends[f.name] = f
        print("{}'s friends: {}".format(self.name, self.friends))

    def __repr__(self):
        return "Frog({})".format(self.name)

# Constructor for YAML
def frog_constructor(loader, node):
    fields = loader.construct_mapping(node)
    frog = Frog(fields.pop('name'))
    yield frog
    frog.set_values(**fields)

yaml.add_constructor('!Frog', frog_constructor, yaml.SafeLoader)

f = open("frog.yaml")
loaded = yaml.safe_load(f)

你可以解析这个frog.yaml

!Frog &tree_frog
    name: tree frog
    colour: green
    friends:
        - !Frog
          name: motorbike frog
          friends:
            - *tree_frog
        - !Frog
          name: blue arrow frog

获取输出:

tree frog's friends: {'blue arrow frog': Frog(blue arrow frog), 'motorbike frog': Frog(motorbike frog)}
motorbike frog's friends: {'tree frog': Frog(tree frog)}
blue arrow frog's friends: {}