Laurent LAPORTE Laurent LAPORTE - 5 months ago 30
Python Question

Cascading string interpolation in python

Given a dictionary of format strings,
I want to do cascading/recursive string interpolation.

FOLDERS = dict(home="/home/user",
workspace="{home}/workspace",
app_project="{workspace}/{app_name}",
app_name="my_app")


I started with this implementation:

def interpolate(attrs):
remain = [k for k, v in attrs.items() if "{" in v]
while remain:
for k in remain:
attrs[k] = attrs[k].format(**attrs)
remain = [k for k in remain if "{" in attrs[k]]


The
interpolate()
function first select the format strings.
Then, it substitute the strings until no more format strings remain.

When I call this function with the following Python dictionary, I get:

>>> import pprint
>>> pprint.pprint(FOLDERS)
{'app_name': 'my_app',
'app_project': '/home/user/workspace/my_app',
'home': '/home/user',
'workspace': '/home/user/workspace'}


The result is OK but this implementation doesn't detect reference cycles.

For instance, the following call results in Infinite loop!

>>> interpolate({'home': '{home}'})


Could anyone give me a better implementation?

EDIT: solution

I think Leon's solution is good and simple, the one of Serge Bellesta too.

I'll implement it that way:

def interpolate(attrs):
remain = [k for k, v in attrs.items() if "{" in v]
while remain:
for k in remain:
attrs[k] = attrs[k].format(**attrs)
fmt = '{' + k + '}'
if fmt in attrs[k]: # check for reference cycles
raise ValueError("Reference cycle found for '{k}'!".format(k=k))
remain = [k for k in remain if "{" in attrs[k]]

Answer

You can check for such reference cycles in the for loop easily. Just check if the key is referenced in the matching value within the for loop:

def interpolate(attrs):
    remain = [k for k, v in attrs.items() if "{" in v]
    while remain:
        for k in remain:
            attrs[k] = attrs[k].format(**attrs)
            if '{%s}' % k in attrs[k]: # check for reference cycles
                raise ValueError("Input contains at least one reference cycle!")
        remain = [k for k in remain if "{" in attrs[k]]

Now, if there is a reference cycle, an error is raised. This will detect reference cycles of any length, as it is substituted until one is found or all substitutions are complete.