<!-- .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" -->
----

----

----
### Other benchmarks
[Phoronix compare 3.11 beta to PyPy and Pyston](https://www.phoronix.com/review/python311-pyston-pypy)
----

---
## 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}]"}