¿Qué hay de nuevo en Python 3.11?

_images/python-logo.png

👋

Marzo 2020

Estamos de vuelta

>>> import datetime
>>> import humanize
>>> delta = datetime.datetime(2022, 10, 27) - datetime.datetime(2020, 3, 4)
>>> humanize.i18n.activate("es_ES")
>>> print(repr(humanize.precisedelta(delta)))
"2 años, 7 meses y 23 días"

Python Málaga

_images/python-malaga-logo.png

Sobre mí Nekmo

_images/cara.svg

Programando en Python desde 2006

Python 2.5

¿Qué hay de nuevo en Python 3.11?

Listado de cambios

  • PEP 654: Exception Groups y except*.

  • PEP 678: Enriquecer excepciones con notas.

  • PEP 680: tomllib.

  • PEP 657: Mejoras en las indicaciones de error en los tracebacks.

  • Opción -P en la línea de comandos y variable de entorno PYTHONSAFEPATH.

  • PEP 646: Variadic Generics.

  • PEP 655: TypedDict Required/NotRequired.

  • PEP 673: Tipo Self.

  • PEP 675: Tipo de cadena literal arbitraria

  • PEP 681: Data Class Transforms

  • Módulos obsoletos (PEP 594), Py_UNICODE API eliminada (PEP 624) y macros convertidas a funciones estáticas en línea (PEP 670).

Gracias

PEP 654: Exception Groups y except*

 class NameError(Exception):
     pass


 def validate_name(value: str) -> None:
     if not value.istitle():
         raise NameError("El nombre debe empezar por mayúscula.")


 form = {"name": "nekmo"}
 try:
     validate_name(form["name"])
 except NameError as err:
     print(err)  # Salta el error
_images/validacion-formulario.png
 from typing import Iterable, Tuple, Dict, Callable


 class NumberError(Exception):
     pass


 def validate_age(value: str) -> None:
     if not value.isdigit():
         raise NumberError("La edad debe ser un valor numérico.")


 form = {"name": "nekmo", "age": "diez"}
 form_validations = [("name", validate_name), ("age", validate_age)]
 exceptions = []

 for form_key, input_validation in input_validations:
     try:
         input_validation(form[form_key])
     except Exception as err:
         exceptions.append(value)
 if exceptions:
     raise ExceptionGroup("errors message", exceptions)
_images/json.png

except*

 try:
     read_inputs()
 except* NameError as eg:
     # Si hay errores NameError esto se llama
     print(f"Errores en el nombre: {eg.exceptions}")
 except* NumberError as eg:
     # Y si hay errores NumberError, esto también
     print(f"Errores numéricos: {eg.exceptions}")
