Hola a todos! Hoy les contaré la historia del desarrollo de la mecanografía en el ejemplo de uno de los proyectos en
Ostrovok.ru .

Esta historia comenzó mucho antes del
bombo publicitario en
python3.5 , además, comenzó dentro de un proyecto escrito en
python2.7 .
2013 : recientemente se lanzó
Python3.3 , no tenía sentido migrar a la nueva versión, ya que no agregaba ninguna característica específica, y habría mucho dolor y sufrimiento durante la transición.
Estuve involucrado en el proyecto Partners en Ostrovok.ru: este servicio fue responsable de todo lo relacionado con las integraciones de socios, reservas, estadísticas y una cuenta personal. Utilizamos tanto API internas para otros microservicios de la empresa como una API externa para nuestros socios.
En algún momento, el equipo formó el siguiente enfoque para escribir controladores HTTP o algún tipo de lógica empresarial:
1) los datos de entrada y salida deben ser descritos por una estructura (clase),
2) los contenidos de instancias de estructuras deben validarse de acuerdo con la descripción,
3) una función que toma una estructura en la entrada y le da la estructura en la salida debe verificar los tipos de datos en la entrada y la salida, respectivamente.
No me detendré en cada punto en detalle, el siguiente ejemplo debería ser suficiente para comprender lo que está en juego.
Ejemplo.
import datetime as dt from contracts import new_contract, contract from schematics.models import Model from schematics.types import IntType, DateType
El ejemplo utiliza bibliotecas:
esquemas y
pycontracts .
*
esquemas : una forma de describir y validar datos.
*
pycontracts : una forma de verificar la entrada / salida de una función en tiempo de ejecución.
Este enfoque le permite:
- es más fácil escribir pruebas: no surgen problemas con la validación y solo se cubre la lógica empresarial.
- Para garantizar el formato y la calidad de la respuesta en la API, aparece un marco rígido para lo que estamos dispuestos a aceptar y lo que podemos dar.
- Es más fácil entender / refactorizar el formato de respuesta si se trata de una estructura compleja con diferentes niveles de anidamiento.
Es importante comprender que la verificación de tipos (no validación) solo funciona en
tiempo de
ejecución , y esto es conveniente para el desarrollo local, ejecutar pruebas en CI y verificar la liberación de un candidato en un entorno de
ensayo . En un entorno de producción, esto debe deshabilitarse; de lo contrario, el servidor se ralentizará.
Pasaron los años, nuestro proyecto creció, apareció una lógica comercial más nueva y compleja, al menos el número de identificadores API no disminuyó.
En algún momento, comencé a notar que el lanzamiento del proyecto ya tomó unos segundos notables, esto fue molesto, porque cada vez que editaba el código y ejecutaba las pruebas tenía que sentarme y esperar mucho tiempo. Cuando esta espera comenzó a tomar 8-10 segundos, finalmente decidimos averiguar qué estaba pasando debajo del capó.
De hecho, todo resultó ser bastante simple. Al comenzar un proyecto, la biblioteca pycontracts analiza todas las
cadenas de documentos que están cubiertas por
@contract para registrar todas las estructuras en la memoria y luego verificarlas correctamente. Cuando el número de estructuras en un proyecto asciende a miles, todo esto comienza a disminuir.
¿Qué hacer al respecto? La respuesta correcta es buscar otras soluciones, afortunadamente en el patio ya es
2018 (
python3.5 -
python3.6 ), y ya hemos migrado nuestro proyecto a
python3.6 .
Comencé a estudiar soluciones alternativas y a pensar en cómo migrar un proyecto de “
pycontracts + type description in
docstring ” a “something + type description in
typing annotation ”. Resultó que si actualiza
pycontracts a la última versión, puede describir los tipos en el estilo de
anotación de mecanografía , por ejemplo, podría verse así:
@contract def get_order_info(data_in: OrderInfoData) -> OrderInfoResult: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Los problemas comienzan si necesita usar estructuras de
mecanografía , por ejemplo,
Opcional o
Unión , ya que
pycontracts NO sabe cómo trabajar con ellas:
from typing import Optional @contract def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Empecé a buscar bibliotecas alternativas para la verificación de tipos en
tiempo de
ejecución :
*
hacer cumplir*
typeguard*
pytypesEnforce en ese momento no era compatible con
python3.7 , pero ya lo hemos actualizado, a
pytypes no le gustó la sintaxis, como resultado, la elección recayó en
typeguard .
from typeguard import typechecked @typechecked def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Aquí hay ejemplos de un proyecto real:
@typechecked def view( request: HttpRequest, data_in: AffDeeplinkSerpIn, profile: Profile, contract: Contract, ) -> AffDeeplinkSerpOut: ... @typechecked def create_contract( user: Union[User, AnonymousUser], user_uid: Optional[str], params: RegistrationCreateSchemaIn, account_manager: Manager, support_manager: Manager, sales_manager: Optional[Manager], legal_entity: LegalEntity, partner: Partner, ) -> tuple: ... @typechecked def get_metaorder_ids_from_ordergroup_orders( orders: Tuple[OrderGroupOrdersIn, ...], contract: Contract ) -> list: ...
Como resultado, después de un largo proceso de refactorización, pudimos transferir completamente el proyecto a
anotaciones de tipeo +
tipeo .
Qué resultados hemos logrado:
- El proyecto comienza en 2-3 segundos, lo que al menos no es molesto.
- la legibilidad del código ha mejorado.
- El proyecto se ha reducido tanto en el número de líneas como en los archivos, ya que no hay más registros de estructura a través de @new_contract .
- Los IDE inteligentes de PyCharm se han vuelto mejores para indexar un proyecto y hacer diferentes sugerencias, porque ahora no son comentarios, sino importaciones honestas.
- Puede usar analizadores estáticos como mypy y pyre-check , ya que admiten trabajar con anotaciones de tipeo .
- La comunidad de Python en su conjunto se está moviendo hacia la escritura de una forma u otra, es decir, las acciones actuales son inversiones en el futuro del proyecto.
- a veces hay problemas con las importaciones cíclicas, pero hay pocos, y pueden ser descuidados.
¡Espero que este artículo te sea útil!
Referencias
*
hacer cumplir*
typeguard*
pytypes*
pycontracts*
esquemas