Skip to content

Commit f2aa20f

Browse files
valentin-pBethanyG
authored andcommitted
string formatting concept exercise
* config and first draft for pretty leaflet * finishing the exercise and documentation * reviews from Bethany * add more details about i18n and safe * add msg for each string * Update languages/concepts/string-formatting/about.md Co-authored-by: BethanyG <BethanyG@users.noreply.github.com> * add words Co-authored-by: BethanyG <BethanyG@users.noreply.github.com>
1 parent 7ac5b63 commit f2aa20f

10 files changed

Lines changed: 582 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"url": "https://docs.python.org/3/library/stdtypes.html#str.format",
4+
"description": "Docs: str.format()"
5+
},
6+
{
7+
"url": "https://docs.python.org/3/reference/lexical_analysis.html#f-strings",
8+
"description": "Docs: f-strings"
9+
},
10+
{
11+
"url": "https://realpython.com/python-formatted-output/",
12+
"description": "Complete formatting guide"
13+
},
14+
{
15+
"url": "https://www.python.org/dev/peps/pep-3101/#standard-format-specifiers",
16+
"description": "PEP: format specifier"
17+
}
18+
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
## General
2+
3+
Use only f-strings or the `format()` method to build a leaflet containing basic information about an event.
4+
5+
- [Introduction to string formatting in Python][str-f-strings-docs]
6+
- [Article on realpython.com][realpython-article]
7+
8+
## 1. Capitalize the header
9+
10+
- Make sure your class is named `Leaflet`.
11+
- Make sure the constructor `__init__` accepts three parameters: the header, the artists, the unicode characters.
12+
- Capitalize the title using the str method `capitalize`.
13+
- Store the header in an instance variable in the constructor.
14+
15+
## 2. Add an optional date
16+
17+
- The instance variable `date` should be formatted manually using `f''` or `''.format()`.
18+
- The `date` should use this format: 'Month day, year'.
19+
- The year argument can be `None`, in that case, only the month name and the day should be displayed.
20+
- If `set_date` is not called before the leaflet is printed, no date should be displayed.
21+
- Verify your instance variable `date` is initialized with an empty string in the constructor.
22+
23+
## 3. Render the unicode characters as icons
24+
25+
- One way of rendering with `format` would be to use the unicode prefix `u'{}'`.
26+
27+
## 4. Display the finished leaflet
28+
29+
- Find the right [format_spec field][formatspec-docs] to align the asterisks and characters.
30+
- Section 1 is the `header` as a capitalized string.
31+
- Section 2 is the `date` instance variable.
32+
- Section 3 is the list of artists, each artist is associated with the unicode character having the same index.
33+
- Each line should contain 20 characters.
34+
- Write concise code to add the necessary empty lines between each section.
35+
36+
```python
37+
******************** # 20 asterisks
38+
* *
39+
* 'Header' * # capitalized header
40+
* *
41+
* Month day, year * # Optional date
42+
* *
43+
* Artist1 ⑴ * # Artist list from 1 to 4
44+
* Artist2 ⑵ *
45+
* Artist3 ⑶ *
46+
* Artist4 ⑷ *
47+
* *
48+
********************
49+
```
50+
51+
[str-f-strings-docs]: https://docs.python.org/3/reference/lexical_analysis.html#f-strings
52+
[realpython-article]: https://realpython.com/python-formatted-output/
53+
[formatspec-docs]: https://docs.python.org/3/library/string.html#formatspec
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
Your acquaintance Erin needs to print thousands of handbills for multiple events and they need your help! They've asked you to create the layout for a leaflet containing a header, an optional date, and a list of artists -- each associated with a _unicode icon_. The goal is to `print` and distribute your cool new leaflet design - and you'll need all your Python string formatting skills to succeed.
2+
3+
## 1. Create the class with a capitalized header
4+
5+
The `Leaflet` class wraps all the instance variables and methods needed to create the leaflet. Implement that class, the header instance variable correspond to the first argument of the constructor.
6+
7+
The constructor should take three parameters: the header of type `str`, one `list` of artists and one `list` of unicode code points.
8+
9+
The header should be capitalized as illustrated in this example.
10+
11+
```python
12+
>>> Leaflet("title", [], []).header
13+
"Title"
14+
```
15+
16+
## 2. Add an optional date
17+
18+
Implement a new method `set_date(day, month, year)` for your class with an _optional_ `Month, Day, Year` parameter. The values passed in as arguments should be formatted like this: **`December 6, 2021`**. If this method doesn't get called, the instance variable **`date`** should be an _empty string_.
19+
20+
```python
21+
>>> leaflet = Leaflet("title", [], [])
22+
>>> leaflet.date
23+
""
24+
>>> leaflet.set_date(21, 2, 2020)
25+
>>> leaflet.date
26+
"February 21, 2020"
27+
>>> leaflet.set_date(30, 3)
28+
>>> leaflet.date
29+
"March 30, 2020"
30+
```
31+
32+
## 3. Render the unicode code points as icons
33+
34+
When the method `get_icons` is called, the list of unicode code points passed as the third argument in the constructor should be rendered and returned as a `list` of _icons_.
35+
36+
```python
37+
>>> leaflet = Leaflet("title", [], ['\U00000040', '\U0001D70B'])
38+
>>> leaflet.get_icons()
39+
['@', '𝜋']
40+
```
41+
42+
## 4. Display the finished leaflet
43+
44+
The method `print_leaflet()` should return the finished leaflet, the poster should abide by the layout below.
45+
46+
The leaflet follows a specific layout in the shape of a rectangle:
47+
48+
- The first and last rows contain 20 asterisks. `"*"`
49+
- Each section is separated by an empty row above and below it.
50+
- An empty row contains only one asterisk at the beginning and one at the end.
51+
- The first section is the header of the leaflet, this title is capitalized.
52+
- The second section is the option date, if the date is not defined, this section should be an empty row.
53+
- The third and last section is the list of the artists, each artist associated with the corresponding icon.
54+
55+
```python
56+
********************
57+
* *
58+
* 'Concert' *
59+
* *
60+
* June 22, 2020 *
61+
* *
62+
* John 🎸 *
63+
* Benjamin 🎤 *
64+
* Max 🎹 *
65+
* *
66+
********************
67+
```
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
## string-formatting
2+
3+
The Python `str` built-in can be initialized using two robust string formatting methods : `f-strings` and `str.format()`. String interpolation with `f'{variable}'` is preferred because it is an easily read, complete, and very fast module. When an internationalization-friendly or more flexible approach is needed, `str.format()` allows creating almost all the other `str` variations you might need.
4+
5+
# literal string interpolation. f-string
6+
7+
Literal string interpolation is a way of quickly and efficiently formatting and evaluating expressions to `str` using the `f` prefix and the curly brace `{object}`. It can be used with all enclosing string types as: single quote `'`, double quote `"` and for multi-lines and escaping triple quotes `'''` or `"""`.
8+
9+
In this basic example of **f-string**, the variable `name` is rendered at the beginning of the string, and the variable `age` of type `int` is converted to `str` and rendered after `' is '`.
10+
11+
```python
12+
>>> name, age = 'Artemis', 21
13+
>>> f'{name} is {age} years old.'
14+
'Artemis is 21 years old.'
15+
```
16+
17+
The expressions evaluated by an `f-string` can be almost anything -- so the usual cautions about sanitizing input apply. Some of the many values 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 output supports the same control mechanisms such as _width_, _alignment_, and _precision_ that are described for `.format()`. String interpolation cannot be used together with the GNU gettext API for internationalization (I18N) and localization (L10N), `str.format()` needs to be used instead.
30+
31+
# str.format() method
32+
33+
`str.format()` allows for the replacement of in-text placeholders. Placeholders are identified with named indexes `{price}` or numbered indexes `{0}` or empty placeholders `{}`. Their values are specified as parameters in the `str.format()` method. Example:
34+
35+
```python
36+
>>> 'My text: {placeholder1} and {}.'.format(12, placeholder1='value1')
37+
'My text: value1 and 12.'
38+
```
39+
40+
Python `.format()` supports a whole range of [mini language specifier][format-mini-language] that can be used to align text, convert, etc.
41+
42+
The complex formatting specifier is `{[<name>][!<conversion>][:<format_specifier>]}`:
43+
44+
- `<name>` can be a named placeholder or a number or empty.
45+
- `!<conversion>` is optional and should be one of the three: `!s` for [`str()`][str-conversion], `!r` for [`repr()`][repr-conversion] or `!a` for [`ascii()`][ascii-conversion]. By default, `str()` is used.
46+
- `:<format_specifier>` is optional and has a lot of options, which we are [listed here][format-specifiers].
47+
48+
Example of conversions for a diacritical ascii letter:
49+
50+
```python
51+
>>> '{0!s}'.format('ë')
52+
'ë'
53+
>>> '{0!r}'.format('ë')
54+
"'ë'"
55+
>>> '{0!a}'.format('ë')
56+
"'\\xeb'"
57+
58+
>>> 'She said her name is not {} but {!r}.'.format('Anna', 'Zoë')
59+
"She said her name is not Anna but 'Zoë'."
60+
```
61+
62+
Example of format specifiers, [more examples at the end of this page][summary-string-format]:
63+
64+
```python
65+
>>> "The number {0:d} has a representation in binary: '{0: >8b}'.".format(42)
66+
"The number 42 has a representation in binary: ' 101010'."
67+
```
68+
69+
`str.format()` should be used together with the [GNU gettext API][gnu-gettext-api] for internationalization (I18N) and localization (L10N).
70+
71+
[all-about-formatting]: https://realpython.com/python-formatted-output
72+
[difference-formatting]: https://realpython.com/python-string-formatting/#2-new-style-string-formatting-strformat
73+
[printf-style-docs]: https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting
74+
[tuples]: https://www.w3schools.com/python/python_tuples.asp
75+
[format-mini-language]: https://docs.python.org/3/library/string.html#format-specification-mini-language
76+
[str-conversion]: https://www.w3resource.com/python/built-in-function/str.php
77+
[repr-conversion]: https://www.w3resource.com/python/built-in-function/repr.php
78+
[ascii-conversion]: https://www.w3resource.com/python/built-in-function/ascii.php
79+
[format-specifiers]: https://www.python.org/dev/peps/pep-3101/#standard-format-specifiers
80+
[summary-string-format]: https://www.w3schools.com/python/ref_string_format.asp
81+
[template-string]: https://docs.python.org/3/library/string.html#template-strings
82+
[gnu-gettext-api]: https://docs.python.org/3/library/gettext.html
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"contributors": [
3+
{
4+
"github_username": "j08k",
5+
"exercism_username": "j08k"
6+
},
7+
{
8+
"github_username": "BethanyG",
9+
"exercism_username": "BethanyG"
10+
}
11+
],
12+
"authors": [
13+
{
14+
"github_username": "valentin-p",
15+
"exercism_username": "valentin-p"
16+
}
17+
]
18+
}

0 commit comments

Comments
 (0)