YAML parsing to Objects (PyYAML Python3)

瘦欲@ 提交于 2019-12-22 08:03:59

问题


I've got the following code:

class Settings:
    def __init__(self, annual_volatility_target):
        self.annual_volatility_target = annual_volatility_target
        self.daily = annual_volatility_target/np.sqrt(252)

def yaml_load(name):
    with open('yaml/' + str(name) + '.yaml', 'r') as ymlfile:
        return yaml.load(ymlfile)

settings = yaml_load("settings")

With the following YAML:

!!python/object:v.Settings
annual_volatility_target: 0.25

The problem is, when I load settings, settings.daily isn't set. settings.annual_volatility_target is, regardless of whether I say so in __init__ or not.

If I instantiate a Settings object manually (i.e. not using PyYAML), it works fine.

What am I doing wrong?


回答1:


Python objects in PyYAML are constructed in a two step process. First __new__ is called (in Constructor.make_python_instance()) and then the attributes are set (in Constructor.set_python_instance_state()). This two step process is needed because YAML supports references to objects and if that object is (indirectly) self referential it cannot be constructed in one go, because the parameters on which it depends (which include itself) are not available yet.

You can solve this in two ways. You can define __setstate__() for Settings and that will be called with a dict and call this from __init__() as well:

import yaml

yaml_str = """\
!!python/object:try.Settings
annual_volatility_target: 0.25
"""

class Settings:
    def __init__(self, annual_volatility_target):
        self.__setstate__({annual_volatility_target: annual_volatility_target})

    def __setstate__(self, kw):
        self.annual_volatility_target = kw.get('annual_volatility_target')
        self.daily = self.annual_volatility_target/np.sqrt(252)

    def __repr__(self):
        return "Setting({}, {})".format(self.annual_volatility_target, self.daily)

settings = yaml.load(yaml_str)

print(settings)

Another, and more general (non PyYAML) solution is to create the daily value on first access:

class Settings:
    def __init__(self, annual_volatility_target):
        self.annual_volatility_target = annual_volatility_target

    @property:
    def daily(self):
         return annual_volatility_target/np.sqrt(252)

If you access daily often, then you should cache it in e.g. self._daily the first time you calculate the value:

class Settings:
    def __init__(self, annual_volatility_target):
        self.annual_volatility_target = annual_volatility_target
        self._daily = None

    @property:
    def daily(self):
         if self._daily is None:  
             self._daily = annual_volatility_target/np.sqrt(252)
         return self._daily



回答2:


One possibility is to write a constructor for Settings:

import yaml
import numpy as np

class Settings:
    def __init__(self, annual_volatility_target):
        self.annual_volatility_target = annual_volatility_target
        self.daily = annual_volatility_target/np.sqrt(252)

def yaml_load(name):
    with open(str(name) + '.yaml', 'r') as ymlfile:
        return yaml.load(ymlfile)

def settings_constructor(loader, node):
    fields = loader.construct_mapping(node)
    return Settings(fields['annual_volatility_target'])

yaml.add_constructor('!v.Settings', settings_constructor)

settings = yaml_load("settings")

print(settings.annual_volatility_target)
print(settings.daily)

I have to use a modified yaml file (I was unable to make it work with annotation !!python/object:v.Settings):

!v.Settings
annual_volatility_target: 0.25


来源:https://stackoverflow.com/questions/37259748/yaml-parsing-to-objects-pyyaml-python3

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!