Load YAML as nested objects instead of dictionary in Python

前端 未结 3 775
没有蜡笔的小新
没有蜡笔的小新 2020-12-07 04:09

I have a configuration file in YAML that is currently loaded as a dictionary using yaml.safe_load. For convenience in writing my code, I\'d prefer to load it as a set of nes

3条回答
  •  攒了一身酷
    2020-12-07 04:32

    This can be done, relatively easily, and without changing the input file.

    Since the dict PyYAML uses is hard-coded and cannot be patched, you not only have to provide a dict-like class that behaves as you want, you also have to go through the hoops to make PyYAML use that class. I.e. change the SafeConstructor that would normally construct a dict to use that new class, incorporate that in a new Loader and use PyYAML's load to use that Loader:

    import sys
    import yaml
    
    from yaml.loader import Reader, Scanner, Parser, Composer, SafeConstructor, Resolver
    
    class MyDict(dict):
       def __getattr__(self, name):
           return self[name]
    
    class MySafeConstructor(SafeConstructor):
       def construct_yaml_map(self, node):
           data = MyDict()
           yield data
           value = self.construct_mapping(node)
           data.update(value)
    
    MySafeConstructor.add_constructor(
      u'tag:yaml.org,2002:map', MySafeConstructor.construct_yaml_map)
    
    
    class MySafeLoader(Reader, Scanner, Parser, Composer, MySafeConstructor, Resolver):
        def __init__(self, stream):
            Reader.__init__(self, stream)
            Scanner.__init__(self)
            Parser.__init__(self)
            Composer.__init__(self)
            MySafeConstructor.__init__(self)
            Resolver.__init__(self)
    
    
    yaml_str = """\
    a: 1
    b:
    - q: "foo"
      r: 99
      s: 98
    - x: "bar"
      y: 97
      z: 96
    c:
      d: 7
      e: 8
      f: [9,10,11]
    """
    
    mydict = yaml.load(yaml_str, Loader=MySafeLoader)
    
    print(mydict.b[0].r)
    

    which gives:

    99
    

    If you need to be able to handle YAML1.2 you should use ruamel.yaml (disclaimer: I am the author of that package) which makes the above slightly simpler

    import ruamel.yaml
    
    # same definitions for yaml_str, MyDict
    
    class MySafeConstructor(ruamel.yaml.constructor.SafeConstructor):
       def construct_yaml_map(self, node):
           data = MyDict()
           yield data
           value = self.construct_mapping(node)
           data.update(value)
    
    MySafeConstructor.add_constructor(
      u'tag:yaml.org,2002:map', MySafeConstructor.construct_yaml_map)
    
    
    yaml = ruamel.yaml.YAML(typ='safe')
    yaml.Constructor = MySafeConstructor
    mydict = yaml.load(yaml_str)
    
    print(mydict.b[0].r)
    

    which also gives:

    99
    

    (and if your real input is large, should load your data noticably faster)

提交回复
热议问题