YAML parsing to Objects (PyYAML Python3)

2019-05-06 00:51发布

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?

标签: python pyyaml
2条回答
神经病院院长
2楼-- · 2019-05-06 01:14

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
查看更多
祖国的老花朵
3楼-- · 2019-05-06 01:29

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
查看更多
登录 后发表回答