Apresentamos a você a terceira parte da tradução do material sobre o caminho que a empresa Dropbox percorreu ao implementar um sistema de verificação de tipos no código Python.
→ Partes anteriores: e
Alcançando 4 milhões de linhas de código tipadas
Outra tarefa importante (esta era a segunda questão mais popular que preocupava os participantes das pesquisas internas) foi aumentar o volume de código no Dropbox coberto por verificações de tipos. Experimentamos várias abordagens para resolver essa questão — desde o crescimento natural do volume da base de código tipada até a concentração dos esforços da equipe mypy em dedução de tipos estática e dinâmica automatizada. Como resultado, pareceu que não havia uma estratégia vencedora simples, mas conseguimos alcançar um rápido crescimento no volume de código anotado, combinando várias abordagens.
Como resultado, em nosso maior repositório Python (com código de backend), o número de linhas de código anotado quase alcançou 4 milhões. O trabalho na tipagem estática do código foi realizado em cerca de três anos. O mypy agora suporta vários tipos de relatórios de cobertura de tipos de código que facilitam o monitoramento do progresso da tipagem. Especificamente, podemos gerar relatórios para código com incertezas de tipos, como por exemplo, o uso explícito de tipo Any em anotações que não podem ser verificadas, ou como no caso da importação de bibliotecas de terceiros que não possuem anotações de tipos. Como parte de um projeto para melhorar a precisão da verificação de tipos no Dropbox, contribuímos para aprimorar definições de tipos (os chamados arquivos stub) para algumas bibliotecas open-source populares em um repositório Python centralizado .
Implementamos (e padronizamos nas PEP subsequentes) novos recursos do sistema de tipos que permitem usar tipos mais precisos para alguns padrões específicos do Python. Um exemplo notável disso é TypeDict, que fornece tipos para dicionários semelhantes a JSON que possuem um conjunto fixo de chaves de string, cada uma com um valor de seu próprio tipo. Continuaremos a expandir o sistema de tipos. Provavelmente, nosso próximo passo será melhorar o suporte para as capacidades do Python relacionadas a números.

Número de linhas de código anotado: servidor

Número de linhas de código anotado: cliente

