无法从 PyYAML 调用的构造函数中的参数构造对象

Tox*_*kes 5 python yaml pyyaml python-3.x

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

---
!Frog
    name: tree frog
    colour: green
    friends:
        - !Frog
          name: motorbike frog
        - !Frog
          name: blue arrow frog
Run Code Online (Sandbox Code Playgroud)

以及一个使用 PyYAML 根据文件创建对象的 python 程序:

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)
Run Code Online (Sandbox Code Playgroud)

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

tree frog's friends: {}
motorbike frog's friends: {}
blue arrow frog's friends: {}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,self.friends对于所有三只青蛙来说,字典都是空的,但是树蛙应该有两个朋友。如果我简单地创建self.friends = friends,它就会按预期工作:self.friends是青蛙朋友的列表。我究竟做错了什么?

Ant*_*hon 3

如果你这样做,事情就会起作用,self.friends = friends这并不奇怪。您将一个最初为空的列表分配给self.friends,该列表随后由 YAML 解析器附加到该列表中。

如果您希望在构造 之前填充该列表Frog(),则必须提供 的deep=True参数construct_mapping(),这样做将确保首先创建底层非标量构造以及标量构造。

def frogConstructor(loader, node):
    fields = loader.construct_mapping(node, deep=True)
    return Frog(**fields)
Run Code Online (Sandbox Code Playgroud)

然而,您的代码还存在一些问题(但它们都不妨碍上述功能):

  • 只有一个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)
Run Code Online (Sandbox Code Playgroud)

这样你就可以解析这个frog.yaml

!Frog &tree_frog
    name: tree frog
    colour: green
    friends:
        - !Frog
          name: motorbike frog
          friends:
            - *tree_frog
        - !Frog
          name: blue arrow frog
Run Code Online (Sandbox Code Playgroud)

得到输出:

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: {}
Run Code Online (Sandbox Code Playgroud)