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 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
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