El placer de escribir | Hacia la ciencia de datos

, como en la vida, es importante saber con qué estás trabajando. El sistema de tipos dinámicos de Python parece dificultar esto a primera vista. Un tipo es una promesa sobre los valores que un objeto puede contener y las operaciones que se le aplican: un número entero se puede multiplicar o comparar, una cadena concatenar, un diccionario indexado por clave. Muchos idiomas verifican estas promesas antes de ejecutar el programa. Rust and Go detecta discrepancias de tipos en el momento de la compilación y se niega a producir un binario ejecutable si fallan; TypeScript ejecuta sus comprobaciones durante un paso de compilación independiente. Python no realiza ninguna comprobación de forma predeterminada y las consecuencias se manifiestan en tiempo de ejecución.

En Python, un nombre se vincula únicamente a un valor. El nombre en sí no conlleva ningún compromiso sobre el tipo de valor y la siguiente asignación puede reemplazar el valor con uno de un tipo completamente diferente. Una función aceptará todo lo que le pases y devolverá lo que produzca su cuerpo; Si el tipo de cualquiera de los dos no es el que usted pretendía, el intérprete no lo dirá. La falta de coincidencia solo surge como una excepción más tarde, si es que aparece, cuando el código posterior realiza una operación que el tipo real no admite: aritmética en una cadena, una llamada a un método en el tipo incorrecto de objeto, una comparación que silenciosamente se evalúa como algo sin sentido. Esta indulgencia es a menudo, de hecho, una fortaleza: se adapta a la creación rápida de prototipos y al tipo de trabajo exploratorio basado en cuadernos donde la forma de un valor es algo que se descubre sobre la marcha. Pero en los flujos de trabajo de aprendizaje automático y ciencia de datos, donde los procesos son largos y un solo tipo inesperado puede interrumpir silenciosamente un paso posterior o producir resultados sin sentido, la misma flexibilidad se convierte en un serio inconveniente.

La respuesta de Python moderno a esto son las anotaciones de tipo. Agregadas a Python en la versión 3.5 a través de PEP 484, las anotaciones son una sintaxis para especificar los tipos que desea. Una función obtiene información de tipo adjuntándola a sus argumentos y devuelve el valor con dos puntos y una flecha:

def scale_data(x: flotante) -> flotante: devolver x * 2

La anotación no se aplica en tiempo de ejecución. Llamar a scale_data("123") no genera ningún error en el intérprete; la función concatena diligentemente la cadena consigo misma y devuelve "123123". Lo que detecta la discrepancia es una pieza de software separada, llamada verificador de tipo estático, que lee las anotaciones y las verifica antes de ejecutar el código:

scale_data(x="123") # ¡Error de tipo! Flotación esperada, tengo str

Las fichas estáticas muestran anotaciones de tipo directamente en el editor, marcando discrepancias a medida que escribe. Junto con herramientas establecidas como mypy y pyright, una nueva generación de verificadores basados ​​en Rust (ty de Astral, Pyrefly de Meta y Zuban, ahora de código abierto) están impulsando el rendimiento mucho más allá, haciendo factible el análisis completo de proyectos incluso en bases de código grandes. Este modelo está deliberadamente separado del tiempo de ejecución de Python. Las sugerencias de tipo son opcionales y la verificación se realiza antes de la ejecución y no durante la misma. Como dice PEP 484:

"Python seguirá siendo un lenguaje de escritura dinámica y los autores no desean que las sugerencias de escritura sean obligatorias, ni siquiera por convención".

La razón es tanto histórica como filosófica. Python creció como un lenguaje de tipado dinámico y, cuando llegó PEP 484, había décadas de código sin tipificar en libertad. Hacer que las pistas fueran obligatorias habría roto eso de la noche a la mañana.

Un verificador de tipos no ejecuta su programa ni exige la corrección de tipos mientras se ejecuta. En cambio, analiza el código fuente estáticamente, identificando lugares donde su código contradice su propia intención declarada. Algunas de estas discordancias eventualmente generarían excepciones, otras producirían silenciosamente resultados incorrectos. De cualquier manera, se vuelven visibles inmediatamente. Un argumento no coincidente que, de otro modo, podría aparecer horas después de la ejecución de una canalización, queda atrapado en el momento de escribir este artículo. Las anotaciones hacen explícitas las expectativas de una función: documentan sus entradas y salidas, reducen la necesidad de inspeccionar su cuerpo y fuerzan decisiones sobre casos extremos antes del tiempo de ejecución. Una vez que te acostumbres, agregar anotaciones tipográficas puede ser muy satisfactorio e incluso divertido.

