Skip to content

Commit 1b3bdaa

Browse files
authored
Merge pull request #283 from realpython/python-311-examples
Python 3.11 examples
2 parents f1ad275 + 31f9952 commit 1b3bdaa

17 files changed

+634
-0
lines changed

python-311/README.md

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# Python 3.11 Demos
2+
3+
This repository holds example code that demos some of the new features in Python 3.11.
4+
5+
## Introduction
6+
7+
You need Python 3.11 installed to run these examples. See the following tutorial instructions:
8+
9+
- [How Can You Install a Pre-Release Version of Python](https://realpython.com/python-pre-release/)
10+
11+
You can learn more about Python 3.11's new features in the following Real Python tutorials:
12+
13+
- [Python 3.11 Preview: Even Better Error Messages](https://realpython.com/python311-error-messages/)
14+
- [Python 3.11 Preview: Task and Exception Groups](https://realpython.com/python311-exception-groups/)
15+
- [Python 3.11 Preview: TOML and `tomllib`](https://realpython.com/python311-tomllib/)
16+
17+
You'll find examples from all these tutorials in this repository.
18+
19+
## Dependencies
20+
21+
Install necessary dependencies for the examples:
22+
23+
```console
24+
$ python -m pip install colorama parse
25+
```
26+
27+
## Examples
28+
29+
This section only contains brief instructions on how you can run the examples. See the tutorials for technical details.
30+
31+
### Improved Error Messages
32+
33+
Load [`scientists.py`](scientists.py) into your interactive REPL:
34+
35+
```console
36+
$ python -i scientists.py
37+
```
38+
You can then experiment with `dict_to_person()` and `convert_pair()`:
39+
40+
```pycon
41+
>>> dict_to_person(scientists[1])
42+
Traceback (most recent call last):
43+
...
44+
File "/home/realpython/scientists.py", line 37, in dict_to_person
45+
name=f"{info['name']['first']} {info['name']['last']}",
46+
~~~~~~~~~~~~^^^^^^^^
47+
KeyError: 'last'
48+
49+
>>> convert_pair(scientists[0], scientists[2])
50+
Traceback (most recent call last):
51+
...
52+
File "/home/realpython/scientists.py", line 44, in convert_pair
53+
return dict_to_person(first), dict_to_person(second)
54+
^^^^^^^^^^^^^^^^^^^^^^
55+
File "/home/realpython/scientists.py", line 38, in dict_to_person
56+
life_span=(info["birth"]["year"], info["death"]["year"]),
57+
~~~~~~~~~~~~~^^^^^^^^
58+
TypeError: 'NoneType' object is not subscriptable
59+
```
60+
61+
See [Even Better Error Messages in Python 3.11](https://realpython.com/python311-error-messages/#even-better-error-messages-in-python-311) and [PEP 657](https://peps.python.org/pep-0657/).
62+
63+
### Exception Groups
64+
65+
Use `ExceptionGroup` and `except*` to handle several errors at once:
66+
67+
```pycon
68+
>>> try:
69+
... raise ExceptionGroup(
70+
... "group", [TypeError("str"), ValueError(654), TypeError("int")]
71+
... )
72+
... except* ValueError as eg:
73+
... print(f"Handling ValueErrors: {eg.exceptions}")
74+
...
75+
Handling ValueErrors: (ValueError(654),)
76+
+ Exception Group Traceback (most recent call last):
77+
| ...
78+
| ExceptionGroup: group (2 sub-exceptions)
79+
+-+---------------- 1 ----------------
80+
| TypeError: str
81+
+---------------- 2 ----------------
82+
| TypeError: int
83+
+------------------------------------
84+
```
85+
86+
See [Exception Groups and `except*` in Python 3.11](https://realpython.com/python311-exception-groups/#exception-groups-and-except-in-python-311) and [PEP 654](https://peps.python.org/pep-0654/).
87+
88+
### Task Groups
89+
90+
Run [`count.py`](count.py), [`count_gather.py`](count_gather.py), and [`count_taskgroup.py`](count_taskgroup.py) and compare their behaviors. For example:
91+
92+
```console
93+
$ python count_taskgroup.py scientists.py rot13.txt count.py
94+
scientists.py □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ (44)
95+
Files with thirteen lines are too scary!
96+
count.py □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ (32)
97+
```
98+
99+
See [Asynchronous Task Groups in Python 3.11](https://realpython.com/python311-exception-groups/#asynchronous-task-groups-in-python-311) and [BPO 46752](https://github.com/python/cpython/pull/31270).
100+
101+
### `tomllib`
102+
103+
[`python_info.toml`](python_info.toml) and [`tomli_pyproject.toml`](tomli_pyproject.toml) show two examples of TOML files.
104+
105+
Use [`read_toml.py`](read_toml.py) to read them:
106+
107+
```console
108+
$ python read_toml.py python_info.toml tomli_pyproject.toml
109+
======================python_info.toml======================
110+
{'python': {'version': 3.11,
111+
'release_manager': 'Pablo Galindo Salgado',
112+
'is_beta': True,
113+
'beta_release': 3,
114+
'release_date': datetime.date(2022, 6, 16),
115+
'peps': [657, 654, 678, 680, 673, 675, 646, 659]}}
116+
====================tomli_pyproject.toml====================
117+
{'build-system': {'requires': ['flit_core>=3.2.0,<4'],
118+
'build-backend': 'flit_core.buildapi'},
119+
'project': {'name': 'tomli',
120+
'version': '2.0.1',
121+
'description': "A lil' TOML parser",
122+
'requires-python': '>=3.7',
123+
'readme': 'README.md',
124+
'keywords': ['toml'],
125+
'urls': {'Homepage': 'https://github.com/hukkin/tomli',
126+
'PyPI': 'https://pypi.org/project/tomli'}}}
127+
```
128+
129+
[`tomllib_w.py`](tomllib_w.py) shows how you can write simplified TOML files:
130+
131+
```pycon
132+
>>> import tomllib_w
133+
>>> data = {"url": "https://realpython.com/python311-tomllib/",
134+
... "author": {"name": "Geir Arne Hjelle", "email": "[email protected]"}}
135+
136+
>>> print(tomllib_w.dumps(data))
137+
url = "https://realpython.com/python311-tomllib/"
138+
139+
[author]
140+
name = "Geir Arne Hjelle"
141+
142+
```
143+
144+
See [`tomllib` TOML Parser in Python 3.11](https://realpython.com/python311-tomllib/#tomllib-toml-parser-in-python-311) and [PEP 680](https://peps.python.org/pep-0680/).
145+
146+
### `Self` Type
147+
148+
[`polar_point.py`](polar_point.py) uses `Self` for annotation:
149+
150+
```console
151+
$ python polar_point.py
152+
PolarPoint(r=5.0, φ=0.9272952180016122)
153+
```
154+
155+
See [`Self` Type](https://realpython.com/python311-tomllib/#self-type) and [PEP 673](https://peps.python.org/pep-0673/).
156+
157+
### Arbitrary `LiteralString` Type
158+
159+
[`execute_sql.py`](execute_sql.py) shows an example of `LiteralString`:
160+
161+
```console
162+
$ python execute_sql.py
163+
Pretending to execute: SELECT * FROM users
164+
Pretending to execute: SELECT * FROM users
165+
166+
Enter table name: users; DROP TABLE users; --
167+
Pretending to execute: SELECT * FROM users; DROP TABLE users; --
168+
```
169+
170+
See [Arbitrary Literal String Type](https://realpython.com/python311-tomllib/#arbitrary-literal-string-type) and [PEP 675](https://peps.python.org/pep-0675/).
171+
172+
### Variadic Generic Types: `TypeVarTuple`
173+
174+
[`ndarray.py`](ndarray.py) shows an example of using `TypeVarTuple`.
175+
176+
See [Variadic Generic Types](https://realpython.com/python311-tomllib/#variadic-generic-types) and [PEP 646](https://peps.python.org/pep-0646/).
177+
178+
### Exception Annotations
179+
180+
Use `.add_notes()` to annotate exceptions with custom notes:
181+
182+
```pycon
183+
>>> err = ValueError(678)
184+
>>> err.add_note("Enriching Exceptions with Notes")
185+
>>> err.add_note("Python 3.11")
186+
187+
>>> err.__notes__
188+
['Enriching Exceptions with Notes', 'Python 3.11']
189+
>>> for note in err.__notes__:
190+
... print(note)
191+
...
192+
Enriching Exceptions with Notes
193+
Python 3.11
194+
195+
>>> raise err
196+
Traceback (most recent call last):
197+
...
198+
ValueError: 678
199+
Enriching Exceptions with Notes
200+
Python 3.11
201+
```
202+
203+
See [Annotate Exceptions With Custom Notes](https://realpython.com/python311-exception-groups/#annotate-exceptions-with-custom-notes) and [PEP 678](https://peps.python.org/pep-0678/).
204+
205+
### Reference Active Exceptions
206+
207+
You can use `sys.exception()` to access the active exception:
208+
209+
```pycon
210+
>>> import sys
211+
212+
>>> try:
213+
... raise ValueError("bpo-46328")
214+
... except ValueError:
215+
... print(f"Handling {sys.exception()}")
216+
...
217+
Handling bpo-46328
218+
```
219+
220+
Note that this is typically not necessary in regular code. You can use the `except ValueError as err` syntax instead.
221+
222+
See [Reference the Active Exception With `sys.exception()`](https://realpython.com/python311-exception-groups/#reference-the-active-exception-with-sysexception) and [BPO 46328](https://github.com/python/cpython/issues/90486).
223+
224+
### Consistent Tracebacks
225+
226+
`traceback_demo.py` shows that tracebacks can be consistently accessed through the exception object:
227+
228+
```console
229+
$ python traceback_demo.py
230+
tb_last(exc_value.__traceback__) = 'bad_calculation:13'
231+
tb_last(exc_tb) = 'bad_calculation:13'
232+
```
233+
234+
See [Reference the Active Traceback Consistently](https://realpython.com/python311-exception-groups/#reference-the-active-traceback-consistently) and [BPO 45711](https://github.com/python/cpython/issues/89874).
235+
236+
### New Math Functions: `cbrt()` and `exp2()`
237+
238+
You can use `math.cbrt()` to calculate cube roots:
239+
240+
```pycon
241+
>>> import math
242+
>>> math.cbrt(729)
243+
9.000000000000002
244+
245+
>>> 729**(1/3)
246+
8.999999999999998
247+
248+
>>> math.pow(729, 1/3)
249+
8.999999999999998
250+
```
251+
252+
You can use `math.exp2()` to calculate powers of two:
253+
254+
```pycon
255+
>>> import math
256+
>>> math.exp2(16)
257+
65536.0
258+
259+
>>> 2**16
260+
65536
261+
262+
>>> math.pow(2, 16)
263+
65536.0
264+
```
265+
266+
See [Cube Roots and Powers of Two](https://realpython.com/python311-error-messages/#cube-roots-and-powers-of-two), [BPO 44357](https://github.com/python/cpython/issues/88523) and [BPO 45917](https://github.com/python/cpython/issues/90075).
267+
268+
### Underscores in Fractions
269+
270+
You can use underscores when defining fractions from strings:
271+
272+
```pycon
273+
>>> from fractions import Fraction
274+
>>> print(Fraction("6_024/1_729"))
275+
6024/1729
276+
```
277+
278+
See [Underscores in Fractions](https://realpython.com/python311-error-messages/#underscores-in-fractions) and [BPO 44258](https://github.com/python/cpython/issues/88424).
279+
280+
### Flexible Calling of Objects: `operator.call()`
281+
282+
The Norwegian calculator implemented in [`kalkulator.py`](kalkulator.py) uses `operator.call()`:
283+
284+
```pycon
285+
>>> import kalkulator
286+
>>> kalkulator.calculate("20 pluss 22")
287+
42.0
288+
289+
>>> kalkulator.calculate("11 delt på 3")
290+
3.6666666666666665
291+
```
292+
293+
See [Flexible Calling of Objects](https://realpython.com/python311-error-messages/#flexible-calling-of-objects) and [BPO 44019](https://github.com/python/cpython/issues/88185).
294+
295+
## Author
296+
297+
- **Geir Arne Hjelle**, E-mail: [[email protected]]([email protected])
298+
299+
## License
300+
301+
Distributed under the MIT license. See [`LICENSE`](../LICENSE) for more information.

python-311/count.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import sys
2+
import time
3+
4+
import colorama
5+
from colorama import Cursor
6+
7+
colorama.init()
8+
9+
10+
def print_at(row, text):
11+
print(Cursor.POS(1, 1 + row) + str(text))
12+
time.sleep(0.03)
13+
14+
15+
def count_lines_in_file(file_num, file_name):
16+
counter_text = f"{file_name[:20]:<20} "
17+
with open(file_name, mode="rt", encoding="utf-8") as file:
18+
for line_num, _ in enumerate(file, start=1):
19+
counter_text += "□"
20+
print_at(file_num, counter_text)
21+
print_at(file_num, f"{counter_text} ({line_num})")
22+
23+
24+
def count_all_files(file_names):
25+
for file_num, file_name in enumerate(file_names, start=1):
26+
count_lines_in_file(file_num, file_name)
27+
28+
29+
if __name__ == "__main__":
30+
print(colorama.ansi.clear_screen())
31+
count_all_files(sys.argv[1:])
32+
print(Cursor.POS(1, 1 + len(sys.argv)))

python-311/count_gather.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import asyncio
2+
import sys
3+
4+
import colorama
5+
from colorama import Cursor
6+
7+
colorama.init()
8+
9+
10+
async def print_at(row, text):
11+
print(Cursor.POS(1, 1 + row) + str(text))
12+
await asyncio.sleep(0.03)
13+
14+
15+
async def count_lines_in_file(file_num, file_name):
16+
counter_text = f"{file_name[:20]:<20} "
17+
with open(file_name, mode="rt", encoding="utf-8") as file:
18+
for line_num, _ in enumerate(file, start=1):
19+
counter_text += "□"
20+
await print_at(file_num, counter_text)
21+
await print_at(file_num, f"{counter_text} ({line_num})")
22+
23+
24+
async def count_all_files(file_names):
25+
tasks = [
26+
asyncio.create_task(count_lines_in_file(file_num, file_name))
27+
for file_num, file_name in enumerate(file_names, start=1)
28+
]
29+
await asyncio.gather(*tasks)
30+
31+
32+
if __name__ == "__main__":
33+
print(colorama.ansi.clear_screen())
34+
asyncio.run(count_all_files(sys.argv[1:]))
35+
print(Cursor.POS(1, 1 + len(sys.argv)))

0 commit comments

Comments
 (0)