16

I have to format a string with values from a dictionary, but the string already contains curly brackets. For example:

raw_string = """
    DATABASE = {
        'name': '{DB_NAME}'
   }
"""

But, of course, raw_string.format(my_dictionary) results in KeyError.

Is there a way to use different symbols to use with .format()?

This is not a duplicate of How do I escape curly-brace ({}) characters characters in a string while using .format? as I need to keep curly brackets just as they are and use a different delimiter for .format.

10

3 Answers 3

15

I don't think it is possible to use alternative delimiters. You need to use double-curly braces {{ }} for curly braces that you don't want to be replaced by format():

inp = """
DATABASE = {{
    'name': '{DB_NAME}'
}}"""

dictionary = {'DB_NAME': 'abc'}
output = inp.format(**dictionary)
print(output)

Output

DATABASE = {
    'name': 'abc'
}
Sign up to request clarification or add additional context in comments.

2 Comments

Nice answer. For further reference, stackoverflow.com/questions/5466451/…
Mhm... my string comes from a complex file (Django's settings.py) and I'd prefer to keep the template as similar as the file it comes from. Of course I could do double sostitution, but I was looking for a more elegant solution
3

Using custom placeholder tokens with python string.format()

Context

  • python 2.7
  • string.format()
  • alternative approach that allows custom placeholder syntax

Problem

We want to use custom placeholder delimiters with python str.format()

  • string.format() is powerful, but no native support for placeholder delimiter modification.
  • string.format() uses curly-brace which is very common and and causes Delimiter collision
  • string.format() default workaround is to double-up the delimiters, which can be cumbersome.

Solution

We write a custom class that extends native python str.format()

  • extend native python string.Formatter with custom class
  • configure string.format() to support arbitrary delimiter placeholder syntax
  • permit other enhancements such as custom formatters and filters

Example001: Demo use of a custom ReFormat class

  • we wrote a custom ReFormat class that extends python str.format()
# import custom class
import ReFormat

# prepare source data
odata = { "fname" : "Planet",
          "lname" : "Earth",
          "age"   : "4b years",
         }

# format output using .render() 
# method of custom ReFormat class
#
vout = ReFormat.String("Hello <%fname%> <%lname%>!",odata).render()
print(vout)

Pitfalls

  • requires extension class to str.format()
  • not intended as a substitute for full-blown sandbox-compatible templating solution
  • str.format has a problem with exact preservation of whitespace around curly-brace when using this kind of approach

4 Comments

How does it compare with alternative templating systems like pystache github.com/defunkt/pystache ?
This approach is not a strong substitute for a full-blown template system, because those systems are designed to permit designers with no programming experience to do basic tasks. This approach is more compatible with a python-centered project where most or all of the people interacting with the code will know some python. It's basically a couple notches above str.format() all by itself, and a couple notches below Jinja2.
The github link is now dead.
Where"s ReFormat implementation?
0

As far as I can tell, the only way to do this is to subclass Formatter and override the parse() method. This breaks the string down into a set of tuples, each of which represents some (optional) literal text followed by an (optional) field to be expanded.

Unfortunately, the default parse() implementation isn't exposed and is non-trivial, involving some regex magic. It's particularly bad if you want to implement anything using balanced delimiters, which can't be done with regexes. Consider braceField={someDict["}"]}.

For my specific use case, I want to expand fields containing valid Python expressions. To do this, I'm splitting the string on the opening delimiters and then using the ast module to grab a valid Python expression before parsing the rest of the string. This means that balanced delimiters work automatically.

import ast

class BracketedFormatter(string.Formatter):
    def parse(self, format_string):
        while format_string:
            left, *right = format_string.split("{", 1)
            if not right:
                yield (left, None, None, None)
                break
            right = right[0]

            # Handle doubled delimiters for literals.
            if right.startswith("{"):
                yield (left + "{", None, None, None)
                format_string = right[1:]
                continue

            # Use ast.parse to find the length of the expression.
            offset = len(right) + 1
            try:
                ast.parse(right)
            except SyntaxError as e:
                if not str(e).startswith("unmatched '}'"):
                    raise e
                offset = e.offset

            expr = right[0 : offset - 1]
            format_string = right[offset:]
            yield (left if left else None, expr, None, None)

And the tests:

from hamcrest import (
    assert_that,
    contains_exactly,
)

def parse(s):
    return list(BracketedFormatter().parse(s))

assert_that(parse(""), contains_exactly())
assert_that(parse("1234"), contains_exactly(("1234", None, None, None)))
assert_that(parse("1234{5678}"), contains_exactly(("1234", "5678", None, None)))
assert_that(
    parse("1234{5678}9abc"),
    contains_exactly(("1234", "5678", None, None), ("9abc", None, None, None)),
)
assert_that(parse("{5678}"), contains_exactly((None, "5678", None, None)))
assert_that(parse("{}"), contains_exactly((None, "", None, None)))
assert_that(
    parse("{1}{2}"),
    contains_exactly((None, "1", None, None), (None, "2", None, None)),
)

assert_that(parse("123}456"), contains_exactly(("123}456", None, None, None)))
assert_that(
    parse("{1}123}456"),
    contains_exactly((None, "1", None, None), ("123}456", None, None, None)),
)

assert_that(parse("{'}'}"), contains_exactly((None, "'}'", None, None)))
assert_that(parse("{'{}'}"), contains_exactly((None, "'{}'", None, None)))

assert_that(
    parse("abc{{def"),
    contains_exactly(("abc{", None, None, None), ("def", None, None, None)),
)

This doesn't handle format specifiers at all, because I don't need them, and no doubt there are many nasty edge cases (I'll try and come back and edit this as I find them). But even though this doesn't quite fit the OP's requirements, I haven't found any other examples allowing this online after some time of looking, so it's worth posting here.

Changing it to work with a different delimiter is left as an exercise for the reader (there are nasty edge cases).

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.