raise ExceptionGroup("nested",
    [
         ValueError(654),
         ExceptionGroup("imports",
             [
                 ImportError("no_such_module"),
                 ModuleNotFoundError("another_module"),
             ]
         ),
         TypeError("int"),
     ]

¡Continuamos!

PEP 678: Enriquecer excepciones con notas

 try:
      raise TypeError('bad type')
 except Exception as e:
      e.add_note('¡Ah, ah, ah! ¡No has dicho la palabra mágica!')
      raise
 Traceback (most recent call last):
   File "<stdin>", line 2, in <module>
 TypeError: bad type
 ¡Ah, ah, ah! ¡No has dicho la palabra mágica!
+ Exception Group Traceback (most recent call last):
|   File "test.py", line 4, in test
|     def test(x):
|
|   File "hypothesis/core.py", line 1202, in wrapped_test
|     raise the_error_hypothesis_found
|     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ExceptionGroup: Hypothesis found 2 distinct failures.
+-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File "test.py", line 6, in test
    |     assert x > 0
    |     ^^^^^^^^^^^^
    | AssertionError: assert -1 > 0
    |
    | Falsifying example: test(
    |     x=-1,
    | )
    +---------------- 2 ----------------
    | Traceback (most recent call last):
    |   File "test.py", line 5, in test
    |     assert x < 0
    |     ^^^^^^^^^^^^
    | AssertionError: assert 0 < 0
    |
    | Falsifying example: test(
    |     x=0,
    | )
    +------------------------------------

PEP 657: Mejoras en las indicaciones de error en los tracebacks

 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'
 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'
 Traceback (most recent call last):
   File "query.py", line 37, in <module>
     magic_arithmetic('foo')
   File "query.py", line 18, in magic_arithmetic
     return add_counts(x) / 25
            ^^^^^^^^^^^^^
   File "query.py", line 24, in add_counts
     return 25 + query_user(user1) + query_user(user2)
                 ^^^^^^^^^^^^^^^^^
   File "query.py", line 32, in query_user
     return 1 + query_count(db, response['a']['b']['c']['user'], retry=True)
                                ~~~~~~~~~~~~~~~~~~^^^^^
 TypeError: 'NoneType' object is not subscriptable

PEP 680: Tomllib

Tom's Obvious, Minimal Language

_images/tom.png
# This is a TOML document

title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00

[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 }

[servers]

[servers.alpha]
ip = "10.0.0.1"
role = "frontend"
import tomllib


with open("fichero.toml") as f:
    tomllib.load(f)

Novedades en el tipado

_images/badum.png

PEP 646: Variadic Generics

 from typing import Tuple, Generic, TypeVarTuple, TypeVar


 T = TypeVar('T')
 Ts = TypeVarTuple('Ts')  # Esta es la novedad


 # Aquí usamos el TypeVarTuple como definición para el tipo
 class Array(Generic[*Ts]):
     def multiply(self, x: int) -> Tuple[*Ts]:  # Y aquí como return
         ...

     def add_dimension(self, t: T) -> Tuple[T, *Ts]:
         ...


 # Ts en este caso será [float, int, str]
 my_array: Array[float, int, str] = Array()
 my_array.multiply(2)  # El tipo devuelto será Tuple[float, int, str]
 # El tipo devuelto será Tuple[str, float, int, str]
 my_array.add_dimension("spam")

PEP 655: TypedDict Required/NotRequired

 from typing import TypedDict


 class Person(TypedDict, total=False):
     name: str  # Queremos que sea obligatorio pero no lo es
     surname: str  # Queremos que sea obligatorio pero no lo es
     age: int

 person: Person = {"name": "Juan", "surname": "Luna"}
 from typing import TypedDict


 class PersonRequired(TypedDict, total=True):
     name: str
     surname: str


 class Person(PersonRequired, total=False):
     age: int

 person: Person = {"name": "Juan", "surname": "Luna"}
 from typing import NotRequired, Required, TypedDict


 class Person(TypedDict):  # total=True por defecto
     name: str
     surname: str
     age: NotRequired[int]  # age no será requerido por el NotRequired[]


 person: Person = {"name": "Juan", "surname": "Luna"}
 from typing import NotRequired, Required, TypedDict


 class Person(TypedDict, total=False):
     name: Required[str]
     surname: Required[str]
     age: int


 person: Person = {"name": "Juan", "surname": "Luna"}

PEP 673: Tipo Self

 from typing import Self


 class Customer:

     def __init__(self, name: str, age: int):
         self.name = name
         self.age = age

     def __copy__(self) -> Self:
         return self.__class__(self.name, self.age)

PEP 675: LiteralString

 from typing import LiteralString, Iterable, Any


 def execute(sql: LiteralString, *params: Iterable[Any]):
     ...

# Esta línea validará, porque pasamos los parámetros de forma segura
execute("SELECT * FROM data WHERE user_id = ?", [123])

# Esta línea dará error, porque se modifica el string de entrada previamente
execute(f"SELECT * FROM data WHERE user_id = {user_id}")  # MEEH! Error.

Más seguridad

PEP 681: Data Class Transforms

# La clase ``ModelBase`` está definida en la biblioteca.
@typing.dataclass_transform()
class ModelBase: ...


# La clase ``ModelBase`` puede ser usado para crear nuevos modelos,
# similar a como se hace en estos frameworks.
class CustomerModel(ModelBase):
    id: int
def dataclass_transform(
    *,
    eq_default: bool = True,
    order_default: bool = False,
    kw_only_default: bool = False,
    field_specifiers: tuple[type | Callable[..., Any], ...] = (),
    **kwargs: Any,
) -> Callable[[_T], _T]: ...

¿Qué otras novedades hay?

  • Nuevo argumento -P en la línea de comandos y variable de entorno PYTHONSAFEPATH para evitar ejecutar código inseguro.

  • PEP 594: Eliminar módulos muertos de la librería estándar (deprecated, a eliminar en 3.13).

  • PEP 624: Eliminadas las APIs de codificación de Py_UNICODE.

  • PEP 670: Convertir macros a funciones en la API en C de Python.

  • ¡Y es más rápido! (10-60% respecto Python 3.10).

Más información

https://docs.python.org/3.11/whatsnew/3.11.html

¿Cómo puedo conseguirlo?

¡¡Ya disponible!!

¿Desde los fuentes?

https://www.build-python-from-source.com/

Python te necesita

¡Muchas gracias!

Referencias

¿Y la presentación?

github:Nekmo/python311-presentacion

Contactar