Total de linhas de código anotado
Aqui está uma visão geral das principais características das ações que realizamos para aumentar a quantidade de código anotado no Dropbox:
Rigor da anotação. Aumentamos gradualmente as exigências de rigor na anotação do novo código. Começamos com sugestões de linters, que recomendavam adicionar anotações em arquivos que já tinham algumas anotações. Agora, exigimos a presença de anotações de tipo em novos arquivos Python e na maioria dos arquivos existentes.
Relatórios de tipagem. Enviamos semanalmente às equipes relatórios sobre o nível de tipagem de seu código e damos conselhos sobre o que deve ser anotado primeiro.
Promoção do mypy. Estamos falando sobre o mypy em diversos eventos e interagindo com as equipes, ajudando-as a começar a usar anotações de tipos.
Pesquisas. Realizamos pesquisas periódicas com usuários para identificar os principais problemas. Estamos dispostos a ir longe na resolução desses problemas (até mesmo criando uma nova linguagem para acelerar o mypy!).
Desempenho. Melhoramos significativamente o desempenho do mypy por meio do uso de daemon e mypyc. Isso foi feito para suavizar as dificuldades que surgem durante a anotação e para permitir o trabalho com grandes volumes de código.
Integração com editores. Criamos ferramentas para suportar a execução do mypy em editores populares do Dropbox. Isso inclui PyCharm, Vim e VS Code. Isso facilitou significativamente o processo de anotação do código e verificação de sua funcionalidade. Essas ações são geralmente características ao anotar código existente.
Análise estática. Criamos uma ferramenta para gerar assinaturas de funções usando ferramentas de análise estática. Essa ferramenta pode funcionar apenas em situações relativamente simples, mas nos ajudou a aumentar a cobertura de tipos no código sem muito esforço.
Suporte para bibliotecas de terceiros. Muitos de nossos projetos utilizam o conjunto de ferramentas SQLAlchemy. Ele utiliza as capacidades dinâmicas do Python, que os tipos PEP 484 não conseguem modelar diretamente. De acordo com o PEP 561, criamos o arquivo stub correspondente e escrevemos um plugin para o mypy.), melhorando o suporte ao SQLAlchemy.
Desafios que encontramos
O caminho até 4 milhões de linhas de código tipificado nem sempre foi fácil. Ao longo dessa jornada, encontramos vários obstáculos e cometemos alguns erros. Aqui estão alguns dos problemas que enfrentamos. Esperamos que este relato ajude outros a evitar problemas semelhantes.
Arquivos ausentes. Começamos nosso trabalho com a verificação de um pequeno volume de arquivos. Tudo o que não estava nessa lista não era checado. Arquivos eram adicionados à lista de verificação assim que as primeiras anotações apareciam. Se algo era importado de um módulo fora da área de verificação, estávamos lidando com valores do tipo Any, que não eram verificados. Isso levou a uma significativa perda de precisão na tipificação, especialmente nas fases iniciais da migração. Esse método ainda funcionava surpreendentemente bem, embora fosse comum que a adição de arquivos à área de verificação revelasse problemas em outras partes da base de código. No pior dos casos, quando duas áreas de código isoladas, nas quais os tipos foram verificados independentemente, eram combinadas, descobria-se que os tipos dessas áreas eram incompatíveis. Isso resultava na necessidade de fazer muitas alterações nas anotações. Agora, olhando para trás, percebemos que deveríamos ter incluído os módulos de biblioteca básica na área de verificação de tipos mypy o mais cedo possível. Isso teria tornado nosso trabalho muito mais previsível.
Anotação de código antigo. Quando começamos nosso trabalho, tínhamos cerca de 4 milhões de linhas de código Python já existente. Ficou claro que a anotação de todo esse código era uma tarefa difícil. Criamos uma ferramenta chamada PyAnnotate, que pode coletar informações de tipos durante a execução dos testes e adicionar anotações de tipos ao código com base nas informações coletadas. No entanto, não percebemos uma adoção muito ampla dessa ferramenta. A coleta de informações de tipos era lenta e as anotações geradas automaticamente frequentemente exigiam muitas correções manuais. Pensamos em iniciar automaticamente essa ferramenta a cada verificação de código ou em coletar informações de tipos com base na análise de um pequeno volume de solicitações de rede reais, mas decidimos não fazê-lo, pois qualquer uma dessas abordagens era muito arriscada.
No final, podemos notar que a maior parte do código foi anotaada manualmente por seus proprietários. Para direcionar esse processo na direção certa, estamos preparando relatórios sobre módulos e funções especialmente importantes que precisam ser anotados. Por exemplo, é importante fornecer anotações de tipos para o módulo de biblioteca, utilizado em centenas de locais. Já para o serviço antigo, que está sendo substituído por um novo, não é tão importante anotá-lo. Além disso, estamos experimentando o uso de análise estática para gerar anotações de tipos para o código antigo.
Importações cíclicas. Acima, mencionei as importações cíclicas (os "novelos de dependências"), cuja existência complicou a aceleração do mypy. Além disso, tivemos que trabalhar arduamente para fornecer ao mypy suporte a todos os tipos de idiomatismos, cuja origem está nessas importações cíclicas. Recentemente, concluímos um grande projeto de redesign do sistema, que corrigiu a maioria dos problemas do mypy relacionados a importações cíclicas. Esses problemas, na verdade, surgiram desde os primórdios do projeto, ainda do Alore, a linguagem de programação educativa para a qual o mypy foi inicialmente orientado. A sintaxe do Alore resolve facilmente os problemas de comandos de importação cíclica. O mypy moderno herdou algumas limitações de sua implementação inicial simples (que funcionava muito bem para Alore). O Python complica o trabalho com importações cíclicas principalmente devido à ambiguidade das expressões. Por exemplo, durante uma operação de atribuição, um alias de tipo pode, na verdade, ser definido. O mypy nem sempre consegue identificar essas coisas até que a maior parte do ciclo de importação tenha sido processada. No Alore, essas ambiguidades não existiam. Decisões mal sucedidas tomadas nos estágios iniciais de desenvolvimento do sistema podem surpreender o programador anos depois.
Resultados: o caminho para 5 milhões de linhas de código e novos horizontes
O projeto mypy percorreu um longo caminho – desde os primeiros protótipos até um sistema que controla os tipos de código de produção com 4 milhões de linhas. Ao longo do desenvolvimento, houve uma padronização das sugestões de tipos em Python. Hoje em dia, uma poderosa ecossistema se desenvolveu em torno da tipagem de código Python. Isso inclui suporte a bibliotecas, ferramentas auxiliares para IDEs e editores, além de vários sistemas de controle de tipos, cada um com suas vantagens e desvantagens.
Embora a verificação de tipos já seja considerada algo trivial no Dropbox, estou convencido de que ainda estamos no amanhecer da tipagem de código em Python. Acredito que as tecnologias de verificação de tipos continuarão a evoluir e se aprimorar.
Se você ainda não utilizou checagens de tipos em seu projeto Python em grande escala, saiba que agora é um momento bastante apropriado para começar a transição para a tipagem estática. Conversamos com aqueles que fizeram essa mudança. Ninguém se arrependeu. O controle de tipos transforma o Python em uma linguagem que se adapta muito melhor ao desenvolvimento de grandes projetos do que o "Python comum".
Prezados leitores! Você utiliza controle de tipos em seus projetos Python?
Fonte: habr.com
