|
| 1 | +## String Formatting in Python |
| 2 | + |
| 3 | +The [Zen of Python][zen-of-python] asserts there should be "one _obvious_ way to do something in Python". But when it comes to string formatting, things are a little .... _less zen_. It can be surprising to find out that there are **four** main ways to perform string formatting in Python - each for a different scenario. |
| 4 | + |
| 5 | +# literal string interpolation. f-string |
| 6 | + |
| 7 | +Literal string interpolation or **f-string** is a way of quickly and efficiently formatting and evaluating expressions to `str` using the `f` (or `F`) prefix before the brackets, like so `f'{object}'`. It can be used with all enclosing string types as: single quote `'`, double quote `"` and with multi-lines and escaping triple quotes `'''` or `"""`. |
| 8 | + |
| 9 | +In this example, we insert two variable values in the sentence: one `str` and one `float`: |
| 10 | + |
| 11 | +```python |
| 12 | +>>> name, value = 'eighth', 1/8 |
| 13 | +>>> f'An {name} is approximately {value:.2f}.' |
| 14 | +'An eighth is approximately 0.12.' # .2f truncates so it displays 0.12. |
| 15 | +``` |
| 16 | + |
| 17 | +The expressions evaluated can be almost anything, to mention some of the possibilities that can be evaluated: `str`, numbers, variables, arithmetic expressions, conditional expressions, built-in types, slices, functions or any objects with either `__str__` or `__repr__` methods defined. Some examples: |
| 18 | + |
| 19 | +```python |
| 20 | +>>> waves = {'water': 1, 'light': 3, 'sound': 5} |
| 21 | + |
| 22 | +>>> f'"A dict can be represented with f-string: {waves}."' |
| 23 | +'"A dict can be represented with f-string: {\'water\': 1, \'light\': 3, \'sound\': 5}."' |
| 24 | + |
| 25 | +>>> f'Tenfold the value of "light" is {waves["light"]*10}.' |
| 26 | +'Tenfold the value of "light" is 30.' |
| 27 | +``` |
| 28 | + |
| 29 | +f-string supports control mechanisms such as width, alignment, precision that are described for the `.format()` method. |
| 30 | + |
| 31 | +An advanced example of f-string: |
| 32 | + |
| 33 | +```python |
| 34 | +>>> precision, verb, the_end = 3, "see", ['end', 'of', 'transmission'] |
| 35 | +>>> verb = 'meet' |
| 36 | +>>> f'"Have a {"NICE".lower()} day, I will {verb} you after {f"{30e8*111_000:6.{precision}e}"} light-years."{{{the_end}}}' |
| 37 | +'"Have a nice day, I will meet you after 3.330e+14 light-years."{[\'end\', \'of\', \'transmission\']}' |
| 38 | +# This example includes a function, str, nested f-string, arithmetic expression, precision, bracket escaping and object formatting. |
| 39 | +``` |
| 40 | + |
| 41 | +There is a couple of limitations to be aware of: f-string expressions can't be empty and cannot contain comments: |
| 42 | + |
| 43 | +```python |
| 44 | +>>> f"An empty expression will error: {}" |
| 45 | +SyntaxError: f-string: empty expression not allowed |
| 46 | + |
| 47 | +>>> word = 'word' |
| 48 | +>>> f"""A comment in a triple quoted f-string will error: { |
| 49 | + word # I chose a nice variable |
| 50 | +}""" |
| 51 | +SyntaxError: f-string expression part cannot include '#' |
| 52 | +``` |
| 53 | + |
| 54 | +String interpolation cannot be used together with the GNU gettext API for internationalization (I18N) and localization (L10N), so `str.format()` needs to be used instead of an `f-string` in translation scenarios. Keep in mind that using an expression inside the brackets `{}` is similar to using `eval()` or `exec()`, so it isn't very safe and should be used sparingly. |
| 55 | + |
| 56 | +# str.format() method |
| 57 | + |
| 58 | +The [`str.format()`][str-format] allows to replace placeholders with values, the placeholders are identified with named indexes `{price}` or numbered indexes `{0}` or empty placeholders `{}`. The values are specified as parameters in the `format()` method. |
| 59 | + |
| 60 | +Example: |
| 61 | + |
| 62 | +```python |
| 63 | +>>> 'My text: {placeholder1} and {}.'.format(12, placeholder1='value1') |
| 64 | +'My text: value1 and 12.' |
| 65 | +``` |
| 66 | + |
| 67 | +Python `.format()` supports a whole range of [mini language format specifier][format-mini-language] that can be used to align text, convert, etc. |
| 68 | + |
| 69 | +The complete formatting specifier pattern is `{[<name>][!<conversion>][:<format_specifier>]}`: |
| 70 | + |
| 71 | +- `<name>` can be a named placeholder or a number or empty. |
| 72 | +- `!<conversion>` is optional and should be one of this three conversions: `!s` for [`str()`][str-conversion], `!r` for [`repr()`][repr-conversion] or `!a` for [`ascii()`][ascii-conversion]. By default, `str()` is used. |
| 73 | +- `:<format_specifier>` is optional and has a lot of options, which we are [listed here][format-specifiers]. |
| 74 | + |
| 75 | +Example of conversions for a diacritical letter: |
| 76 | + |
| 77 | +```python |
| 78 | +>>> '{0!s}'.format('ë') |
| 79 | +'ë' |
| 80 | +>>> '{0!r}'.format('ë') |
| 81 | +"'ë'" |
| 82 | +>>> '{0!a}'.format('ë') |
| 83 | +"'\\xeb'" |
| 84 | + |
| 85 | +>>> 'She said her name is not {} but {!r}.'.format('Chloe', 'Zoë') |
| 86 | +"She said her name is not Chloe but 'Zoë'." |
| 87 | +``` |
| 88 | + |
| 89 | +Example of format specifiers: |
| 90 | + |
| 91 | +```python |
| 92 | +>>> "The number {0:d} has a representation in binary: '{0: >8b}'.".format(42) |
| 93 | +"The number 42 has a representation in binary: ' 101010'." |
| 94 | +``` |
| 95 | + |
| 96 | +More examples at the end of [this documentation][summary-string-format]. `str.format()` should be used together with the [GNU gettext API][gnu-gettext-api] for internationalization (I18N) and localization (L10N) in translation scenarios. |
| 97 | + |
| 98 | +## The `%` operator |
| 99 | + |
| 100 | +The `%` operator comes from the C language and it allows to use positional arguments to build a `str`. |
| 101 | + |
| 102 | +This method is now superseded by the `.format()` method, which is why the nickname of `%` is The _'Old Style'_. It can be still found is python 2 or legacy code. The `%` operator is usually avoided because less efficient, fewer options are available and any placeholder-argument mismatch can raise an exception. This operator is similar to [`printf()`][printf-style-docs]. |
| 103 | + |
| 104 | +```python |
| 105 | +>> name = "Anna-conda" |
| 106 | +>> "The snake's name is %s." % name |
| 107 | +"The snake's name is Anna-conda." |
| 108 | +``` |
| 109 | + |
| 110 | +The `%` operator substitutes at runtime the placeholder `%s` with the variable `name`. If you want to add multiple variables to a string you would need a [tuple][tuples] containing one object per placeholder after the `%`. |
| 111 | + |
| 112 | +## Template strings |
| 113 | + |
| 114 | +`Template` is a module from the `string` standard library, which is always shipped with Python. It is simpler but also less capable than the options mentioned before, but can be sometimes enough to build a nice `str`. |
| 115 | + |
| 116 | +```python |
| 117 | +>> from string import Template |
| 118 | +>> template_string = Template("The snake called `$snake_name` has escaped!") |
| 119 | +>> template_string.substitute(snake_name=name) |
| 120 | +'The snake called `Anna-conda` has escaped' |
| 121 | +``` |
| 122 | + |
| 123 | +More information about `Template` string can be found in the Python [documentation][template-string]. |
| 124 | + |
| 125 | +## How to choose which formatting method to use? |
| 126 | + |
| 127 | +1. The `f-string` is the newest and easiest to read, it should be preferred when using Python 3.6+. |
| 128 | +2. Then `.format()` is versatile, very powerful and compatible with most versions of Python. |
| 129 | +3. `Template string` can be used to mitigate risks when inputs from users need to be handled, for simplicity or multiple substitutions. |
| 130 | +4. The `%` operator is not supported in some of the new distributions of Python, it is mostly used for compatibility with old code. This operator can lead to issues displaying non-ascii characters and has less functionalities than other methods. |
| 131 | + |
| 132 | +If you want to go further: [all about formatting][all-about-formatting]. |
| 133 | + |
| 134 | +[zen-of-python]: https://www.python.org/dev/peps/pep-0020/ |
| 135 | +[f-string]: https://realpython.com/python-string-formatting/#3-string-interpolation-f-strings-python-36 |
| 136 | +[str-format]: https://realpython.com/python-string-formatting/#2-new-style-string-formatting-strformat |
| 137 | +[format-mini-language]: https://docs.python.org/3/library/string.html#format-specification-mini-language |
| 138 | +[str-conversion]: https://www.w3resource.com/python/built-in-function/str.php |
| 139 | +[repr-conversion]: https://www.w3resource.com/python/built-in-function/repr.php |
| 140 | +[ascii-conversion]: https://www.w3resource.com/python/built-in-function/ascii.php |
| 141 | +[format-specifiers]: https://www.python.org/dev/peps/pep-3101/#standard-format-specifiers |
| 142 | +[summary-string-format]: https://www.w3schools.com/python/ref_string_format.asp |
| 143 | +[printf-style-docs]: https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting |
| 144 | +[tuples]: https://www.w3schools.com/python/python_tuples.asp |
| 145 | +[template-string]: https://docs.python.org/3/library/string.html#template-strings |
| 146 | +[all-about-formatting]: https://realpython.com/python-formatted-output |
| 147 | +[gnu-gettext-api]: https://docs.python.org/3/library/gettext.html |
0 commit comments