La forma de escribir comprobando 4 millones de líneas de código Python. Parte 3

Presentamos a su atención la tercera parte de la traducción del material en el camino que ha tomado Dropbox, presentando un sistema para verificar los tipos de código Python.



→ Partes anteriores: primera y segunda

Alcanzando 4 millones de líneas de código escrito


Otra tarea importante (este fue el segundo problema más popular que preocupó a quienes participaron en encuestas internas) fue aumentar la cantidad de código en Dropbox cubierto con verificaciones de tipo. Intentamos varios enfoques para resolver este problema, desde el crecimiento natural del volumen de la base de código escrito hasta el foco de los esfuerzos del equipo mypy en la inferencia de tipos automatizada estática y dinámica. Como resultado, parecía que no existe una estrategia ganadora simple, pero pudimos lograr un rápido crecimiento en el volumen de código anotado combinando muchos enfoques.

Como resultado, en nuestro repositorio más grande de Python (con código de fondo), el número de líneas de código anotado ha alcanzado casi 4 millones. El trabajo de tipeo estático del código se llevó a cabo en aproximadamente tres años. Mypy ahora admite varios tipos de informes de cobertura de código que facilitan el monitoreo del progreso de escritura. En particular, podemos generar informes sobre el código con incertidumbres en los tipos, como, por ejemplo, el uso explícito del tipo Any en anotaciones que no se pueden verificar, o como la importación de bibliotecas de terceros en las que no hay anotaciones de tipos. Como parte de un proyecto para aumentar la precisión de la verificación de tipos en Dropbox, hemos contribuido a mejorar las definiciones de tipo (los llamados archivos stub) para algunas bibliotecas populares de código abierto en el repositorio Python centralizado.

Hemos implementado (y estandarizado en PEP posteriores) nuevas características del sistema de tipos, que nos permiten utilizar tipos más precisos para algunos patrones específicos de Python. Un ejemplo notable de esto es TypeDict , que proporciona tipos para diccionarios similares a JSON que tienen un conjunto fijo de claves de cadena, cada uno de los cuales tiene un valor de su propio tipo. Continuaremos expandiendo el sistema de tipos. Probablemente nuestro próximo paso será mejorar el soporte para la capacidad de Python de trabajar con números.


Número de líneas de código anotado: servidor


Número de líneas de código anotado: cliente


El número total de líneas de código anotado.

Aquí hay una descripción general de las características principales de las acciones que realizamos para aumentar el volumen de código anotado en Dropbox:

El rigor de la anotación. Aumentamos gradualmente los requisitos para el rigor de anotar el nuevo código. Comenzamos con consejos de linter que sugerían agregar anotaciones a los archivos que ya tienen algunas anotaciones. Ahora requerimos anotaciones de tipo en los nuevos archivos de Python y en la mayoría de los archivos existentes.

Escribiendo informes. Enviamos informes semanales a los equipos sobre el nivel de mecanografía de su código y damos consejos sobre lo que debe anotarse en primer lugar.

Popularizando mypy. Hablamos sobre mypy en varios eventos y nos comunicamos con los equipos para ayudarlos a comenzar a usar anotaciones de tipo.

Encuestas Realizamos encuestas periódicas a los usuarios para identificar problemas importantes. Estamos listos para llegar lo suficientemente lejos como para resolver estos problemas (¡hasta crear un nuevo lenguaje para acelerar mypy!).

Rendimiento Hemos mejorado mucho el rendimiento de mypy mediante el uso del daemon y mypyc. Esto se hizo para suavizar los inconvenientes que surgen durante el proceso de anotación y para poder trabajar con grandes cantidades de código.

Integración con editores. Hemos creado herramientas para apoyar el lanzamiento de mypy en editores que son populares en Dropbox. Esto incluye PyCharm, Vim y VS Code. Esto simplificó enormemente el proceso de anotar el código y verificar su rendimiento. Dichas acciones suelen ser típicas cuando se anota código existente.

Análisis estático Creamos una herramienta para generar firmas de funciones utilizando herramientas de análisis estático. Esta herramienta solo puede funcionar en situaciones relativamente simples, pero nos ha ayudado a aumentar la cobertura de tipos con poco esfuerzo.

Soporte para bibliotecas de terceros. Muchos de nuestros proyectos utilizan el kit de herramientas SQLAlchemy. Utiliza las capacidades dinámicas de Python, que los tipos PEP 484 no pueden modelar directamente. De acuerdo con PEP 561, creamos el archivo stub correspondiente y escribimos un complemento para mypy ( código abierto ) que mejora el soporte de SQLAlchemy.

Las dificultades que encontramos


El camino a 4 millones de líneas de código escrito no siempre fue fácil para nosotros. En este camino nos encontramos con muchos agujeros y cometimos algunos errores. Estos son algunos de los problemas que hemos encontrado. Esperamos que la historia sobre ellos ayude a otros a evitar tales problemas.

