0

I try to format a string with curly brackets like this:

dct_mapper = dict(a=1)
s = '{key1: value2, key1: value2}; attrib1={a}; attrib2={b}'
s.format(**dct_mapper)

Results in:
# KeyError: 'key1'

Expected:
# '{key1: value2, key1: value2}; attrib1=1; attrib2={b}'

An additional constrainment is that I need to use .format_map() later on the output again, this screws up solutions which use .format() because the out curly brackets will disappear.

I tried defaultdict from the collections package and also into .format_map(), then plaed around with regex trying to replace the brackets with additional ones, which feels less like a solution and more like a hack and also doesn't work if you have multiple repeating brackets in the string to begin with.

It is not a json string, because then I could have used the json library to map the values.

Does anyone have an idea how to solve this?

I currently consider using a loop and str.replace('{a}', 1) but this feels clunky as well.

Andreas
  • 8,694
  • 3
  • 14
  • 38
  • I'm assuming that this would be too fragile right? `s = '{{key1: value2, key1: value2}}; attrib1={a}; attrib2={{b}}'` – JonSG Feb 25 '22 at 19:23
  • @JonSG unfortunately I think yes because I need to use .format() in multiple successions, but when using it the first time, the "outer shell" of curly brackets are gone. So I would have to re-create them again. But maybe I am missing something? – Andreas Feb 25 '22 at 19:25
  • 1
    If values are provided for either or both of `key1` and `value2`, what does the result look like? – JonSG Feb 25 '22 at 20:02
  • @JonSG, key1 and value2 are inside the dictionary which don't change. So (at least for my usecase) the output would not change. It would only change if a key like: 'key1: value2, key1: value2' is provided, which would be an edge case. – Andreas Feb 25 '22 at 20:32

3 Answers3

4

In case you don't want to convert {} to {{}}, I suggest you using Template from string. Its interface allows you to custom the formatting options as I did below:

from string import Template


class MyTemplate(Template):
    delimiter = ""


dct_mapper = dict(a=1)
s = MyTemplate('{key1: value2, key1: value2}; attrib1={a}; attrib2={b}')
print(s.safe_substitute(**dct_mapper))

The output is, as you wanted: {key1: value2, key1: value2}; attrib1=1; attrib2={b}

Explanation: By default i Template you format strings using $var instead of {var} so I changed the pattern of the variable name (after the $) to be ${var} and the delimiter itself to be "", so {var} is enough instead of ${var}.

Moreover, templates have safe_substitute which allows you to format only few variables in the entire string and it doesn't raise an error for non-existing variable formats.

Also, s.safe_substitute(**dct_mapper) can be changed into s.safe_substitute(dct_mapper) if you prefer this over that.

Jonathan1609
  • 1,809
  • 1
  • 5
  • 22
  • 1
    This looks very good! I wasn't aware of Template. I have to play around a bit, but this is definetly much cleaner and solves the issue at hand. Thanks! – Andreas Feb 25 '22 at 21:04
3

Your format string was invalid but assume you wanted to actually print the braces in that case. Double the braces to print one literal brace.

You can subclass dict to just return the braced format key if the key isn't present. This fills out {a} but leaves {b} alone:

class MyDict(dict):

    # "magic" method called when a key is missing from the dict
    def __missing__(self, key):
        return f'{{{key}}}'

dct_mapper = MyDict(a=1)
s = '{{key1: value2, key1: value2}}; attrib1={a}; attrib2={b}'
# Must use format_map.  format(**dct_mapper) doesn't work
print(s.format_map(dct_mapper))

Output:

{key1: value2, key1: value2}; attrib1=1; attrib2={b}
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • I'm starting with this as well and it works great for `a` and `b` but it will break down with the part that looks like a dictionary. Try `s.format_map(MyDict(a=1)).format_map(MyDict(b=2))` which is what I think they ultimately want to do. – JonSG Feb 25 '22 at 20:07
  • @JonSG the part that looks like a dictionary is an invalid format specifier and breaks using `.format`. If the OP wants do do something with that part they need to show an example and can't use `.format`. @Jonathan `Template` looks promising, though, if a regex is formed to handle the wonky syntax. – Mark Tolonen Feb 25 '22 at 20:12
  • I agree that the format specifier looks potentially broken and I asked them to clarify. – JonSG Feb 25 '22 at 20:15
  • @MarkTolonen, Hi thank you for the answer! I tried a similar approach as well. The problem with this is that I need to use .format_map() later again. So if you add the following two lines: `a = s.format_map(dct_mapper)` `print(a.format_map(dct_mapper)` the code will also throw an exception. – Andreas Feb 25 '22 at 20:46
  • @Andreas OK, but that case should be added to your question then. – Mark Tolonen Feb 25 '22 at 21:53
  • @MarkTolonen, it was discuessed in the comments, but you are right. I will add it for future readers. Since your answer would work without the additional constrainment and because it also might help future readers I upvoted. Thanks for your help! Andreas – Andreas Feb 25 '22 at 21:56
  • @Andreas Thanks. Note with the syntax used in the "key1" format it is invalid, so you'd never be able to use .format as is. The Template solution is good. – Mark Tolonen Feb 25 '22 at 22:00
  • @MarkTolonen, yes I am aware of that. Unfortunately that is the kind of strings I have to work with. :/ Thanks for your help! Andreas – Andreas Feb 25 '22 at 22:03
1

Is this what you are looking for?

dct_mapper = dict(a=1, b=2)
s = '{{key1: value2, key1: value2; attrib1={a}; attrib2={b}}}'.format(**dct_mapper)

Output: {key1: value2, key1: value2; attrib1=1; attrib2=2}

dct_mapper = dict(a=1, b=1)
s = '{{key1: value2, key1: value2; attrib1={a}; attrib2={b}}}'.format(**dct_mapper)

Output: {key1: value2, key1: value2; attrib1=1; attrib2=1}

But if you try to pass only 1 value, say for just "a", it will throw an error because format() is going to fill in the values given to all the {} in the string.

dct_mapper = dict(a=1)
s = '{{key1: value2, key1: value2; attrib1={a}; attrib2={b}}}'.format(**dct_mapper)

Output: KeyError: 'b'

Hope this helps!

Shivika P
  • 115
  • 8
  • Thank you for your answer. Unfortunately it didn't help me to solve the problem, but future readers might find it helpful. Best wishes, Andreas – Andreas Feb 25 '22 at 21:06