-1

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]]
Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • 2
    *"Could anyone give me a better implementation?"* - that is not what SO is for. What is the actual problem you're trying to solve - will you actually get any inputs with reference cycles? – jonrsharpe Jun 14 '16 at 12:23
  • *"Could anyone give me a better implementation?"* - Does it only have to produce the exact same results for the given input? – Peter Wood Jun 14 '16 at 12:42
  • If fact, I'm searching a generic solution. The exemples are only for illustration. Yes, we can have cycles if the user which defines the folders makes an error in — typically — a configuration files: the ``interpolate()`` function should found it. – Laurent LAPORTE Jun 14 '16 at 20:34

2 Answers2

1

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.

Leon
  • 2,926
  • 1
  • 25
  • 34
0

If your only problem is to detect cyclic references in order to avoid infinite loops, you can stop as soon as one interpolation returns its input:

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)
        temp = [k for k in remain if "{" in attrs[k]]
        if temp == remain:
            # cyclic reference detected
            ...
        remain = temp
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252