Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

84%
+9 −0
Q&A How can I build a string from smaller pieces?

Before attempting this, make sure it makes sense in context. In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or f...

posted 1y ago by Karl Knechtel‭  ·  edited 1y ago by Karl Knechtel‭

Answer
#19: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T02:46:05Z (over 1 year ago)
organize chronologically; add references for when features were added; label sections with brief guidance (and edit details to avoid repetition)
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted.
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but instead using the `format` method of an ordinary string. This method supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced and useless for almost everyone.** The documentation explains what everything does, but doesn't really make the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.** It should not be used in new code without a good reason. However, it's the original way to solve the problem, and some older interfaces are designed around it. It also isn't formally deprecated.
  • </section>
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It's also possible to use keyword arguments to substitute into the template:
  • ```python
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## f-strings ([since Python 3.6](https://peps.python.org/pep-0498/))
  • <section class="notice is-success">
  • **This approach is recommended for cases where it's applicable.**
  • </section>
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted.
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method ([since Python 3.0](https://peps.python.org/pep-3101/), [backported to 2.6](https://docs.python.org/3/whatsnew/2.6.html#pep-3101-advanced-string-formatting))
  • <section class="notice is-success">
  • **This approach is recommended for cases where f-strings won't work.** It offers compatibility with much older versions of Python, and allows storing a template for later reuse. However, it's slightly more awkward and limited.
  • </section>
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This uses the same basic template syntax as f-strings, but instead using the `format` method of an ordinary string. This method supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced, and useless for almost everyone.** The documentation explains what everything does, but doesn't really make the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `Template` class ([since Python 2.4](https://peps.python.org/pep-0292/))
  • <section class="notice is-warning">
  • **This approach is unpopular, and cumbersome for simple cases.** It seems to be rarely used or mentioned, especially since the `format` method of strings also allows for simple, reusable templates. However, it's still maintained and even got new functionality added in 3.11.
  • </section>
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation.
  • It's also possible to use keyword arguments to substitute into the template:
  • ```python
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.** It should not be used in new code without a good reason. However, it's the original way to solve the problem, and some older interfaces are designed around it. It also isn't formally deprecated.
  • </section>
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <section class="notice is-warning">
  • **This approach can be awkward to use,** especially when there are multiple, non-string pieces to assemble. However, it's simple and effective. Contrary to what some would expect, it doesn't ordinarily run into performance issues; although Python strings are immutable from Python, the implementation can and does "cheat".
  • </section>
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully.
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
#18: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T02:13:57Z (over 1 year ago)
Avoid repeating the main examples within the details
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced and useless for almost everyone.** The documentation explains what everything does, but doesn't really make the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted.
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but instead using the `format` method of an ordinary string. This method supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced and useless for almost everyone.** The documentation explains what everything does, but doesn't really make the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.** It should not be used in new code without a good reason. However, it's the original way to solve the problem, and some older interfaces are designed around it. It also isn't formally deprecated.
  • </section>
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It's also possible to use keyword arguments to substitute into the template:
  • ```python
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#17: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T02:02:12Z (over 1 year ago)
fix formatting typo; improve wording a bit more
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced and useless for almost everyone.** The documentation explains what everything does, but not really in a way that makes the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced and useless for almost everyone.** The documentation explains what everything does, but doesn't really make the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#16: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T01:59:43Z (over 1 year ago)
Consistently show errors from 3.11; improve section warnings
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • f'{"\n"}'
  • ^
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • f'{''}'
  • ^
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-warning">
  • **This is quite advanced and useless for almost everyone.** The documentation explains what everything does, but not really in a way that makes the practical usage clear.
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a real number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/local/lib/python3.11/string.py", line 121, in substitute
  • return self.pattern.sub(convert, self.template)
  • ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  • File "/usr/local/lib/python3.11/string.py", line 114, in convert
  • return str(mapping[named])
  • ~~~~~~~^^^^^^^
  • KeyError: 'count'```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#15: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T01:42:00Z (over 1 year ago)