Hacer explícita la estructura

Los diccionarios son el caballo de batalla del trabajo con datos de Python. Filas de un conjunto de datos, objetos de configuración, respuestas de API: todos representados habitualmente como dictados con claves y tipos de valores conocidos. TypedDict (PEP 589) proporciona una forma ligera de escribir dicho esquema:

al escribir import TypedDict clase SensorReading(TypedDict): marca de tiempo: temperatura flotante: presión flotante: ubicación flotante: str def Process_reading(lectura: SensorReading) -> float: lectura de retorno["temperatura"] * 1.8 + 32 # lectura de retorno["temp"] # Error de tipo: no existe tal clave

En tiempo de ejecución, SensorReading es simplemente un dictado normal sin sobrecarga de rendimiento. Pero su verificador de tipos ahora conoce el esquema, lo que significa que los errores tipográficos en los nombres de las claves se detectan inmediatamente en lugar de aparecer como KeyErrors en producción. El PEP destaca los objetos JSON como el caso de uso canónico. Esta es una razón más profunda por la que TypedDict es importante en el trabajo con datos: le permite describir la forma de los datos que no le pertenecen, como las respuestas que provienen de una API, las filas que llegan de un CSV o los documentos que extrae de una base de datos, sin tener que agruparlos primero en una clase. PEP 655 agregó NotRequired para campos opcionales y PEP 705 agregó ReadOnly para los inmutables, ambos útiles para estructuras anidadas de API o consultas de bases de datos. TypedDict tiene un tipo estructural en lugar de ser cerrado: de forma predeterminada, un dict puede contener claves adicionales que no enumeraste y aun así satisfacer el tipo, lo cual es una elección deliberada para la interoperabilidad, pero en ocasiones es sorprendente. PEP 728, aceptado en 2025 y dirigido a Python 3.15, le permite declarar un TypedDict con cerrado=True, lo que convierte cualquier clave no listada en un error de tipo.

Los valores categóricos son otro tipo de conocimiento implícito que el código de ciencia de datos lleva consigo constantemente. Métodos de agregación, especificaciones de unidades, nombres de modelos, indicadores de modo: a menudo estos se encuentran solo en cadenas de documentos y comentarios, donde el verificador de tipos no puede alcanzarlos. Los tipos literales (PEP 586) hacen explícito el conjunto de valores válidos:

