Hoje, chamamos a atenção para a primeira parte da tradução do material sobre como o Dropbox está envolvido no controle de tipo de código Python.

O Dropbox escreve muito em Python. Essa é uma linguagem que usamos extremamente amplamente - tanto para serviços de back-end quanto para aplicativos cliente de desktop. Também usamos Go, TypeScript e Rust em grandes volumes, mas Python é a nossa linguagem principal. Dado o nosso escopo, e estamos falando de milhões de linhas de código Python, a digitação dinâmica desse código complicou desnecessariamente seu entendimento e começou a afetar seriamente a produtividade. Para atenuar esse problema, começamos a traduzir gradualmente nosso código para verificação de tipo estático usando mypy. Este é provavelmente o sistema de verificação de tipo autônomo mais popular para Python. Mypy é um projeto de código aberto; seus principais desenvolvedores trabalham no Dropbox.
O Dropbox foi uma das primeiras empresas a implementar a verificação de tipo estático no código Python em uma escala semelhante. Atualmente, o mypy é usado em milhares de projetos. Essa ferramenta é inúmeras vezes, como se costuma dizer, "testada em batalha". Nós, a fim de chegar onde estamos agora, tivemos que percorrer um longo caminho. Nesse caminho, houve muitos empreendimentos malsucedidos e experimentos fracassados. Este material trata da história da verificação estática de tipos no Python - desde o seu início muito difícil, que fazia parte do meu projeto de pesquisa científica, até os dias atuais, quando as checagens e dicas de tipo se tornaram familiares para inúmeros desenvolvedores que escrevem no Python. Esses mecanismos agora são suportados por muitas ferramentas - como IDEs e analisadores de código.
→
Leia a segunda partePor que a verificação de tipo é necessária?
Se você já usou o Python de digitação dinâmica, pode ter alguma confusão sobre o motivo de um burburinho desse tipo ter ocorrido ultimamente com a digitação estática e o mypy. Ou pode ser que você goste de Python precisamente por causa de sua digitação dinâmica, e o que está acontecendo apenas o incomoda. A chave para o valor da digitação estática é a escala de decisões: quanto maior o seu projeto, mais você tende a digitar estática e, no final, mais precisa realmente.
Suponha que um projeto atinja dezenas de milhares de linhas e que vários programadores estejam trabalhando nele. Considerando esse projeto, com base em nossa experiência, podemos dizer que entender seu código será a chave para dar suporte à produtividade do desenvolvedor. Sem anotações de tipo, não é fácil descobrir, por exemplo, quais argumentos você precisa passar para a função ou quais valores de quais tipos uma função pode retornar. Aqui estão perguntas típicas que geralmente são difíceis de responder sem usar anotações de tipo:
- Esta função pode retornar
None
? - Qual deve ser o argumento desses
items
? - Qual é o tipo de atributo
id
: int
, é str
ou talvez algum tipo personalizado? - Esse argumento deve ser uma lista? É possível passar uma tupla para ele?
Se você observar o seguinte trecho de código, equipado com anotações de tipo, e tentar responder a essas perguntas, verifica-se que esta é a tarefa mais simples:
class Resource: id: bytes ... def read_metadata(self, items: Sequence[str]) -> Dict[str, MetadataItem]: ...
read_metadata
não retorna None
, porque o tipo de retorno não é Optional[…]
.- O argumento
items
é uma sequência de strings. Não pode ser iterado em nenhuma ordem. - O atributo
id
é uma sequência de bytes.
Em um mundo ideal, seria de esperar que todas essas sutilezas fossem descritas na documentação incorporada (docstring). Mas a experiência fornece muitos exemplos do fato de que essa documentação no código com a qual você precisa trabalhar geralmente não é observada. Mesmo que essa documentação esteja presente no código, não se pode contar com sua absoluta exatidão. Esta documentação pode não ser clara, imprecisa, deixando muitas possibilidades para seu mal-entendido. Em grandes equipes ou em grandes projetos, esse problema pode se tornar extremamente agudo.
Embora o Python tenha um bom desempenho nos estágios inicial ou intermediário dos projetos, em algum momento projetos e empresas bem-sucedidos que usam o Python podem apresentar uma pergunta vital: "Precisamos reescrever tudo em uma linguagem de tipo estaticamente?"
Sistemas de verificação de tipos como o mypy resolvem o problema mencionado, fornecendo ao desenvolvedor uma linguagem formal para a descrição dos tipos e verificando se as descrições dos tipos são consistentes com as implementações do programa (e, opcionalmente, verificando sua existência). Em geral, podemos dizer que esses sistemas nos fornecem algo como documentação cuidadosamente verificada.
O uso de tais sistemas tem outras vantagens, e elas já são completamente não triviais:
- Um sistema de verificação de tipo pode detectar alguns erros pequenos (e não muito pequenos). Um exemplo típico é quando eles esquecem de processar o valor
None
ou alguma outra condição especial. - A refatoração de código é bastante simplificada, pois o sistema de verificação de tipo geralmente informa com precisão o código que precisa ser alterado. Nesse caso, não precisamos esperar uma cobertura 100% do código com testes, o que, em qualquer caso, geralmente é impossível. Não precisamos examinar a profundidade dos relatórios de rastreamento de pilha para descobrir a causa do problema.
- Mesmo em projetos grandes, o mypy costuma fazer uma verificação completa do tipo em uma fração de segundo. E a execução dos testes geralmente leva dezenas de segundos ou até minutos. O sistema de verificação de tipo fornece feedback instantâneo ao programador e permite que ele faça seu trabalho mais rapidamente. Ele não precisa mais escrever testes de unidade frágeis e com suporte pesado que substituam entidades reais por mokas e patches apenas para obter os resultados do teste de código mais rapidamente.
IDEs e editores, como PyCharm ou Visual Studio Code, usam o poder das anotações de tipo para fornecer aos desenvolvedores a capacidade de concluir automaticamente o código, destacar erros e dar suporte a construções de linguagem usadas com frequência. E essas são apenas algumas das vantagens que a digitação oferece. Para alguns programadores, tudo isso é o principal argumento a favor da digitação. É isso que se beneficia imediatamente após a implementação. Esse caso de uso de tipo não requer um sistema de verificação de tipo separado, como o mypy, embora seja necessário observar que o mypy ajuda a manter a consistência entre as anotações de tipo e o código.
Mypy Background
A história do mypy começou no Reino Unido, em Cambridge, alguns anos antes de eu me juntar ao Dropbox. Como parte de minha pesquisa de doutorado, lidei com a unificação de linguagens estáticas e dinâmicas. Fui inspirado por um artigo sobre a digitação gradual de Jeremy Siek e Walid Taha, bem como o projeto Typed Racket. Tentei encontrar maneiras de usar a mesma linguagem de programação para vários projetos - de pequenos scripts a bases de código que consistem em muitos milhões de linhas. Ao mesmo tempo, eu gostaria que um projeto de qualquer escala não tivesse que fazer grandes compromissos. Uma parte importante de tudo isso foi a ideia de uma transição gradual de um projeto de protótipo não tipado para um produto acabado, estaticamente testado e amplamente testado. Hoje, essas idéias são amplamente consideradas como certas, mas em 2010 era um problema que ainda estava sendo explorado ativamente.
Meu trabalho inicial no campo de verificação de tipo não foi direcionado ao Python. Em vez disso, usei a pequena linguagem "caseira"
Alore . Aqui está um exemplo que permitirá que você entenda o que está em jogo (as anotações de tipo são opcionais aqui):
def Fib(n as Int) as Int if n <= 1 return n else return Fib(n - 1) + Fib(n - 2) end end
Usar uma linguagem simplificada de nosso próprio design é uma abordagem comum usada em pesquisas científicas. Isso não é menos importante, porque isso permite que você conduza experimentos rapidamente, e também porque o fato de a pesquisa ser irrelevante, pode ser livremente ignorado. As linguagens de programação realmente usadas são geralmente fenômenos de larga escala com implementações complexas, e isso atrasa os experimentos. No entanto, qualquer resultado baseado em uma linguagem simplificada parece um pouco suspeito, pois quando os resultados foram obtidos, o pesquisador pode ter sacrificado considerações importantes para o uso prático das línguas.
Minha ferramenta de verificação de tipo para o Alore parecia muito promissora, mas eu queria testá-lo experimentando código real, que, pode-se dizer, não estava escrito no Alore. Felizmente, Alore foi amplamente baseado nas mesmas idéias que o Python. Foi simples o suficiente para refazer a ferramenta de verificação de tipo para que ela pudesse funcionar com sintaxe e semântica do Python. Isso nos permitiu tentar a verificação de tipo no código Python de código aberto. Além disso, escrevi um transportador para converter código escrito em código Alore para Python e usei-o para traduzir o código da minha ferramenta de verificação de tipo. Agora eu tinha um sistema de verificação de tipo escrito em Python que suportava um subconjunto de Python, algum tipo de linguagem! (Certas soluções arquitetônicas que faziam sentido para o Alore eram pouco adequadas para o Python, o que ainda é perceptível em partes da base de código mypy.)
De fato, a linguagem suportada pelo meu sistema de tipos naquele momento não poderia ter sido chamada de Python: era uma variante do Python devido a algumas limitações da sintaxe da sintaxe de anotação do tipo Python 3.
Parecia uma mistura de Java e Python:
int fib(int n): if n <= 1: return n else: return fib(n - 1) + fib(n - 2)
Uma das minhas idéias na época era usar anotações de tipo para melhorar o desempenho compilando esse tipo de Python em C, ou possivelmente no bytecode da JVM. Eu avancei para o estágio de escrever o protótipo do compilador, mas deixei essa ideia, pois a verificação de tipo parecia bastante útil.
No final, apresentei meu projeto na conferência PyCon 2013 em Santa Clara. Também conversei sobre isso com Guido van Rossum, o generoso ditador ao longo da vida de Python. Ele me convenceu a abandonar minha própria sintaxe e seguir a sintaxe padrão do Python 3. O Python 3 suporta anotações de funções; como resultado, meu exemplo pode ser reescrito conforme mostrado abaixo, obtendo um programa normal do Python:
def fib(n: int) -> int: if n <= 1: return n else: return fib(n - 1) + fib(n - 2)
Eu precisava fazer alguns compromissos (antes de tudo, quero observar que inventei minha própria sintaxe por esse motivo). Em particular, o Python 3.3, a versão mais recente da linguagem na época, não suportava anotações de variáveis. Discuti com Guido por e-mail as várias possibilidades de sintaxe de tais anotações. Decidimos usar comentários de tipo para variáveis. Isso nos permitiu alcançar nosso objetivo, mas parecia um pouco complicado (o Python 3.6 nos deu uma sintaxe mais agradável):
products = []
Os comentários de tipo também foram úteis para o suporte ao Python 2, que não possui suporte interno para anotações de tipo:
f fib(n):
Aconteceu que esses (e outros) compromissos, na verdade, não tinham muito significado - as vantagens da tipagem estática levaram ao fato de que os usuários logo esqueceram a sintaxe não muito perfeita. Como nenhuma construção de sintaxe especial foi usada no código Python no qual os tipos eram controlados, as ferramentas existentes do Python e os processos de processamento de código continuaram funcionando normalmente, o que facilitou muito o desenvolvimento da nova ferramenta pelos desenvolvedores.
Guido também me convenceu a me juntar ao Dropbox depois que defendi minha formatura. É aqui que começa a parte divertida da história do mypy.
Para continuar ...
Caros leitores! Se você usa Python, conte-nos sobre a escala de projetos que está desenvolvendo nesta linguagem.
