Hoje, oferecemos a você a primeira parte da tradução do material sobre como o Dropbox realiza o controle de tipos no código Python.
No Dropbox, escrevemos muito em Python. Esta é a linguagem que usamos de forma extremamente ampla — tanto para serviços de backend quanto para aplicativos de desktop. Também utilizamos em grandes volumes Go, TypeScript e Rust, mas Python é a nossa principal linguagem. Considerando nossas dimensões, que se referem a milhões de linhas de código Python, verificou-se que a tipagem dinâmica desse código tornou sua compreensão indevidamente complexa e começou a afetar seriamente a produtividade. Para suavizar esse problema, começamos a realizar a transição gradual do nosso código para verificação de tipos estáticos usando o mypy. Este é provavelmente o sistema de verificação de tipos independente mais popular para Python. O mypy é um projeto de código aberto, e seus principais desenvolvedores trabalham no Dropbox.
O Dropbox foi uma das primeiras empresas a implementar a verificação de tipos estáticos em código Python em tal escala. Hoje em dia, o mypy é utilizado em milhares de projetos. Esta ferramenta foi testada em combate inúmeras vezes, por assim dizer. Para chegarmos ao ponto onde estamos agora, tivemos que percorrer um longo caminho. Nessa jornada, houve muitas tentativas malsucedidas e experimentos que falharam. Este material narra a história da verificação de tipos estáticos em Python — desde seu início difícil, que foi parte do meu projeto de pesquisa acadêmica, até os dias atuais, quando as verificações de tipos e as dicas de tipos se tornaram comuns para inúmeras desenvolvedores que escrevem em Python. Esses mecanismos agora são suportados por várias ferramentas — como IDEs e analisadores de código.
→
Por que a verificação de tipos é necessária?
Se você alguma vez usou o Python, que é tipado dinamicamente, pode ter havido alguma confusão sobre por que houve tanta discussão sobre tipagem estática e mypy ultimamente. Ou talvez você goste do Python exatamente por causa de sua tipagem dinâmica, e o que está acontecendo simplesmente o deixa frustrado. A chave para o valor da tipagem estática é a escala das soluções: quanto maior seu projeto, mais você se inclina para a tipagem estática, e, no final das contas, mais isso se torna realmente necessário.
Suponha que um projeto tenha atingido dezenas de milhares de linhas e que ele esteja sendo desenvolvido por vários programadores. Ao considerar um projeto assim, com base na nossa experiência, podemos dizer que entender seu código será a chave para manter a produtividade dos desenvolvedores. Sem anotações de tipo, pode ser difícil descobrir, por exemplo, quais argumentos precisam ser passados para uma função ou quais tipos de valores uma determinada função pode retornar. Aqui estão algumas perguntas típicas que muitas vezes são difíceis de responder sem anotações de tipo:
- Esta função pode retornar
Nenhum? - Que tipo de argumento deve ser este
itens? - Qual o tipo do atributo
id:inté isso,str, ou talvez algum tipo personalizado? - Este argumento deve ser uma lista? É possível passar uma tupla para ele?
Se olharmos para o próximo fragmento de código, com anotações de tipo, e tentarmos responder a perguntas como essas, perceberemos que é uma tarefa muito simples:
class Resource:
id: bytes
...
def read_metadata(self,
items: Sequence[str]) -> Dict[str, MetadataItem]:
...read_metadatanão retornaNenhum, já que o tipo retornado não éOptional[…].- Argumento
itens— é uma sequência de strings. Não pode ser iterada em ordem arbitrária. - Atributo
id— é uma string de bytes.
Em um mundo ideal, poderíamos esperar que todas essas sutilezas estivessem descritas na documentação embutida (docstring). No entanto, a experiência nos fornece inúmeros exemplos de que tal documentação no código com o qual trabalhamos muitas vezes está ausente. Mesmo quando essa documentação existe, não se pode contar com sua absoluta precisão. Essa documentação pode ser vaga, imprecisa ou deixar margem para uma má interpretação. Em equipes grandes ou em projetos amplos, esse problema pode se tornar extremamente agudo.
Embora Python se saia bem nas fases iniciais ou intermediárias de projetos, em determinado momento, projetos bem-sucedidos e empresas que utilizam Python podem se deparar com a questão crucial: "Precisamos reescrever tudo em uma linguagem de tipagem estática?".
Sistemas de verificação de tipos como o mypy abordam esse problema mencionado ao fornecer aos desenvolvedores uma linguagem formal para descrever tipos e ao verificar se as descrições de tipos correspondem à implementação dos programas (e, opcionalmente, verificar sua existência). De um modo geral, pode-se dizer que esses sistemas nos proporcionam algo semelhante a uma documentação cuidadosamente verificada.
A utilização desses sistemas traz outras vantagens, que não são triviais:
- O sistema de verificação de tipos pode detectar alguns erros menores (assim como erros não tão pequenos). Um exemplo típico é quando esquecemos de tratar um valor
Nenhumou alguma outra condição especial. - O refatoramento do código se torna significativamente mais simples, pois o sistema de verificação de tipos frequentemente indica com precisão qual parte do código deve ser alterada. Não precisamos contar com 100% de cobertura de testes, o que, de qualquer forma, geralmente é inviável. Não precisamos mergulhar nos detalhes dos relatórios de rastreamento de pilha para descobrir a causa da falha.
- Mesmo em grandes projetos, o mypy pode frequentemente realizar uma verificação completa de tipos em frações de segundo. A execução de testes geralmente leva dezenas de segundos ou até minutos. O sistema de verificação de tipos oferece ao programador um feedback instantâneo e permite que ele trabalhe de forma mais rápida. Não é mais necessário escrever testes modulares frágeis e difíceis de manter, que substituem entidades reais por mocks e patches apenas para obter resultados de teste mais rapidamente.
IDEs e editores, como PyCharm ou Visual Studio Code, utilizam as capacidades de anotações de tipos para fornecer aos desenvolvedores recursos de autocompletar, destacar erros e suportar construções de linguagem frequentemente usadas. E essas são apenas algumas das vantagens que a tipagem oferece. Para alguns programadores, tudo isso é o principal argumento em favor da tipagem. É algo que traz benefícios imediatamente após a implementação. Este uso de tipos não requer a aplicação de um sistema de verificação de tipos separado, como o mypy, embora deva-se notar que o mypy ajuda a manter a conformidade entre anotações de tipos e código.
Histórico do mypy
A história do mypy começou no Reino Unido, em Cambridge, alguns anos antes de eu me juntar ao Dropbox. Eu estava trabalhando, no âmbito de minha pesquisa de doutorado, na unificação de linguagens estaticamente tipadas e dinâmicas. Fui inspirado por um artigo sobre tipagem gradual de Jeremy Siek e Walid Taha, assim como pelo projeto Typed Racket. Eu tentava encontrar maneiras de usar a mesma linguagem de programação para diferentes projetos — desde pequenos scripts até bases de código de milhões de linhas. Queria que, em projetos de qualquer escala, não fosse necessário fazer grandes compromissos. Uma parte importante de tudo isso era a ideia de transição gradual de um protótipo não tipado para um produto final estaticamente tipado, amplamente testado. Hoje em dia, essas ideias são amplamente aceitas, mas em 2010, essa ainda era uma questão que estava sendo ativamente pesquisada.
Meu trabalho inicial em verificação de tipos não era voltado para o Python. Em vez disso, eu utilizava uma pequena linguagem "caseira". Aqui está um exemplo que vai te ajudar a entender sobre o que se trata (anotações de tipos 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
endO uso de uma linguagem simplificada de desenvolvimento próprio é uma abordagem comum em pesquisas científicas. Isso se deve em grande parte ao fato de que assim podem ser realizados experimentos rapidamente, além de que o que não está relacionado à pesquisa pode ser ignorado sem problemas. Linguagens de programação realmente utilizadas geralmente representam fenômenos complexos com implementações complicadas, o que retarda os experimentos. No entanto, quaisquer resultados baseados em uma linguagem simplificada parecem um pouco suspeitos, pois ao obter esses resultados, o pesquisador pode ter sacrificado considerações importantes para o uso prático dessas linguagens.
Meu verificador de tipos para Alore parecia bastante promissor, mas queria testá-lo realizando experimentos com código real, que, pode-se dizer, não existia em Alore. Para minha sorte, a linguagem Alore estava amplamente baseada nas mesmas ideias que Python. Foi relativamente fácil reestruturar o verificador de tipos para que pudesse operar com a sintaxe e a semântica do Python. Isso permitiu tentar realizar a verificação de tipos em código Python de código aberto. Além disso, escrevi um transpiler para converter código escrito em Alore para código Python e o utilizei para traduzir o código do meu verificador de tipos. Agora eu tinha um sistema de verificação de tipos escrito em Python que suportava um subconjunto de Python, uma variação dessa linguagem! (Certas decisões arquitetônicas que faziam sentido para Alore não se adequaram bem ao Python; isso ainda é notável em algumas partes da base de código do mypy.)
Na verdade, a linguagem suportada pelo meu sistema de tipos, nesse momento, não podia ser chamada inteiramente de Python: era uma variante do Python devido a algumas limitações da sintaxe das anotações de tipos do Python 3.
Isso 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 ideias na época era usar anotações de tipo para melhorar o desempenho compilando essa variante do Python em C, ou possivelmente em bytecode JVM. Cheguei a escrever um protótipo de compilador, mas abandonei a ideia, pois a verificação de tipos parecia útil por si só.
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 vitalício do Python. Ele me convenceu a desistir da minha própria sintaxe e a aderir à sintaxe padrão do Python 3. O Python 3 suporta anotações de função, então meu exemplo poderia ser reescrito conforme abaixo, resultando em um programa Python normal:
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Eu precisei fazer alguns compromissos (primeiro quero notar que inventei minha própria sintaxe exatamente por isso). Em particular, o Python 3.3, a versão mais recente da linguagem na época, não suportava anotações de variáveis. Discutei com Guido por e-mail várias possibilidades de formatação sintática para essas anotações. Decidimos usar comentários para variáveis, indicando os tipos. Isso alcançou o objetivo, mas parecia um tanto desajeitado (o Python 3.6 nos deu uma sintaxe mais agradável):
products = [] # type: List[str] # EcaAs anotações de tipo também foram úteis para suportar o Python 2, que não possui suporte embutido para anotações de tipo:
f fib(n):
# type: (int) -> int
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Aconteceu que esses (e outros) compromissos, na verdade, não tinham muita importância — as vantagens da tipagem estática fizeram com que os usuários logo esquecessem a sintaxe não totalmente ideal. Como agora no código Python, em que os tipos eram controlados, não eram usadas construções sintáticas especiais, as ferramentas e processos existentes para manipulação de código continuaram funcionando normalmente, o que facilitou muito a adoção da nova ferramenta pelos desenvolvedores.
Além disso, Guido me convenceu a me juntar ao Dropbox depois que defendi minha tese. Aqui começa a parte mais interessante da história do mypy.
Continua…
Prezados leitores! Se você usa Python, por favor, compartilhe sobre os projetos de que escala você está desenvolvendo nesta linguagem.
Fonte: habr.com