fix wording (the Template example requires instantiating a library class, so not a one-liner)
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a one-line example of the task attempted in the question; there is a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a minimal example of the task attempted in the question; there's a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#14: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T01:40:19Z (over 1 year ago)
Oops, that wasn't quite short enough
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a one-line example of the task attempted in the question; there is a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'You have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam®; baked beans are {status}'
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'You have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'You have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'You have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('You have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a one-line example of the task attempted in the question; there is a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'I have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'I have {count} cans of Spam®; baked beans are {status}'
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'I have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'I have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, I have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, I have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'I have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'I have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'I have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'I have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'I have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'I have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('I have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('I have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('I have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'I have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'I have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'I have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#13: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T01:37:18Z (over 1 year ago)
Shorten the main example to avoid ugly wrapping on summary headers
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a one-line example of the task attempted in the question; there is a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'You have {count} cans of Spam® and the baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'You have {} cans of Spam® and the baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(count, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(count, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'You have %d cans of Spam® and the baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (count, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('You have $count cans of Spam® and the baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a one-line example of the task attempted in the question; there is a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'You have {count} cans of Spam®; baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam®; baked beans are {status}'
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'You have {} cans of Spam®; baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam®; baked beans are {}'.format(count, status)
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam®; baked beans are {}'
  • >>> template.format(count, status)
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'You have %d cans of Spam®; baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam®; baked beans are %s' % (count, status)
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam®; baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam®; baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'You have ' + str(count) + ' cans of Spam®; baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam®; baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('You have $count cans of Spam®; baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam®; baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam®; baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam®; baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam®; baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#12: Post edited by user avatar Karl Knechtel‭ · 2023-08-08T01:30:09Z (over 1 year ago)
Add warnings/advice to the top based on my old Stack Overflow answer https://stackoverflow.com/a/73658493; hide details for each approach with a direct code example; fix references `spam_cans` -> `count` in examples
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • <section class="notice is-warning">
  • **Before attempting this, make sure it makes sense in context.**
  • In a few particular situations, it would be better to take a different approach rather than using the normal tools for composing or formatting a string.
  • * If the string is for an SQL query, use the SQL library's built-in functionality for parameterized queries. Trying to build the string by hand risks a [**critical security failure**](https://en.wikipedia.org/wiki/SQL_injection) - one that historically has cost (and still does) real businesses huge amounts of money.
  • * If the string is a URL query string - for example, to use an API endpoint - there are well established tools for this, that will take care of e.g. issues with escaping `&` in query parameters automatically.
  • * Similarly, the standard library has a full set of tools for building and manipulating file paths.
  • * If the string is only needed so that it can be displayed or written somewhere immediately, consider [approaches for doing that directly](https://software.codidact.com/posts/289264) instead.
  • That said, there are many tools available for this task. Each section below shows a one-line example of the task attempted in the question; there is a lot to say about each, so please click to expand.
  • </section>
  • ## Python 3.6 onward: f-strings
  • <details>
  • <summary>
  • `f'You have {count} cans of Spam® and the baked beans are {status}'`
  • </summary>
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • </details>
  • ## The `format` method
  • <details>
  • <summary>
  • `'You have {} cans of Spam® and the baked beans are {}'.format(count, status)`
  • </summary>
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(count, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(count, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • </details>
  • ## The `%` operator
  • <details>
  • <summary>
  • `'You have %d cans of Spam® and the baked beans are %s' % (count, status)`
  • </summary>
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (count, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [count, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % count, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • </details>
  • ## String concatenation with explicit casting
  • <details>
  • <summary>
  • `'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status`
  • </summary>
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). Notice that any desired spaces between parts of the text need to be accounted for carefully. (This can also be awkward to use when there are multiple, non-string pieces to assemble.)
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Aside from using `+` to join the strings together, an already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • </details>
  • ## The `Template` class
  • <details>
  • <summary>
  • `from string import Template`<br>
  • `t = Template('You have $count cans of Spam® and the baked beans are $status')`<br>
  • `t.substitute({'count': 8, 'status': 'off'})`
  • </summary>
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • </details>
#11: Post edited by user avatar Karl Knechtel‭ · 2023-08-07T02:49:25Z (over 1 year ago)
Add inline-code formatting for details-section labels (refer: https://meta.codidact.com/posts/289271)
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Implementation details: the format builtin and __format__ magic method</summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>The Formatter class</summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>
  • Implementation details: the `format` builtin and `__format__` magic method
  • </summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>
  • The `format_map` method
  • </summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>
  • The `Formatter` class
  • </summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#10: Post edited by user avatar Karl Knechtel‭ · 2023-08-07T02:27:12Z (over 1 year ago)
Fix header for %-style formatting, including relevant doc link
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Implementation details: the format builtin and __format__ magic method</summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>The Formatter class</summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **This approach is discouraged in new code. It has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Implementation details: the format builtin and __format__ magic method</summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>The Formatter class</summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **[This approach](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it. It is also not formally deprecated.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#9: Post edited by user avatar Karl Knechtel‭ · 2023-08-07T02:22:20Z (over 1 year ago)
Even more detail, reorganization and polishing
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # escaped braces and placeholders mix freely.
  • '{8}'
  • ```
  • <details>
  • <summary>Slightly more advanced placeholder usage</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Implementation details: the format builtin and __format__ magic method</summary>
  • The builtin function `format` (a plain function, **not** the method of string objects) implements the logic for formatting a single value that will replace a placeholder. With one argument, it's basically equivalent to calling `str` to convert the object to string:
  • ```
  • >>> format([1, 2, 5, 'Three, sir.'])
  • "[1, 2, 5, 'Three, sir.']"
  • ```
  • The second argument can be a format specifier that works just like for f-strings or the `format` method:
  • ```
  • >>> format(8, '#010b')
  • '0b00001000'
  • ```
  • To emulate conversions, just do that conversion to the first argument instead:
  • ```
  • >>> f'{"®"!a}' # for reference
  • "'\\xae'"
  • >>> format(ascii('®'))
  • "'\\xae'"
  • ```
  • This `format` function is a wrapper for the `__format__` magic method, also used by the `format` method of strings, which implements a type's logic for handling format specifiers. This can be used to implement custom rules for formatting, that can also be customized in a custom way in the template.
  • For example:
  • ```
  • class Fancy(str):
  • def __format__(self, spec):
  • # The `str` call here avoids unbounded recursion.
  • return f'{spec}{str(self.upper())}!{spec[::-1]}'
  • ```
  • Which enables:
  • ```
  • >>> meal = Fancy(spam)
  • >>> f'{meal:-+*}'
  • '-+*SPAM!*+-'
  • ```
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>The Formatter class</summary>
  • <section class="notice is-danger">
  • **This is quite advanced, not very well documented, and useless for almost everyone.**
  • </section>
  • The standard library `string` module provides a class called `Formatter` which is a pure-Python implementation of the `format` method's logic. A call like `string.Formatter().format(my_string, ...)` is equivalent to `my_string.format(...)`, with whatever positional and keyword arguments replacing the `...`. The purpose of this class is to provide hooks for changing how the formatting process works.
  • A simple (sort of...) example, where the "template" is now a plain list of comma-separated items:
  • ```python
  • class CommaFormatter(string.Formatter):
  • def parse(self, fstr):
  • # This will also be called to look for nested placeholders within
  • # format specs. Ignoring these fully requires special handling.
  • if fstr is None:
  • return
  • field_names = [f.strip() for f in fstr.split(',')]
  • leading = ''
  • for fname in field_names:
  • yield (leading, fname, None, 's')
  • leading = ', ' # for each item except the first
  • ```
  • Which allows:
  • ```
  • >>> cf = CommaFormatter().format
  • >>> cf('meat, veggies', meat='spam', veggies='baked beans')
  • 'spam, baked beans'
  • >>> cf('0,0,0,0,0,0,1,0,0,0,0,0', 'spam', 'baked beans')
  • 'spam, spam, spam, spam, spam, spam, baked beans, spam, spam, spam, spam, spam'
  • ```
  • And, perhaps counter-intuitively:
  • ```
  • >>> cf('', 'spam', 'baked beans')
  • 'spam'
  • >>> cf(',', 'spam', 'baked beans')
  • 'spam, baked beans'
  • ```
  • </details>
  • ## The `%` operator
  • <section class="notice is-warning">
  • **This approach is discouraged in new code. It has many disadvantages and idiosyncracies.**
  • </section>
  • However, this is the original way to solve the problem, and some older interfaces are designed around it.
  • Formatting strings using the `%` operator still works by substituting values into the placeholders of a template, but they look very different. This approach is meant to mimic functions like `printf` in C, although it supports many things that the original functions don't.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. However, it's usually not necessary to worry about this too much. The `%s` placeholder will convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>Advanced usage</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#8: Post edited by user avatar Karl Knechtel‭ · 2023-08-07T00:49:50Z (over 1 year ago)
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • </section>
  • <section class="notice is-success">
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (this can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#7: Post edited by user avatar Karl Knechtel‭ · 2023-08-07T00:44:19Z (over 1 year ago)
Rearrange some points to exploit the labelled-example format more consistently
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **But f-string and `format` approaches work (since they allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders can use names supplied using *keyword arguments*:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Local variable names will *not* be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • **Numbered placeholders allow reusing or re-ordering the formatted values:**
  • ```python
  • >>> # for example, for localization
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • >>> # or just repetition
  • >>> '{0}, {0}, {0}, {0}, {0}, {1} {0}, {0}'.format('spam', 'lovely')
  • 'spam, spam, spam, spam, spam, lovely spam, spam'
  • ```
  • **The template can safely ignore extra values, regardless of numbering:**
  • ```python
  • >>> '{}'.format('spam', 'eggs')
  • 'spam'
  • >>> '{1}'.format('spam', 'eggs')
  • 'eggs'
  • >>> '{veggie}'.format(meat='spam', veggie='baked beans')
  • 'baked beans'
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Placeholders have special, limited syntax for element/item/attribute access:**
  • ```python
  • >>> # Doesn't work with negative numbers or slices.
  • >>> '{[0]}'.format([1, 2, 3])
  • '1'
  • >>> # Only works with keys that are strings.
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **But they *cannot* use arbitrary expressions the way that f-strings do**:
  • ```python
  • >>> '{x+y}'.format(x=1, y=2)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x+y'
  • ```
  • **And can give confusing error messages:**
  • ```
  • >>> # This negative index gets interpreted as a string key instead.
  • >>> '{[-1]}'.format([1,2,3])
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: list indices must be integers or slices, not str
  • ```
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • <section class="notice is-danger">
  • **Extra positional arguments are not ignored and cause an error:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Multiple arguments have to be in a tuple - not a list:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Single values don't need to be in a tuple:**
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Unless the single value *is* a tuple:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • <section class="notice is-success">
  • **`%` can also use a mapping (placeholder names go in parentheses):**
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-warning">
  • **Some advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, certain format specifiers do not work (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#6: Post edited by user avatar Karl Knechtel‭ · 2023-08-07T00:08:54Z (over 1 year ago)
update and enhance information about f-string syntax limitations
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-danger'>
  • **However, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **But f-string and `format` approaches work (since they allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **However, in 3.6 through 3.11, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • **and can't use the same quotes for a nested expression as for the string**:
  • ```
  • >>> f'{''}'
  • File "<stdin>", line 1
  • SyntaxError: f-string: expecting '}'
  • ```
  • **These restrictions [are lifted in 3.12](https://docs.python.org/3.12/whatsnew/3.12.html#pep-701-syntactic-formalization-of-f-strings
  • ).**
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **But f-string and `format` approaches work (since they allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#5: Post edited by user avatar Karl Knechtel‭ · 2023-08-06T06:08:42Z (over 1 year ago)
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-danger'>
  • **However, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **But f-string/`format` approaches work (they're designed to allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method of the template will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **The `safe_substitute` method leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-danger'>
  • **However, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **But f-string and `format` approaches work (since they allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **`safe_substitute` leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#4: Post edited by user avatar Karl Knechtel‭ · 2023-08-06T06:06:32Z (over 1 year ago)
tweak some wording to try to limit section headers to one line
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-danger'>
  • **However, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **The f-string and `format` approaches do work (because they're designed to allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method of the template will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **The `safe_substitute` method leaves those placeholders alone (not always desirable):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-danger'>
  • **However, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **But f-string/`format` approaches work (they're designed to allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method of the template will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **The `safe_substitute` method leaves those placeholders alone (can hide problems):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#3: Post edited by user avatar Karl Knechtel‭ · 2023-08-06T06:02:58Z (over 1 year ago)
More consistent formatting: establishing a style for the use of shaded <section>s
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:
  • <section class='notice is-success'>
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • However, backslashes can't be used anywhere inside the placeholder:
  • <section class='notice is-danger'>
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template. The placeholders *can* use names; these parts of the template need to be filled in using *keyword arguments* to `.format` - it does not care what any local variables are named.
  • <section class="notice is-danger">
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • Because it only uses keys, the template cannot contain any positional values:
  • <section class="notice is-danger">
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple - a list will not work:
  • <section class="notice is-danger">
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Parentheses are necessary because of the order of operations:
  • <section class="notice is-danger">
  • ```python
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • (That tries to do the formatting first, and then make a tuple with the formatted string and the `status` value.)
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped:
  • <section class="notice is-danger">
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • This can't be mixed with positional placeholders, and the error messages can be confusing:
  • <section class="notice is-danger">
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Advanced formatting options are also supported:
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • However, some format specifiers are not:
  • <section class="notice is-danger">
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • See full documentation for the format specifiers [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting).
  • Finally, formatting using the `%` operator does not ignore extra positional arguments:
  • <section class="notice is-danger">
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). A conversion is required in order to avoid errors:
  • <section class='notice is-danger'>
  • ### Ambiguous; disallowed
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • ### Explicit; avoids problems
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • The `substitute` method of the template will raise an exception if a keyword is missing:
  • <section class='notice is-danger'>
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • The `safe_substitute` method will instead leave placeholders alone if no substitution is provided. Keep in mind this may not be desirable.
  • <section class='notice is-warning'>
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • <section class='notice is-success'>
  • **Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:**
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • <section class='notice is-danger'>
  • **However, backslashes can't be used anywhere inside the placeholder:**
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template.
  • <section class="notice is-success">
  • **The placeholders *can* use names:**
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • However, these parts of the template need to be filled in using *keyword arguments* to `.format`.
  • <section class="notice is-danger">
  • **In particular, local variable names will not be considered:**
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • <section class="notice is-danger">
  • **Because it only uses keys, the template cannot contain any positional values:**
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple.
  • <section class="notice is-danger">
  • **A list will not work:**
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Parentheses are necessary around the tuple because of the order of operations:**
  • ```python
  • >>> # This tries to do the formatting first, and THEN
  • >>> # make a tuple with the formatted string and the `status` value.
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped.
  • <section class="notice is-danger">
  • **This tuple represents three separate arguments, not one:**
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **Putting the tuple inside another 1-tuple solves the problem:**
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • <section class="notice is-danger">
  • **This can't be mixed with positional placeholders, and trying causes confusing errors:**
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • <section class="notice is-success">
  • **Advanced formatting options are also supported:**
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • </section>
  • <section class="notice is-danger">
  • **However, some format specifiers are not (See full documentation [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting)):**
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • <section class="notice is-danger">
  • **Formatting using the `%` operator does not ignore extra positional arguments:**
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • **The f-string and `format` approaches do work (because they're designed to allow reordering and reuse):**
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`).
  • <section class='notice is-danger'>
  • **This is disallowed because it's ambiguous:**
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • **Explicitly converting either side of the `+` makes the meaning clear:**
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus, the original example can be redone like:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • <section class='notice is-danger'>
  • **The `substitute` method of the template will raise an exception if a keyword is missing:**
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • <section class='notice is-warning'>
  • **The `safe_substitute` method leaves those placeholders alone (not always desirable):**
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#2: Post edited by user avatar Karl Knechtel‭ · 2023-08-05T19:28:39Z (over 1 year ago)
Whoops, totally forgot about string.Template the first time.
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:
  • <section class='notice is-success'>
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • However, backslashes can't be used anywhere inside the placeholder:
  • <section class='notice is-danger'>
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template. The placeholders *can* use names; these parts of the template need to be filled in using *keyword arguments* to `.format` - it does not care what any local variables are named.
  • <section class="notice is-danger">
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • Because it only uses keys, the template cannot contain any positional values:
  • <section class="notice is-danger">
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple - a list will not work:
  • <section class="notice is-danger">
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Parentheses are necessary because of the order of operations:
  • <section class="notice is-danger">
  • ```python
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • (That tries to do the formatting first, and then make a tuple with the formatted string and the `status` value.)
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped:
  • <section class="notice is-danger">
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • This can't be mixed with positional placeholders, and the error messages can be confusing:
  • <section class="notice is-danger">
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Advanced formatting options are also supported:
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • However, some format specifiers are not:
  • <section class="notice is-danger">
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • See full documentation for the format specifiers [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting).
  • Finally, formatting using the `%` operator does not ignore extra positional arguments:
  • <section class="notice is-danger">
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). A conversion is required in order to avoid errors:
  • <section class='notice is-danger'>
  • ### Ambiguous; disallowed
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • ### Explicit; avoids problems
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • There are many tools available for this task.
  • ## Python 3.6 onward: f-strings
  • "f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
  • ```python
  • f'You have {count} cans of Spam® and the baked beans are {status}'
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
  • ```python
  • >>> f'}}{{}}{{'
  • '}{}{'
  • >>> f'{{{count}}}' # it mixes freely with
  • '{8}'
  • ```
  • <details>
  • <summary>Custom formatting</summary>
  • Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.
  • <details>
  • <summary>Conversions</summary>
  • Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
  • ```python
  • >>> f'{status!s}' # Converting the string using `str`
  • 'off'
  • >>> f'{status!r}' # Converting the string using `repr`
  • "'off'"
  • ```
  • Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
  • </details>
  • <details>
  • <summary>Format specifiers</summary>
  • Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted.
  • There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
  • ```python
  • >>> f'{count:#b}' # binary, with a prefix (because of the #)
  • '0b1000'
  • >>> f'{count:<5}' # left-aligned within a "field"
  • '8 '
  • >>> f'{count:05}' # right-aligned (the default) and zero-padded
  • '00008'
  • >>> f'{count:e}' # scientific notation
  • '8.000000e+00'
  • >>> f'{count:{"5"}}' # numbers here can also use placeholders
  • ' 8'
  • ```
  • </details>
  • </details>
  • <details>
  • <summary>Advanced uses</summary>
  • Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:
  • <section class='notice is-success'>
  • ```python
  • >>> f'{"example"}' # Interpolate a normal, double-quoted string
  • 'example'
  • >>> f'{1+2}'
  • '3'
  • ```
  • </section>
  • However, backslashes can't be used anywhere inside the placeholder:
  • <section class='notice is-danger'>
  • ```python
  • >>> f'{"\n"}'
  • File "<stdin>", line 1
  • SyntaxError: f-string expression part cannot include a backslash
  • ```
  • </section>
  • </details>
  • ## The `format` method
  • This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
  • ```python
  • >>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • This supports all the same custom formatting and escaping as the f-string approach:
  • ```python
  • >>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
  • 'In binary, you have 0b00001000 cans of Spam®.'
  • >>> '{{{}}}'.format('wavy')
  • '{wavy}'
  • ```
  • <details>
  • <summary>Advanced usage, and comparison to f-strings</summary>
  • The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
  • ```python
  • >>> template = 'You have {} cans of Spam® and the baked beans are {}'
  • >>> template.format(spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template. The placeholders *can* use names; these parts of the template need to be filled in using *keyword arguments* to `.format` - it does not care what any local variables are named.
  • <section class="notice is-danger">
  • ```python
  • >>> x = 1
  • >>> '{x}'.format(1)
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'x'
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> '{x} + {x}'.format(x=1)
  • '1 + 1'
  • ```
  • </section>
  • Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
  • ```python
  • >>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
  • 'In English, a dead parrot.'
  • >>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
  • 'En Français, un perroquet mort.'
  • ```
  • Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
  • ```python
  • >>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
  • '1'
  • >>> '{[key]}'.format({'key': 'value'})
  • 'value'
  • >>> '{.imag}'.format(1+2j)
  • '2.0'
  • ```
  • But they *cannot* use arbitrary expressions the way that f-strings do.
  • </details>
  • <details>
  • <summary>The format_map method</summary>
  • The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.
  • Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
  • ```python
  • >>> class ExampleMapping(dict):
  • ... def __missing__(self, key):
  • ... return f'value for {key}'
  • ...
  • >>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • KeyError: 'ham'
  • >>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
  • 'value for ham and value for eggs'
  • ```
  • Because it only uses keys, the template cannot contain any positional values:
  • <section class="notice is-danger">
  • ```python
  • >>> # This does not try an empty string key!
  • >>> '{}'.format_map(ExampleMapping())
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: Format string contains positional fields
  • ```
  • </section>
  • </details>
  • ## The `%` operator
  • This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.
  • `%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.
  • The basic use looks like this:
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).
  • To escape a literal `%` sign, double it up (just like with the braces before):
  • ```python
  • >>> '%%%s%%' % 'I like percentage signs'
  • '%I like percentage signs%'
  • ```
  • <details>
  • <summary>Complications</summary>
  • When there is more than one value to substitute, they need to be put in a tuple - a list will not work:
  • <section class="notice is-danger">
  • ```python
  • >>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: %d format: a number is required, not list
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Parentheses are necessary because of the order of operations:
  • <section class="notice is-danger">
  • ```python
  • >>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • (That tries to do the formatting first, and then make a tuple with the formatted string and the `status` value.)
  • Single values don't need to be in a tuple:
  • ```python
  • >>> my_list = [1, 2, 3]
  • >>> 'This is a list: %s' % my_list
  • 'This is a list: [1, 2, 3]'
  • ```
  • ... unless the single value *is* a tuple, which then needs to be wrapped:
  • <section class="notice is-danger">
  • ```python
  • >>> my_tuple = (1, 2, 3)
  • >>> 'This is a tuple: %s' % my_tuple
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> 'This is a tuple: %s' % (my_tuple,)
  • 'This is a tuple: (1, 2, 3)'
  • ```
  • </section>
  • </details>
  • <details>
  • <summary>Advanced usage</summary>
  • It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
  • ```python
  • >>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
  • 'ham and eggs'
  • ```
  • This can't be mixed with positional placeholders, and the error messages can be confusing:
  • <section class="notice is-danger">
  • ```python
  • >>> '%(key)s %s' % ({'key': 'value'}, 'text')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: format requires a mapping
  • >>> '%(key)s %s' % {'key': 'value', '': ''}
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not enough arguments for format string
  • ```
  • </section>
  • Advanced formatting options are also supported:
  • ```python
  • >>> '%08x' % count
  • '00000008'
  • >>> '%#010x' % count
  • '0x00000008'
  • ```
  • However, some format specifiers are not:
  • <section class="notice is-danger">
  • ```python
  • >>> f'{count:b}'
  • '1000'
  • >>> '%b' % count
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • ValueError: unsupported format character 'b' (0x62) at index 1
  • ```
  • </section>
  • See full documentation for the format specifiers [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting).
  • Finally, formatting using the `%` operator does not ignore extra positional arguments:
  • <section class="notice is-danger">
  • ```python
  • >>> '%s' % ('one', 'two')
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: not all arguments converted during string formatting
  • ```
  • </section>
  • <section class="notice is-success">
  • ```python
  • >>> '{}'.format('one', 'two')
  • 'one'
  • ```
  • </section>
  • </details>
  • ## String concatenation
  • The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). A conversion is required in order to avoid errors:
  • <section class='notice is-danger'>
  • ### Ambiguous; disallowed
  • ```python
  • >>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • TypeError: can only concatenate str (not "int") to str
  • ```
  • </section>
  • <section class='notice is-success'>
  • ### Explicit; avoids problems
  • ```python
  • >>> int('2') + 2
  • 4
  • >>> '2' + str(2)
  • '22'
  • ```
  • </section>
  • Thus:
  • ```python
  • >>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.
  • ## The `join` method
  • An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):
  • ```python
  • >>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
  • >>> ' '.join(pieces) # join with a space in between each pair
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • ## The `Template` class
  • The standard library `string` module provides a class called [`Template`](https://docs.python.org/3/library/string.html#template-strings) that provides reusable string formatting from keywords (similar to a string with braces that will have its `format_map` method called later). It uses a less powerful, but simpler syntax that appears inspired by Perl's string interpolation. It seems to be rarely used or mentioned, but it's still maintained and even got new functionality added in 3.11.
  • It looks like:
  • ```python
  • >>> from string import Template
  • >>> t = Template('You have $count cans of Spam® and the baked beans are $status')
  • >>> t.substitute({'count': 8, 'status': 'off'})
  • 'You have 8 cans of Spam® and the baked beans are off'
  • >>> # Or, using keyword arguments:
  • >>> t.substitute(count=8, status='off')
  • 'You have 8 cans of Spam® and the baked beans are off'
  • ```
  • <details>
  • <summary>More details</summary>
  • The `substitute` method of the template will raise an exception if a keyword is missing:
  • <section class='notice is-danger'>
  • ```
  • >>> t.substitute()
  • Traceback (most recent call last):
  • File "<stdin>", line 1, in <module>
  • File "/usr/lib/python3.8/string.py", line 126, in substitute
  • return self.pattern.sub(convert, self.template)
  • File "/usr/lib/python3.8/string.py", line 119, in convert
  • return str(mapping[named])
  • KeyError: 'count'
  • ```
  • </section>
  • The `safe_substitute` method will instead leave placeholders alone if no substitution is provided. Keep in mind this may not be desirable.
  • <section class='notice is-warning'>
  • ```
  • >>> t.safe_substitute(status='off')
  • 'You have $count cans of Spam® and the baked beans are off'
  • ```
  • </section>
  • Use `{}` to clarify the placeholder name if there need to be letters immediately after the substituted value:
  • ```
  • >>> Template('${b}a${c}o$n').substitute(b='b',c='c',n='n')
  • 'bacon'
  • ```
  • To escape a literal `$`, double it up (I'm noticing a theme...):
  • ```
  • >>> Template('ca$$h $cash').substitute(cash='money')
  • 'ca$h money'
  • ```
  • For really advanced use cases, the `Template` class can also be subclassed - refer to the documentation for details.
  • </details>
#1: Initial revision by user avatar Karl Knechtel‭ · 2023-08-05T16:33:43Z (over 1 year ago)
There are many tools available for this task.

## Python 3.6 onward: f-strings

"f-string" is an informal (but official!) name for the [formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals) introduced in Python 3.6, introduced [by PEP 498](https://peps.python.org/pep-0498/). To use them, write an entire "template" string with "placeholders" (my terminology) that are surrounded by `{}` and contain the appropriate variable names for whatever will be inserted. For example:
```python
f'You have {count} cans of Spam® and the baked beans are {status}'
'You have 8 cans of Spam® and the baked beans are off'
```
f-strings have the same quoting and escaping rules as normal strings, except for the placeholders. To use literal `{` and `}` symbols in the string, double them up:
```python
>>> f'}}{{}}{{'
'}{}{'
>>> f'{{{count}}}' # it mixes freely with 
'{8}'
```
<details>
<summary>Custom formatting</summary>

Within each placeholder, the value that will be formatted can be followed by a *conversion* and a *format specifier*.

<details>
<summary>Conversions</summary>

Conversions are mostly not very useful. They're used to convert the value explicitly to string first. This isn't needed for making it work (as shown above), but sometimes it's useful to bypass the type's own formatting rules. There are also different ways to convert Python values to strings. For example:
```python
>>> f'{status!s}' # Converting the string using `str`
'off'
>>> f'{status!r}' # Converting the string using `repr`
"'off'"
```
Obviously, converting a string to a string, using `str`, has no real effect. However, `!r` converts the string to a *representation* of the string - a way that it could be expressed in Python source code. (There is also `!a`, for converting to an ASCII representation - this is a legacy purpose that should rarely if ever be necessary.)
</details>
<details>
<summary>Format specifiers</summary>

Format specifiers are mostly used for aligning and padding numeric values. How they work depends on the type of whatever is being formatted. 
There are some general rules (that are implemented by the built-in `int` and `float`), but there's too much to go over here; please [see the documentation](https://docs.python.org/3/library/string.html#format-specification-mini-language) for a complete reference. Here are a few examples, though:
```python
>>> f'{count:#b}' # binary, with a prefix (because of the #)
'0b1000'
>>> f'{count:<5}' # left-aligned within a "field"
'8    '
>>> f'{count:05}' # right-aligned (the default) and zero-padded
'00008'
>>> f'{count:e}' # scientific notation
'8.000000e+00'
>>> f'{count:{"5"}}' # numbers here can also use placeholders
'    8'
```
</details>
</details>

<details>
<summary>Advanced uses</summary>

Placeholders can contain more complex [expressions](https://software.codidact.com/posts/289228), as well as literal values:
<section class='notice is-success'>

```python
>>> f'{"example"}' # Interpolate a normal, double-quoted string
'example'
>>> f'{1+2}'
'3'
```
</section>
However, backslashes can't be used anywhere inside the placeholder:
<section class='notice is-danger'>

```python
>>> f'{"\n"}'
  File "<stdin>", line 1
SyntaxError: f-string expression part cannot include a backslash
```
</section>
</details>

## The `format` method

This has existed since Python 2.6, although it took a while to become popular. It uses the same basic syntax as f-strings, but with a regular string. The string type has a `format` method which can be called like so:
```python
>>> 'You have {} cans of Spam® and the baked beans are {}'.format(spam_cans, status)
'You have 8 cans of Spam® and the baked beans are off'
```
This supports all the same custom formatting and escaping as the f-string approach:
```python
>>> 'In binary, you have {:#010b} cans of Spam®.'.format(count)
'In binary, you have 0b00001000 cans of Spam®.'
>>> '{{{}}}'.format('wavy')
'{wavy}'
```
<details>
<summary>Advanced usage, and comparison to f-strings</summary>

The advantage over f-strings is that the "template" string can be stored for later use, and explicitly fill in the values later, and that the placeholders can usefully be empty:
```python
>>> template = 'You have {} cans of Spam® and the baked beans are {}'
>>> template.format(spam_cans, status)
'You have 8 cans of Spam® and the baked beans are off'
```
However, the syntax is much more limited. The values that will be formatted need to be passed in as arguments - any calculation happens at or before that point, not as part of the template. The placeholders *can* use names; these parts of the template need to be filled in using *keyword arguments* to `.format` - it does not care what any local variables are named.
<section class="notice is-danger">

```python
>>> x = 1
>>> '{x}'.format(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'x'
```
</section>

<section class="notice is-success">

```python
>>> '{x} + {x}'.format(x=1)
'1 + 1'
```
</section>

Placeholders can also use numbers, so that the same value can be reused, or values can be re-ordered. This is often useful for localization:
```python
>>> 'In English, {0} {1} {2}.'.format('a', 'dead', 'parrot')
'In English, a dead parrot.'
>>> 'En Français, {0} {2} {1}.'.format('un', 'mort', 'perroquet')
'En Français, un perroquet mort.'
```
Finally, placeholders offer a *limited* ability to access elements of a sequence or mapping, or attributes of an object:
```python
>>> '{[0]}'.format([1, 2, 3]) # does not support negative indexing!
'1'
>>> '{[key]}'.format({'key': 'value'})
'value'
>>> '{.imag}'.format(1+2j)
'2.0'
```
But they *cannot* use arbitrary expressions the way that f-strings do.
</details>

<details>
<summary>The format_map method</summary>

The `format_map` method is a minor variation on the `format` method that is rarely seen. It takes a single parameter which is some kind of mapping; it works like `format`, except looking up keys in the mapping instead of keyword arguments.

Calling `.format_map(mapping)` is similar to calling `.format(**mapping)`, but it allows the mapping to compute values on-demand when the keys are looked up, and doesn't require the mapping to know what its keys are. It's possible to define custom mappings, like so:
```python
>>> class ExampleMapping(dict):
...     def __missing__(self, key):
...         return f'value for {key}'
... 
>>> '{ham} and {eggs}'.format(**ExampleMapping()) # doesn't work
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'ham'
>>> '{ham} and {eggs}'.format_map(ExampleMapping()) # this is needed
'value for ham and value for eggs'
```
Because it only uses keys, the template cannot contain any positional values:
<section class="notice is-danger">

```python
>>> # This does not try an empty string key!
>>> '{}'.format_map(ExampleMapping()) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Format string contains positional fields
```
</section>
</details>

## The `%` operator

This is the original way to solve the problem. It has many disadvantages and idiosyncrasies. Some older interfaces are designed around it, but new code should generally not take this approach.

`%` still works by substituting values into the placeholders of a template, but they look very different. It's meant to mimic functions like `printf` in C, although it has some extensions.

The basic use looks like this:
```python
>>> 'You have %d cans of Spam® and the baked beans are %s' % (spam_cans, status)
'You have 8 cans of Spam® and the baked beans are off'
```
The letters used for the placeholders follow mostly the same rules as in C, which are also used for format specifiers for f-strings and the `format` method. It's usually not necessary to worry about this too much, because `%s` means to convert the value directly to string with `str`, and everything supports that by default (although for some types, the result might not always be what you want).

To escape a literal `%` sign, double it up (just like with the braces before):
```python
>>> '%%%s%%' % 'I like percentage signs'
'%I like percentage signs%'
```

<details>
<summary>Complications</summary>

When there is more than one value to substitute, they need to be put in a tuple - a list will not work:

<section class="notice is-danger">

```python
>>> 'You have %d cans of Spam® and the baked beans are %s' % [spam_cans, status]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: %d format: a number is required, not list
>>> 'You have %s cans of Spam® and the baked beans are %s' % [spam_cans, status]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not enough arguments for format string
```
</section>

Parentheses are necessary because of the order of operations:

<section class="notice is-danger">

```python
>>> 'You have %s cans of Spam® and the baked beans are %s' % spam_cans, status
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not enough arguments for format string
```
</section>

(That tries to do the formatting first, and then make a tuple with the formatted string and the `status` value.)

Single values don't need to be in a tuple:
```python
>>> my_list = [1, 2, 3]
>>> 'This is a list: %s' % my_list
'This is a list: [1, 2, 3]'
```
... unless the single value *is* a tuple, which then needs to be wrapped:

<section class="notice is-danger">

```python
>>> my_tuple = (1, 2, 3)
>>> 'This is a tuple: %s' % my_tuple
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not all arguments converted during string formatting
```
</section>
<section class="notice is-success">

```python
>>> 'This is a tuple: %s' % (my_tuple,)
'This is a tuple: (1, 2, 3)'
```
</section>
</details>

<details>
<summary>Advanced usage</summary>

It's also possible to use the `%` operator with a mapping, giving names to each placeholder in parentheses:
```python
>>> '%(meat)s and %(side)s' % {'side': 'eggs', 'meat': 'ham'}
'ham and eggs'
```
This can't be mixed with positional placeholders, and the error messages can be confusing:

<section class="notice is-danger">

```python
>>> '%(key)s %s' % ({'key': 'value'}, 'text')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: format requires a mapping
>>> '%(key)s %s' % {'key': 'value', '': ''}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not enough arguments for format string
```
</section>

Advanced formatting options are also supported:
```python
>>> '%08x' % count
'00000008'
>>> '%#010x' % count
'0x00000008'
```
However, some format specifiers are not:

<section class="notice is-danger">

```python
>>> f'{count:b}'
'1000'
>>> '%b' % count
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: unsupported format character 'b' (0x62) at index 1
```
</section>

See full documentation for the format specifiers [here](https://docs.python.org/3/library/stdtypes.html#old-string-formatting).

Finally, formatting using the `%` operator does not ignore extra positional arguments:

<section class="notice is-danger">

```python
>>> '%s' % ('one', 'two')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: not all arguments converted during string formatting
```
</section>
<section class="notice is-success">

```python
>>> '{}'.format('one', 'two')
'one'
```
</section>
</details>

## String concatenation

The `+` operator *does* work for putting strings together, but it requires a string for *both* operands (on both sides of the `+`). A conversion is required in order to avoid errors:

<section class='notice is-danger'>

### Ambiguous; disallowed
```python
>>> '2' + 2 # "In the face of ambiguity, refuse the temptation to guess."
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
```
</section>
<section class='notice is-success'>

### Explicit; avoids problems
```python
>>> int('2') + 2
4
>>> '2' + str(2)
'22'
```
</section>

Thus:
```python
>>> 'You have ' + str(count) + ' cans of Spam® and the baked beans are ' + status
'You have 8 cans of Spam® and the baked beans are off'
```
Notice that any desired spaces between parts of the text need to be accounted for carefully. This is also awkward to use when there are multiple, non-string pieces to assemble.

## The `join` method

An already-existing sequence of strings can simply be joined together (which is usually considered its own topic):

```python
>>> pieces = ('You have', str(count), 'cans of Spam® and the baked beans are', status)
>>> ' '.join(pieces) # join with a space in between each pair
'You have 8 cans of Spam® and the baked beans are off'
```