<!-- .slide: data-background="https://github.com/noirbizarre/slides-paris.py/raw/master/images/python.jpg" --> # The new snakes --- <!-- .slide: data-background="https://github.com/noirbizarre/slides-paris.py/raw/master/images/python2.jpg" data-background-color="#001db3"--> ## Python 3.10 ---- ### Releases Notes https://docs.python.org/3/whatsnew/3.10.html ---- ### What's new ? - <!-- .element: class="fragment" -->Structural Pattern Matching - <!-- .element: class="fragment" -->Context managers grouping - <!-- .element: class="fragment" -->Improved error line numbering - <!-- .element: class="fragment" -->More typing - <!-- .element: class="fragment" -->More deprecations --- ### Structural pattern matching ---- #### The basic: matching literals ```python match http_error: case 400: ... case 401: ... case _: # Optional wildcard for remaining cases ... ``` ---- #### Matching sequences ```python match do_something(): case [toto]: # Single value case ["toto", titi]: print(titi) # Multiple values including both Literals and variables case [toto, *remaining]: print(remaining) # Handle variadic length ``` ---- #### Matching classes and their attributes ```python class Point: x: int y: int ``` <!-- .element: class="fragment" --> ---- ```python def location(point): match point: case Point(x=0, y=0): print("Origin is the point's location.") case Point(x=0, y=y): print(f"Y={y} and the point is on the y-axis.") case Point(x=x, y=0): print(f"X={x} and the point is on the x-axis.") case Point(): print("The point is located somewhere else on the plane.") case _: print("Not a point") ``` ---- #### Guard conditions ```python match point: case Point(x, y) if x == y: print(f"The point is located on the diagonal Y=X at {x}.") case Point(x, y): print(f"Point is not on the diagonal.") ``` ---- Ugly but allow arbitrary matching ```python match whatever: case _ if arbitrary condition: ... case _ if other condition: ... ``` But please don't do that 🙏🏼 <!-- .element: class="fragment" --> ---- #### ⚠ To match named constant they must be dotted ⚠ ---- This works ```python from enum import Enum class Color(Enum): RED = 0 match color: case Color.RED: print("I see red!") ``` ---- This don't ```python from enum import Enum class Color(Enum): RED = 0 RED = Color.RED match color: case RED: print("I see red!")I'm feeling the blues :(") ``` ---- #### Is it faster ? - <!-- .element: class="fragment" -->New syntax element with C-level optims - <!-- .element: class="fragment" -->No re-evaluation ---- #### No re-evaluation ? ```python class A: @property def heavy(self) -> Whatever: # Do something heavy ``` ```python if a.heavy == "something": ... elif a.heavy == "something else": ... # Computed as many times as tested cases before match ``` <!-- .element: class="fragment" --> ```python match a.heavy case "something": ... case "something else": ... # Computed once ``` <!-- .element: class="fragment" --> --- ### Context managers grouping ---- ```python with (open("a_really_long_foo") as foo, open("a_really_long_bar") as bar): pass ``` ``` Traceback (most recent call last): File "<input>", line 1, in <module> File "demo.py", line 19 with (open("a_really_long_foo") as foo, ^ SyntaxError: invalid syntax ``` <!-- .element: class="fragment" --> --- ### Improved debug line numbering ---- Unbreak the `pass` statement line numbering. Result in more precise numbering for some blocks and loops. See the [PEP examples](https://peps.python.org/pep-0626/#examples-of-code-for-which-the-sequence-of-trace-events-will-change) ---- [But also fixing/precising lots of annoying `SyntaxError` case](https://docs.python.org/3/whatsnew/3.10.html#better-error-messages) --- ### Union operator ---- #### Union type new syntax ```python # Python 3.9 from typing import Union class X: pass class Y: pass Z = Union[X, Y] ``` ```python # Python 3.10+ class X: pass class Y: pass Z = X | Y ``` <!-- .element: class="fragment" --> ---- #### Union type new syntax also replace Optional ```python # Python 3.9 from typing import Optional class X: z: Optional[Z] ``` ```python # Python 3.10+ class X: z: Z | None ``` <!-- .element: class="fragment" --> ---- #### Union operator works with `isinstance` and `issubclass` ```python isinstance(x, (Y, Z)) == isinstance(x, Y | Z) ``` --- ### Explicit Types Aliases ---- From implicit only ```python x = 1 # untyped global expression x: int = 1 # typed global expression x = int # type alias x: Type[int] = int # typed global expression ``` ---- To explicit ```python x = 1 # untyped global expression x: int = 1 # typed global expression x = int # untyped global expression (see note below) x: Type[int] = int # typed global expression x: TypeAlias = int # type alias x: TypeAlias = "MyClass" # type alias with forward reference # 👆🏼 This wasn't posssible before ``` --- ### --- <!-- .slide: data-background="https://github.com/noirbizarre/slides-paris.py/raw/master/images/python2.jpg" data-background-color="#001db3"--> ## Python 3.11 ---- ### Release notes https://docs.python.org/3.11/whatsnew/3.11.html ---- ### What's new ? - <!-- .element: class="fragment" -->tomllib - <!-- .element: class="fragment" -->Performances - <!-- .element: class="fragment" -->Exception groups and notes - <!-- .element: class="fragment" -->Fine-grained error locations in tracebacks - <!-- .element: class="fragment" -->More typing - <!-- .element: class="fragment" -->More deprecations --- ## tomllib A builtin minimalistic toml parsing library ---- - Read/parse only (enough to read configuration) - <!-- .element: class="fragment" -->Consistent with PEP621 & co. (pyproject.toml) - <!-- .element: class="fragment" -->Same API than the json module (load/loads) - <!-- .element: class="fragment" -->No more "no builtin toml support" excuse --- ## Exception groups and notes ---- Basically allows to wrap multiple exception within a single. Some new types: `BaseExceptionGroup` and `ExceptionGroup` <!-- .element: class="fragment" --> And a new keyword: `except*` <!-- .element: class="fragment" --> ---- ```python eg = ExceptionGroup( "one", [ TypeError(1), ExceptionGroup( "two", [TypeError(2), ValueError(3)] ), ExceptionGroup( "three", [OSError(4)] ) ] ) ``` ---- ```python >>> import traceback >>> traceback.print_exception(eg) | ExceptionGroup: one (3 sub-exceptions) +-+---------------- 1 ---------------- | TypeError: 1 +---------------- 2 ---------------- | ExceptionGroup: two (2 sub-exceptions) +-+---------------- 1 ---------------- | TypeError: 2 +---------------- 2 ---------------- | ValueError: 3 +------------------------------------ +---------------- 3 ---------------- | ExceptionGroup: three (1 sub-exception) +-+---------------- 1 ---------------- | OSError: 4 +------------------------------------ ``` ---- #### Catching from exception groups You can try manually catching in the group ```python try: . . . except ExceptionGroup as eg: eg = eg.subgroup(log_and_ignore_ENOENT) if eg is not None: raise eg ``` ---- or use the new `except*`statement ```python try: ... except* SpamError: ... except* FooError as e: ... except* (BarError, BazError) as e: ... ``` ---- More details in the [PEP 654](https://peps.python.org/pep-0654/) which is very detailed. ---- ### Notes ? ```python try: raise TypeError('bad type') except Exception as e: e.add_note('Add some information') raise ``` <!-- .element: class="fragment" --> ``` Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: bad type Add some information ``` <!-- .element: class="fragment" --> --- ## Fine-grained error locations in tracebacks ---- ### The classical ```python AttributeError: 'NoneType' object has no attribute 'x' ``` Which one is it ? ```python a = b.x + c.x + d.x + e.x ``` ---- ### Now you know ``` Traceback (most recent call last): File "distance.py", line 11, in <module> print(manhattan_distance(p1, p2)) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "distance.py", line 6, in manhattan_distance return abs(point_1.x - point_2.x) + abs(point_1.y - point_2.y) ^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'x' ``` --- ## Performances ---- ### Some benchmarks Benchmark from [Phoronix Python 3.11 review](https://www.phoronix.com/review/python-311-performance) <!-- .element: class="fragment" --> Details available on [OpenBenchmarking](https://openbenchmarking.org/result/2210257-NE-PYTHON31168) <!-- .element: class="fragment" --> ---- ![pyperf-logging](https://openbenchmarking.org/embed.php?i=2210257-NE-PYTHON31168&sha=6eca461f32af&p=2) ---- ![](https://openbenchmarking.org/embed.php?i=2210257-NE-PYTHON31168&sha=e186a4b35ec3&p=2) ---- ### Other benchmarks [Phoronix compare 3.11 beta to PyPy and Pyston](https://www.phoronix.com/review/python311-pyston-pypy) ---- ![](https://openbenchmarking.org/embed.php?i=2206071-PTS-PYTHON3176&sha=26ec59be2209&p=2) --- ## Self typing 🤩 ---- It was not possible to describe `self`-related and `cls`-related types ---- ```python class A: def clone(self) -> ???: pass ``` ```python class A: def clone(self) -> A: pass ``` <!-- .element: class="fragment" --> ```python class B(A): pass type(B.clone()) ? ``` <!-- .element: class="fragment" --> ---- ```python class A: @classmethod def factory(cls, *args, **kwargs) -> ???: return cls(*args, **kwargs) ``` ```python class A: @classmethod def factory(cls, *args, **kwargs) -> A: return cls(*args, **kwargs) ``` <!-- .element: class="fragment" --> ```python class B(A): pass type(B.factory()) ? ``` <!-- .element: class="fragment" --> ---- Now it is 🎉 ```python class A: def clone(self) -> Self: pass @classmethod def factory(cls, *args, **kwargs) -> Self: return cls(*args, **kwargs) ``` --- ## TypedDict required/optional keys ---- It was not possible mark individual items as optional or required ---- Either all is required ```python class AllIn(TypedDict): required: int maybe: str AllIn(42, "here") # OK AllIn(42) # KO AllIn(maybe="here") # KO AllIn() # KO ``` ---- Either none is ```python class NoneIn(TypedDict, total=False): required: int maybe: str NoneIn(42, "here") # OK NoneIn(42) # OK NoneIn(maybe="here") # OK NoneIn() # OK NoneIn(whatever="") # Also OK ``` ---- Now you can mark a field optional when other are required by default ```python class AllIn(TypedDict): required: int maybe: NotRequired[str] AllIn(42, "here") # OK AllIn(42) # OK AllIn(maybe="here") # KO AllIn() # KO ``` ---- And you can mark a field required when others are optionals ```python class NoneIn(TypedDict, total=False): required: Required[int] maybe: str NoneIn(42, "here") # OK NoneIn(42) # OK NoneIn(maybe="here") # KO NoneIn() # KO NoneIn(whatever="") # KO NoneIn(42, whatever="") # OK ``` ---- Don't mistake with allowing nullable fields ```python class Nullable(TypedDict): nullable: int | None ``` <!-- .element: class="fragment" --> This is already possible <!-- .element: class="fragment" --> --- ## `from __future__ import annotations` ---- Enable delayed typing and native generic types ---- Delayed typing ```python class A: def method(self) -> "A": pass ``` ```python class A: def method(self) -> A: pass ``` <!-- .element: class="fragment" --> ---- Delayed typing ```python from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from other import Circular class A: def method(self) -> Circular: pass ``` Fail without the first import ---- Native generic types ```python from typing import List, Dict X = List[Z] Y = Dict[Z, X] ``` ```python from __future__ import annotations X = list[Z] Y = dict[Z, X] ``` <!-- .element: class="fragment" --> ---- ### What happened ? - <!-- .element: class="fragment" -->Was targetting 3.10 - <!-- .element: class="fragment" -->Delayed for 3.11 - <!-- .element: class="fragment" -->Postponed until further discussion - <!-- .element: class="fragment" -->from __future__ import co_annotations - <!-- .element: class="fragment" -->Same behavior with different edge cases - <!-- .element: class="fragment" -->annotations is still the recommandation --- ## `typing_extension` Enable backport of types (only) <!-- .element: class="fragment" --> ---- This is working ```python # Python 3.9 from typing_extension import Self class A: def clone(self) -> Self: pass ``` --- <!-- .slide: data-background="https://github.com/noirbizarre/slides-paris.py/raw/master/images/python.jpg" --> # Questions ?
{"metaMigratedAt":"2023-06-17T12:47:33.339Z","metaMigratedFrom":"YAML","title":"The new snakes","breaks":true,"image":"https://www.python.org/static/community_logos/python-logo-master-v3-TM.png","slideOptions":"{\"theme\":\"moon\"}","contributors":"[{\"id\":\"ccb66f62-527e-4f67-aebf-99b8ba78ddd3\",\"add\":14592,\"del\":846}]"}
    150 views