Archivos omitidos Comenzamos comprobando solo una pequeña cantidad de archivos. Todo lo que no está incluido en el número de estos archivos no se verificó. Los archivos se agregaron a la lista de verificación cuando aparecieron las primeras anotaciones en ellos. Si algo se importó de un módulo ubicado fuera del alcance de la verificación, entonces estábamos hablando de trabajar con valores de tipo Any , que no se verificaron en absoluto. Esto condujo a una pérdida significativa de precisión de escritura, especialmente en las primeras etapas de la migración. Este enfoque hasta ahora ha funcionado sorprendentemente bien, aunque era típico que agregar archivos al área de escaneo revelara problemas en otras partes de la base del código. En el peor de los casos, cuando se combinaron dos áreas aisladas de código, en las cuales, independientemente uno del otro, los tipos ya estaban verificados, resultó que los tipos de estas áreas eran incompatibles entre sí. Esto hizo que fuera necesario realizar muchos cambios en las anotaciones. Ahora, mirando hacia atrás, entendemos que debemos agregar los módulos básicos de la biblioteca al área de verificación de tipo mypy lo antes posible. Esto haría nuestro trabajo mucho más predecible.

Anotando código antiguo. Cuando comenzamos, teníamos alrededor de 4 millones de líneas de código Python existente. Estaba claro que anotar todo este código no era tarea fácil. Creamos una herramienta llamada PyAnnotate, que puede recopilar información de tipo durante la ejecución de la prueba y puede agregar anotaciones de tipo al código en función de la información recopilada. Sin embargo, no notamos una introducción particularmente extendida de esta herramienta. Escribir información sobre los tipos era lento, las anotaciones autogeneradas a menudo requerían muchas ediciones manuales. Pensamos en lanzar automáticamente esta herramienta cada vez que revisa el código, o en recopilar información de tipo basada en un análisis de una pequeña cantidad de solicitudes de red reales, pero decidimos no hacerlo, porque cualquiera de estos enfoques es demasiado arriesgado.

Como resultado, se puede notar que la mayoría del código fue anotado manualmente por sus propietarios. Para dirigir este proceso en la dirección correcta, estamos preparando informes sobre módulos y funciones especialmente importantes que deben ser anotados. Por ejemplo, es importante proporcionar anotaciones de tipo con el módulo de biblioteca utilizado en cientos de lugares. Pero el antiguo servicio, que está siendo reemplazado por uno nuevo, ya no es tan importante. También estamos experimentando el uso de análisis estático para generar anotaciones de tipo para código antiguo.

Importaciones en bucle. Anteriormente, hablé sobre las importaciones cíclicas ("maraña de dependencias"), cuya existencia complicó la aceleración de mypy. Además, tuvimos que trabajar duro para proporcionar a mypy soporte para todo tipo de expresiones idiomáticas causadas por estas importaciones cíclicas. Recientemente completamos un importante proyecto de rediseño del sistema que solucionó la mayoría de los problemas de importación cíclica de mypy. Estos problemas, de hecho, surgieron desde los primeros días del proyecto, desde Alore, el lenguaje educativo al que mypy se orientó originalmente. La sintaxis de Alore facilita la resolución de los problemas de los comandos de importación cíclicos. Modern mypy ha heredado algunas limitaciones de su temprana implementación ingenua (que funcionó muy bien para Alore). Python hace que sea difícil trabajar con importaciones circulares, principalmente debido a la ambigüedad de las expresiones. Por ejemplo, durante una operación de asignación, se puede determinar un alias de tipo. Mypy no siempre puede detectar tales cosas hasta que se haya procesado la mayor parte del ciclo de importación. Alore no tenía tales ambigüedades. Las decisiones fallidas tomadas en las primeras etapas del desarrollo del sistema pueden presentar una sorpresa desagradable para un programador después de muchos años.

Resumen: el camino hacia 5 millones de líneas de código y nuevos horizontes


El proyecto mypy ha recorrido un largo camino: desde los primeros prototipos hasta un sistema que controla los tipos de código de producción con un volumen de 4 millones de líneas. A medida que mypy progresaba, las sugerencias de tipo se estandarizaron en Python. Un poderoso ecosistema ha evolucionado alrededor de escribir código Python en estos días. Encontró un lugar para soportar bibliotecas, contiene herramientas auxiliares para IDE y editores, tiene varios sistemas de control de tipo, cada uno de los cuales tiene sus pros y sus contras.

A pesar de que la verificación de tipos ya se da por sentado en Dropbox, estoy seguro de que todavía vivimos en los albores de escribir código Python. Creo que las tecnologías de verificación de tipos continuarán evolucionando y mejorando.

Si no ha utilizado verificaciones de tipo en su proyecto de Python a gran escala, debe saber que ahora es un buen momento para comenzar la transición a la escritura estática. Hablé con aquellos que hicieron una transición similar. Ninguno de ellos se arrepintió. El control de tipo convierte Python en un lenguaje que es mucho mejor que "Python normal" para desarrollar proyectos grandes.

Estimados lectores! ¿Utiliza el control de tipo en sus proyectos de Python?


Source: https://habr.com/ru/post/468237/


All Articles