Han
Han

Reputation: 735

How can I evaluate algebraic expressions defined in YAML?

I have the following two separate YAML files.

val.yaml

x: 2
y: 3
z: 5

expr.yaml

result: (x + y) * z

parse.py

import yaml

with open('val.yaml', 'r') as stream:
    val = yaml.load(stream)

with open('expr.yaml', 'r') as stream:
    expr = yaml.load(stream)

....

# this should be (2 + 3) * 5 = 25
print expr['result']

How can I evaluate expressions defined in expr.yaml by referencing variables defined in val.yaml?

Upvotes: 2

Views: 6192

Answers (2)

Anthon
Anthon

Reputation: 76792

You can most easily do this with sympy:

from __future__ import print_function

import ruamel.yaml
from sympy.parsing.sympy_parser import parse_expr

yaml = ruamel.yaml.YAML(typ='safe')
with open('var.yaml') as stream:
    vars = yaml.load(stream)
with open('expr.yaml') as stream:
    expr = yaml.load(stream)

for k in expr:
    l = parse_expr(expr[k], vars)
    print('{}: {}'.format(k, l))

which prints (using Python 2 or 3):

result: 25
  • You should really start using print as a function, even if you are stuck with Python 2.7, hence the from __future__ import. Python 2.7 with its print statement is planned to be end-of-life in two years, so use such forward compatibility imports and get accustomed to using them.
  • you should not be using PyYAML's yaml.load() as it can be unsafe, certainly if someone else edits your input files. Either use yaml.safe_load() or use the above (which loads faster as well).

Upvotes: 2

Matteo Ragni
Matteo Ragni

Reputation: 2954

This is a way of doing it, even if I strongly discourage this approach in a production situation (eval should be avoided, input should be sanitized, etc...). The idea is to perform s substitution, iterating over the variables in the yaml, under the hypothesis that the expression yaml (in result) follows the same naming convention. Here a sample:

#!/usr/bin/env python

import yaml

VARS_YAML = """
x: 2
y: 3
z: 5
"""

EXPR_YAML = """
result: (x + y) * z
"""

vars = yaml.load(VARS_YAML)
expr = yaml.load(EXPR_YAML)

for key, value in vars.items(): # for python2: vars.iteritems()
  expr['result'] = expr['result'].replace(key, str(value))

r = eval(expr['result'])
print(r) # for python2: print r
# => 25

This code is for python-3 (the only version I have available at the moment), but it is simple to move it to python-2 (that I think is your version). If you prefer to load the yaml from a file:

with open('var.yaml', 'r') as var_file:
  vars = yaml.load(var_file)

is more than enough.

Upvotes: 0

Related Questions