al escribir import Literal def agregado_timeseries( datos: lista[float], método: Literal["mediana", "mediana", "max", "min"] ) -> flotante: if método == "media": return suma(datos) / len(datos) elif método == "mediana": retorno ordenado(datos)[len(datos) // 2] # etc. agregado_timeseries([1, 2, 3], "mediana") # fino agregado_timeseries([1, 2, 3], "promedio") # error de tipo: detectado antes del tiempo de ejecución

Una pequeña nota sobre la sintaxis. list[float] aquí está la forma moderna de lo que el código antiguo escribía como tipear.List[float]. PEP 585 (Python 3.9+) hizo que los tipos de colección estándar fueran genéricos, lo que significa que las funciones integradas en minúsculas ahora hacen el mismo trabajo sin necesidad de importarlas al escribir. Las versiones en mayúscula todavía funcionan, pero la mayoría del código moderno ha pasado a las formas en minúsculas, y los ejemplos de este artículo también lo hacen.

Volviendo a Literal, es más útil en lo profundo de una tubería, donde un error tipográfico como "temperatura" podría no generar una excepción pero producirá resultados silenciosamente incorrectos. Restringir los valores permitidos detecta estos errores temprano y hace explícitas las opciones válidas. Los IDE también pueden autocompletarlos, lo que reduce la fricción con el tiempo. A diferencia de la mayoría de los tipos, que describen un tipo de valor (cualquier cadena, cualquier número entero), Literal describe valores específicos. Es una forma sencilla de hacer que "esta debe ser una de estas opciones" forme parte de la firma de la función.

Cuando una estructura se vuelve lo suficientemente compleja como para que el tipo en sí sea difícil de leer en la firma de una función, los alias de tipo pueden aportar la concisión que tanto se necesita:

al escribir import TypeAlias # Sin alias def process_results( data: dict[str, list[tuple[float, float, str]]] ) -> list[tuple[float, str]]: … # Con alias Coordinate: TypeAlias = tuple[float, float, str] # lat, lon, label LocationData: TypeAlias = dict[str, list[Coordinate]] ProcessedResult: TypeAlias = lista[tuple[float, str]] def Process_results(datos: LocationData) -> ProcessedResult: …

Un alias también puede documentar claramente lo que representa la estructura, no solo de qué tipos de Python se compone. Esto da sus frutos cuando alguien intenta leer el código seis meses después (¡y ese alguien frecuentemente serás tú!).

Hacer la elección explícita

Los datos reales y las API reales rara vez ofrecen un tipo y solo un tipo. Una función puede aceptar un nombre de archivo o un identificador de archivo abierto. Un valor de configuración puede ser un número o una cadena. Un campo faltante puede ser un valor o Ninguno. Los tipos de unión te permiten decirlo directamente:

al escribir import TextIO def load_data(fuente: str | TextIO) -> lista[str]: if isinstance(source, str): con open(source) como f: return f.readlines() else: return source.readlines()

El | La sintaxis fue agregada por PEP 604 y está disponible en Python 3.10. El código anterior usa Union[str, TextIO] del módulo de escritura, lo que significa exactamente lo mismo.

Por cierto margen, la unión más común es aquella en la que Ninguno es una de las alternativas. Las mediciones fallan, los sensores aún no están instalados, las API devuelven respuestas incompletas y una función que devuelve un resultado o nada está en todas partes en el trabajo de datos. La forma moderna de escribirlo es float | Ninguno:

def calcular_eficiencia(combustible_consumido: flotante | Ninguno) -> flotante | Ninguno: si fuel_consumed es Ninguno: devolver Ninguno devolver 100.0 / combustible_consumido

El verificador de tipos ahora marcará cualquier código que intente usar el valor de retorno como un valor flotante definido sin verificar primero Ninguno, lo que evita una gran clase de TypeError: tipos de operandos no admitidos que de otro modo habrían aparecido en tiempo de ejecución.

Una sintaxis más antigua, Opcional[float], significa exactamente lo mismo que float | Ninguno y aparece en todas partes en el código anterior a la 3.10. Sin embargo, vale la pena detenerse en el nombre porque es fácil de malinterpretar. Parece que describe un argumento opcional, uno que puede omitir en una llamada, pero en realidad describe un valor opcional: la anotación permite Ninguno además del tipo con nombre. Estas son propiedades diferentes y ambas existen en Python:

def f(x: int = 0): # el argumento es opcional; el valor es *no* Opcional def f(x: int | Ninguno): # argumento es obligatorio; el valor es opcional def f(x: int | Ninguno = Ninguno): # ambos

La mala interpretación fue lo suficientemente grave como para dar forma a PEP posteriores. PEP 655, cuando agregó NotRequired para claves potencialmente faltantes en un TypedDict, consideró y rechazó reutilizar la palabra Opcional con el argumento de que sería demasiado fácil confundirla con el significado existente. La X | Ninguna sintaxis evita por completo el problema.

Una vez que haya declarado un parámetro como float | Ninguno, el verificador de tipos se vuelve preciso sobre lo que se puede hacer con el valor. Dentro de una rama si el valor es Ninguno, el verificador sabe que el valor es Ninguno; en la rama else, sabe que el valor es flotante. El mismo "estrechamiento de tipo" ocurre después de que un valor de afirmación no sea Ninguno, un aumento anticipado o cualquier otra verificación que descarte una de las alternativas.

def calcular_eficiencia(fuel_consumed: float | Ninguno) -> float: si fuel_consumed es Ninguno: elevar ValueError("fuel_consumed es necesario") # Dentro de este bloque, el verificador de tipo sabe que fuel_consumed es float return 100.0 / fuel_consumed

Cuando el verificador realmente no puede determinar un tipo, escribiendo.cast() le permite anularlo. El caso más común son los valores que llegan desde fuera del sistema de tipos. Por ejemplo, json.loads() está anotado para devolver Cualquiera, porque puede producir combinaciones anidadas arbitrariamente de dictados, listas, cadenas, números y Ninguno, según la entrada. Si conoce la forma esperada de los datos, Cast le permite afirmar ese conocimiento al verificador:

al escribir import cast raw = json.loads(payload) user_id = cast(int, raw["user_id"]) # El verificador de tipos ahora trata user_id como un int.

cast no convierte el valor ni lo verifica en tiempo de ejecución; simplemente le dice al verificador de tipos que trate la expresión como un tipo determinado. Si raw["user_id"] es en realidad una cadena o Ninguno, el código continuará sin quejas y fallará más tarde, como si no hubiera habido ninguna anotación. Por esa razón, el uso frecuente de cast o # type: ignore suele ser una señal de que la información de tipo se está perdiendo en sentido ascendente y, en su lugar, debe hacerse explícita.

Hacer explícito el comportamiento

El trabajo con datos implica pasar funciones como argumentos constantemente. GridSearchCV de Scikit-learn adopta una función de puntuación. Los optimizadores de PyTorch utilizan programadores de tasa de aprendizaje. pandas.DataFrame.groupby().apply() toma cualquier función de agregación que le entregue. Los pipelines locales a menudo componen pasos de preprocesamiento o transformación como una lista de funciones que se aplicarán en secuencia. Sin anotaciones, una firma como def build_pipeline(steps): no dice nada sobre cómo deberían verse los pasos, y el lector tiene que adivinar a partir del cuerpo qué forma de función funcionará.

Callable te permite especificar qué argumentos toma una función y qué devuelve:

al escribir import Callable # Un paso de preprocesamiento: toma una lista de flotantes, devuelve una lista de flotantes Preprocesador = Callable[[list[float]], list[float]] def build_pipeline(steps: list[Preprocessor]) -> Preprocesador: def pipeline(x: list[float]) -> list[float]: para paso a paso: x = paso(x) return x return pipeline

La forma general es Callable[[Arg1Type, Arg2Type, …], ReturnType]. Cuando realmente no te importan los argumentos y solo importa el tipo de retorno, Callable[…, ReturnType] acepta cualquier firma, lo que ocasionalmente es útil para interfaces de complementos, aunque la mayoría de las veces el objetivo es ser específico. Callable tiene límites. No puede expresar argumentos de palabras clave, valores predeterminados ni firmas sobrecargadas. Cuando necesite escribir un invocable con ese nivel de detalle, Protocol puede hacer el trabajo definiendo un método __call__. Pero para el caso abrumadoramente común de "una función que toma X y devuelve Y", Callable es la herramienta adecuada y lee limpiamente la firma.

La escritura pato es una de las cosas que hace que Python se sienta fluido: si un objeto tiene los métodos correctos, puede usarse en un contexto determinado independientemente de su jerarquía de herencia. El problema es que esta fluidez desaparece en la firma de la función. Sin sugerencias de tipo, una firma como def proceso(datos): no le dice nada al lector sobre qué operaciones deben soportar los datos. Una firma escrita que utiliza una clase concreta como def proceso (datos: pd.Series): descarta matrices NumPy y listas simples, incluso si la implementación las aceptaría felizmente.

El Protocolo (PEP 544) resuelve esto escribiendo estructuralmente en lugar de nominalmente. El verificador de tipos decide si un objeto satisface un protocolo inspeccionando sus métodos y atributos, no recorriendo su cadena de herencia. El objeto nunca tiene que heredar de nada, ni siquiera saber que existe el Protocolo.

al escribir import Clase de protocolo Summable(Protocolo): def suma(self) -> float: … def __len__(self) -> int: … def calcular_mean(datos: Summable) -> float: return data.sum() / len(data) importar pandas como pd importar numpy como np calcular_mean(pd.Series([1, 2, 3])) # ✓ verificaciones de tipo calcular_mean(np.array([1, 2, 3])) # ✓ comprobaciones de tipo calcular_mean([1, 2, 3]) # ✗ error de tipo: las listas no tienen .sum()

pd.Series no hereda de Summable, ni tampoco np.ndarray. Satisfacen el protocolo porque tienen un método de suma y admiten len(). Una lista simple de Python no lo hace, ya que la suma en una lista es una función libre en lugar de un método, y el verificador de tipos detecta esa distinción con precisión. El cambio de tipificación nominal a estructural es pequeño en sintaxis y sustancial en espíritu. Los tipos nominales describen qué es un objeto; Los tipos estructurales describen lo que puede hacer. El protocolo le permite preguntar si un objeto puede hacer algo, que casi siempre es la pregunta que importa en el trabajo con datos, sin comprometerse con lo que es.

Vale la pena conocer dos puntos prácticos. La biblioteca estándar ya incluye muchos de los protocolos que realmente desearía, en collections.abc y escribiendo: Iterable, Sized, Hashable, SupportsFloat y una larga lista además. Te encontrarás importándolos con mucha más frecuencia que definiendo los tuyos propios. El otro punto tiene que ver con el comportamiento en tiempo de ejecución: los protocolos se borran de forma predeterminada, lo que significa que isinstance(x, Summable) se activará a menos que el protocolo esté decorado con @runtime_checkable. El valor predeterminado refleja una compensación deliberada, ya que las comprobaciones estructurales en tiempo de ejecución son lentas y el diseño supone que la mayoría de los usos se realizan en el momento de la verificación de tipos. Cuando necesitas una instancia contra un Protocolo, el decorador es de una sola línea y el costo se paga solo cuando lo solicitas.

La ciencia de datos se trata en gran medida de transformaciones, y una transformación bien tipificada preserva la información sobre lo que fluye a través de ella. El desafío es expresar “cualquier tipo que entre, sale el mismo tipo” sin recurrir a Cualquiera, que simplemente desactiva el verificador de tipos para esa variable. TypeVar es la construcción que aborda esto:

al escribir import TypeVar T = TypeVar('T') def first_element(items: list[T]) -> T: devolver elementos[0]x: int = first_element([1, 2, 3]) # ✓ x es int y: str = first_element(["a", "b", "c"]) # ✓ y es str z: str = first_element([1, 2, 3]) # ✗ error de tipo: devuelve int, no str

T es una variable de tipo: un marcador de posición que el verificador resuelve en un tipo concreto en el sitio de llamada. Llamar a first_element([1, 2, 3]) vincula T a int para esa llamada, y la anotación de retorno T se lee como int en consecuencia. Llámelo con una lista de cadenas y T se convierte en cadena. El vínculo entre entrada y salida se conserva sin comprometer la función a ningún tipo en particular. Una vez que tenga una manera de decir "el tipo que entró es el tipo que sale", elegir Cualquiera se convierte en una admisión visible en lugar de un valor predeterminado. La escritura genérica lo empuja, suavemente, hacia funciones de escritura que realmente conservan su forma de entrada, en lugar de funciones que la pierden silenciosamente en algún punto intermedio.

Para las etapas de canalización reutilizables, esto se extiende naturalmente a las clases genéricas:

al escribir import Generic, Callable T = TypeVar('T') class DataBatch(Generic[T]): def __init__(self, items: list[T]) -> Ninguno: self.items = items def map(self, func: Callable[[T], T]) -> "DataBatch[T]": return DataBatch([func(item) for item in self.items]) def get(self, index: int) -> T: devuelve self.items[index] lote: DataBatch[float] = DataBatch([1.0, 2.0, 3.0]) valor: float = lote.get(0) # el verificador de tipo sabe que esto es float

Los TypeVars completamente sin restricciones son más raros en la práctica de lo que cabría esperar. A menudo desea decir "cualquier tipo numérico" o "uno de estos tipos específicos", y TypeVar admite ambos: TypeVar('N',bound=Number) acepta Número y cualquiera de sus subtipos, mientras que TypeVar('T', int, float) acepta solo los tipos enumerados. La mayor parte del tiempo consumirá genéricos en lugar de escribirlos, ya que las bibliotecas de las que depende hacen el trabajo pesado: list[T] es genérico en su tipo de elemento, y las funciones de matriz escrita de NumPy (NDArray[np.float64] y amigos) son genéricas en su tipo de elemento. Pero cuando estás escribiendo utilidades reutilizables, particularmente cualquier cosa que envuelva o agrupe datos, usar TypeVar es lo que permite que el envoltorio sea transparente para quien lo use posteriormente.

La depuración de genéricos puede ser opaca, ya que la T inferida no es visible en el sitio de la llamada. La mayoría de los verificadores de tipos admiten reveló_tipo (x), que imprime el tipo inferido en el momento de la verificación de tipos:

lote = DataBatch([1.0, 2.0, 3.0]) revelar_tipo(lote) # tipo de verificador imprime: DataBatch[float]

Es la forma más rápida de comprender que un error tipográfico aparece donde no lo espera.

Consideraciones prácticas

A pesar de sus numerosos beneficios, las anotaciones tienen límites. El sistema de tipos no puede expresar todo lo que Python puede hacer: los marcos dinámicos, los decoradores que cambian las firmas de funciones y la metaprogramación de estilo ORM se encuentran de manera incómoda dentro de él, y las bibliotecas que se apoyan en estos patrones a menudo necesitan paquetes de códigos auxiliares de tipos y complementos de verificación separados (django-stubs, sqlalchemy-stubs) para ser verificados. Las anotaciones también añaden gastos generales. El verificador de tipos a veces no estará de acuerdo con el código que usted sabe que es correcto, y el tiempo dedicado a persuadirlo es tiempo que no estaba dedicando al problema real. # tipo: ignorar se acumula en bases de código reales por razones honestas, a menudo porque los tipos de una biblioteca ascendente están incompletos o son inexactos.

Incluso su propio código rara vez se escribirá por completo, y eso está bien. PEP 561 estableció dos formas oficiales para que las bibliotecas envíen información tipográfica, ya sea en línea con un marcador py.typed o como un paquete foopkg-stubs separado. NumPy envía sus anotaciones en línea; pandas los distribuye como pandas-stubs. Ambos proyectos han anotado sus API públicas, pero reconocen abiertamente lagunas: el archivo README de pandas-stubs señala que los stubs "probablemente están incompletos en términos de cubrir la API publicada", y la cobertura completa de la última versión de pandas aún está en progreso. La misma dinámica se desarrolla en su propio código base. La cobertura comienza siendo estrecha y crece donde el valor es más alto.

Una respuesta sensata es elegir tus batallas. Comience con las funciones en las que existe mayor incertidumbre sobre lo que ingresa, como las respuestas API o cualquier cosa que se lea desde una base de datos. La cobertura crece hacia afuera a partir de ahí. El mismo gradiente se aplica a cuán estrictamente el verificador aplica sus anotaciones; La verificación básica detecta discrepancias obvias, mientras que los modos más estrictos pueden requerir anotaciones en cada función y rechazar tipos Any implícitos. Mypy, de forma predeterminada, omite funciones que no tienen ninguna anotación, lo que significa que la sorpresa más común entre los nuevos usuarios es habilitar la herramienta y descubrir que no tiene nada que decir sobre el código que aún no han anotado. Pyright y los nuevos verificadores basados ​​en Rust verifican el código no anotado de forma predeterminada, aunque los usuarios de mypy pueden obtener el mismo comportamiento configurando –check-untyped-defs. Cualquiera que sea el nivel que elija, la integración continua (CI) es el lugar natural para aplicarla, ya que una verificación de cada confirmación detecta los errores antes de que lleguen a la rama principal y establece un estándar único para el equipo.

Contra los costos hay victorias concretas. Una clave incorrecta en un TypedDict se detecta al presionar la tecla en lugar de ser un KeyError días después. Una firma de función con tipos le dice al siguiente lector lo que espera sin que tenga que leer el cuerpo. Saber cuándo y cómo agregar mejor anotaciones es un oficio y, como cualquier oficio, recompensa la práctica. Si se usan bien, las anotaciones de tipo convierten las suposiciones sobre su código en cosas que el verificador puede verificar, haciendo su vida más fácil y más segura en el proceso. ¡Feliz escritura!

Referencias

[1]G. van Rossum, J. Lehtosalo y Ł. Langa, PEP 484: Sugerencias de tipo (2014), Propuestas de mejora de Python

[2]E. Smith, PEP 561: Información sobre tipos de distribución y embalaje (2017), Propuestas de mejora de Python

[3]Ł. Langa, PEP 585: Genéricos de sugerencias de tipo en colecciones estándar (2019), propuestas de mejora de Python

[4]J. Lehtosalo, PEP 589: TypedDict: sugerencias de escritura para diccionarios con un conjunto fijo de claves (2019), propuestas de mejora de Python

[5]D. Foster, PEP 655: Marcar elementos individuales de TypedDict como necesarios o potencialmente faltantes (2021), Propuestas de mejora de Python

[6]A. Purcell, PEP 705: TypedDict: elementos de solo lectura (2022), propuestas de mejora de Python

[7]ZJ Li, PEP 728: TypedDict con elementos adicionales escritos (2023), propuestas de mejora de Python

[8]M. Lee, I. Levkivskyi y J. Lehtosalo, PEP 586: Tipos literales (2019), Propuestas de mejora de Python

[9]P. Prados y M. Moss, PEP 604: Permitir escribir tipos de unión como X | Y (2019), Propuestas de mejora de Python

[10]I. Levkivskyi, J. Lehtosalo y Ł. Langa, PEP 544: Protocolos: subtipificación estructural (tipificación estática de pato) (2017), propuestas de mejora de Python