Reputation: 412
I am working on reading parameters from a yaml file, and I want to use a value to produce another value. For example, I need the following parameters a and b, and b uses the value of a.
A:
a: 10
b: a * 10
B:
......
And here is how I process the file:
from ruamel.yaml import safe_load, YAMLError
with open(yaml_file, 'r', encoding="utf-8") as hp:
try:
yaml_hparams = safe_load(hp)
for _, V in yaml_hparams.items():
for k, v in V.items():
hparams[k] = v
except YAMLError:
print(YAMLError)
Obviously this gives a string of "a * 10" to b's value. How can I give a * 10, i.e. 100, to b?
Upvotes: 2
Views: 5418
Reputation: 123463
Here's a simple proof of concept to show the feasibility of using the built-in eval()
function to do it, as I suggested in one of my comments. As such, it has some limitations such as not being able to handle "forward references", but I think many of those could be overcome, if required.
import json
from ruamel.yaml import safe_load, YAMLError
yaml_file = 'test_refs.yaml'
hparams = {}
with open(yaml_file, 'r', encoding="utf-8") as hp:
try:
yaml_hparams = safe_load(hp)
for _, V in yaml_hparams.items():
for k, v in V.items():
hparams[k] = v
except YAMLError as exc:
print(exc)
print('Before:')
print(json.dumps(hparams, indent=4)) # Pretty-print result.
# Interpolate values with eval()
hparams['__builtins__'] = None # Prevents access to built-ins.
for key, value in list(hparams.items()):
if isinstance(value, str):
try:
hparams[key] = eval(value, hparams)
except (NameError, TypeError):
pass # Possible forward-reference, ignore.
del hparams['__builtins__'] # No longer needed.
print()
print('After:')
print(json.dumps(hparams, indent=4))
Upvotes: 0
Reputation: 39688
Not with YAML. YAML is a language for data serialization, it is not a programming language.
You have multiple options. One is to use a templating language like Jinja to preprocess your YAML and calculate the value. Preprocessing YAML with Jinja is done in tools like Ansible and SaltStack, so it is a rather common practice. However, be aware that Jinja does not understand the structure of YAML and you need to be careful about whitespace.
Example:
{% set val = 10 %}
A:
a: {{ val }}
b: {{ val * 10 }}
Obviously, you will need to call Jinja on the input before you load the YAML then.
Another option is to use YAML tags to tell your code how to post-process the values, and implement it there:
A:
a: &val 10
b: !prod [*val, 10]
Code would be something like
from ruamel.yaml import safe_load, SafeLoader, YAMLError
import functools
def prod_constructor(loader, node):
return functools.reduce(lambda x, y: x * y, loader.construct_sequence(node))
SafeLoader.add_constructor(u'!prod', prod_constructor)
with open(yaml_file, 'r', encoding="utf-8") as hp:
try:
yaml_hparams = safe_load(hp)
for _, V in yaml_hparams.items():
for k, v in V.items():
hparams[k] = v
except YAMLError:
print(YAMLError)
Upvotes: 1