patternpythonMinor
Replace keys in values with key->values from same dictionary
Viewed 0 times
samewithreplacekeysvaluesdictionaryfromkey
Problem
I have a dictionary whose values may contain keys that are already in the dictionary. What we are actually talking about are parameters. Sometimes a parameter is defined by a number plus another parameter, etc.
It looks something like this:
If there is another parameter in the value it is of type string, otherwise it is an
The code I wrote to solve this problem is this:
It works as far as I can tell, but it just doesn't feel right to just repeat the whole process a finite number of times and hope that all the strings have been replaced. Is there a safer way?
It looks something like this:
paramDict = {'ad': '2*dmcg/factor*(xw/factor)',
'dmcg': 2.05e-07,
'dxwn_3vna': 10.0,
'factor': 1,
'xw': '0+dxwn_3vna'}If there is another parameter in the value it is of type string, otherwise it is an
int or a float. After the replacing I want the value to be a float.The code I wrote to solve this problem is this:
for _ in range(10):
for key, value in paramDict.items():
if type(value) is str:
matchList = re.findall('[a-z]\w*', value)
matchList = [match for match in matchList if match != 'e']
for match in matchList:
param = paramDict[match]
paramDict[key] = value.replace(match, str(param))
try:
paramDict[key] = eval(paramDict[key])
except:
passIt works as far as I can tell, but it just doesn't feel right to just repeat the whole process a finite number of times and hope that all the strings have been replaced. Is there a safer way?
Solution
Well what you want there is evaluating expressions while keeping track
of dependencies, so you should definitely not implement it via regular
expressions and hoping that running it ten times is enough.
The best way in Python would probably be using the
figure out the referenced variables, then calculate dependencies and
evaluate the parsed expressions via
and
while passing in the referenced variables in the
That way you make sure that the expressions are actually valid, you can
use Python expressions and you can (and should) also detect cycles in
the specification.
Below is a sketch how to do that, not saying that is the best code,
please edit or comment with improvements, I couldn't find a good example
while searching.
So at this point the
the expression.
the stack (in
current variable. It also assigns the results immediately, so in
further invocations it will return early (the
case).
Lastly, iterate over all entries and try to evaluate them.
of dependencies, so you should definitely not implement it via regular
expressions and hoping that running it ten times is enough.
The best way in Python would probably be using the
ast module to parse andfigure out the referenced variables, then calculate dependencies and
evaluate the parsed expressions via
compileand
evalwhile passing in the referenced variables in the
local parameter.That way you make sure that the expressions are actually valid, you can
use Python expressions and you can (and should) also detect cycles in
the specification.
Below is a sketch how to do that, not saying that is the best code,
please edit or comment with improvements, I couldn't find a good example
while searching.
import ast, _ast
params = {
'ad': '2*dmcg/factor*(xw/factor)',
'dmcg': 2.05e-07,
'dxwn_3vna': 10.0,
'factor': 1,
'xw': '0+dxwn_3vna',
'foo': 'bar',
'bar': 'foo'
}
parsed = {}
for key, value in params.iteritems():
parsed[key] = value
if type(value) in (str, unicode):
parsed[key] = ast.parse(value, mode="eval")
evaluated = {key: value for key, value in parsed.iteritems()}So at this point the
evaluated dictionary is mostly a copy of theparams, except that expressions have been parsed.def referenced(code):
return [node.id for node in ast.walk(code) if "id" in node._fields]referenced returns a list with the names of referenced variables inthe expression.
evaluating = []
def resolve(key, value):
if key in evaluating:
raise Exception("Loop while evaluating {}".format(key))
if type(value) is not _ast.Expression:
evaluated[key] = value
return value
evaluating.append(key)
locals = {name: resolve(name, evaluated[name]) for name in referenced(value)}
result = eval(compile(value, "", "eval"), globals(), locals)
evaluated[key] = result
evaluating.pop()
return resultresolve evaluates the expression recursively but also keeps track ofthe stack (in
evaluating), so it can detect cycles by looking up thecurrent variable. It also assigns the results immediately, so in
further invocations it will return early (the
not _ast.Expressioncase).
for key, value in parsed.iteritems():
try:
resolve(key, value)
except Exception as exception:
print ("Error while evaluating {}: {}".format(key, exception))
print(evaluated)Lastly, iterate over all entries and try to evaluate them.
Code Snippets
import ast, _ast
params = {
'ad': '2*dmcg/factor*(xw/factor)',
'dmcg': 2.05e-07,
'dxwn_3vna': 10.0,
'factor': 1,
'xw': '0+dxwn_3vna',
'foo': 'bar',
'bar': 'foo'
}
parsed = {}
for key, value in params.iteritems():
parsed[key] = value
if type(value) in (str, unicode):
parsed[key] = ast.parse(value, mode="eval")
evaluated = {key: value for key, value in parsed.iteritems()}def referenced(code):
return [node.id for node in ast.walk(code) if "id" in node._fields]evaluating = []
def resolve(key, value):
if key in evaluating:
raise Exception("Loop while evaluating {}".format(key))
if type(value) is not _ast.Expression:
evaluated[key] = value
return value
evaluating.append(key)
locals = {name: resolve(name, evaluated[name]) for name in referenced(value)}
result = eval(compile(value, "<unknown>", "eval"), globals(), locals)
evaluated[key] = result
evaluating.pop()
return resultfor key, value in parsed.iteritems():
try:
resolve(key, value)
except Exception as exception:
print ("Error while evaluating {}: {}".format(key, exception))
print(evaluated)Context
StackExchange Code Review Q#80273, answer score: 6
Revisions (0)
No revisions yet.