Tipos em Python é aquele "tipo" de assunto que vai agitar qualquer papo de almoço entre programadores. Eu mesmo fui cético por muito tempo, e franzia a testa constantemente quando esse tópico aparecia. Mas já mudei de opinião algumas vezes, e escrever sobre tais mudanças rendeu alguns dos artigos mais acessados do blog. De IDEs vs. editores, passando por GraphQL e chegando até mesmo ao uso de Java. Agora é a vez de tipagem em Python.
Nota-se uma "nem tão recente onda" na utilização de tipagem estática. Alimentada por linguagens como Go e Typescript, os argumentos (assim como prós e contras) são dos mais variados, e vem influenciando linguagens amplamente conhecidas por serem de tipagem dinâmica, como Python e Ruby.
Mais fundamental que entender o "como", é crucial ficar por dentro da motivação por trás da PEP 484. Vamos viajar um pouquinho no tempo e tentar compreender o "por quê".
Conheci o Python lá pelos idos de 2009, e venho falando sobre a linguagem desde então. Sempre posso contar com o artigo do Wikipedia para definir a linguagem com os atributos que mais admiro:
Python é uma linguagem de programação de alto nível, interpretada, de script, imperativa, orientada a objetos, funcional, de tipagem dinâmica e forte.
E sem mais rodeios, abrimos o nosso primeiro parênteses.
Python, PHP, Javascript e Ruby tem algo em comum: Elas são linguagens de tipagem dinâmica. Em outras palavras, elas são capazes de escolher o tipo de dado de acordo com o valor que está sendo atribuído a uma variável:
a = "foo"
a = 1
b = 2
print(a + b) # 3
Ao contrário de linguagens com tipagem estática, como o Java; onde uma vez que a variável esteja setada com o seu respectivo tipo, não podemos mais alterá-lo:
public class MyClass {
public static void main(String args[]) {
String a = "foo";
a = 1; // TypeError
}
}
Mesmo se utilizarmos recursos mais modernos, como a inferência de tipo, ainda assim estamos sujeitos à tipagem estática:
public class MyClass {
public static void main(String args[]) {
var a = "foo";
a = 1; // TypeError
}
}
O Javascript é um exemplo de linguagem com tipagem fraca. Com ela somos capazes de misturar tipos sem maiores restrições, e contar com conversões realizadas automaticamente pela linguagem, como no exemplo abaixo:
var a = "foo";
var b = 2;
console.log(a + b); // foo2
O mesmo não acontece com o Python, que tem por característica a tipagem forte, sendo assim mais restritivo com esse tipo de operação:
a = "foo"
b = 2
print(a + b) # TypeError: can only concatenate str (not "int") to str
Tipagem dinâmica e forte foram duas das características mais apaixonantes do Python. A linguagem estava me ajudando a ser mais coerente com o design do código, e com isso, me dando uma camada a mais de segurança (e por consequência, qualidade).
A primeira vez que ouvi falar sobre tipagem estática em Python, foi por volta de 2016. Segundo o "Our journey to type checking 4 million lines of Python", no ano anterior (com o Python 3.5) era lançada a PEP 484.
Essa Python Enhancement Proposal oficializava a proposta de Jukka Lehtosalo para o uso de tipos em Python. A diferença aqui, em relação a outras linguagens, é que utilizamos anotações para sugerir qual tipo uma variável ou parâmetro receberá (chamadas de type hints):
def greeting(name: str) -> str:
return 'Hello ' + name
Por anotações entende-se que estamos sugerindo ao programador, e não ao intepretador, quais são os tipos. De fato, o interpretador ignora tais instruções e elas não tem efeito no código rodando.
Na época, o mypy não era essa lib bem estabelecida que é hoje. Conceitos e ferramentas ao redor desse tópico ainda eram confusos, e o mesmo gerava debates acalorados.
Nem preciso dizer que fui um dos programadores que logo de cara torceu o nariz.
Quantas vezes você já precisou abrir uma função para compreender o que exatamente ela retornava? Quantos
Docstrings você leu, dando dicas de tipos de parâmetros e retorno? Quantos print
e pdb.set_trace()
para
entender a razão de um atributo de instância estar disparando algum erro de tipo ou chave?
À medida que o seu projeto vai crescendo em tamanho e complexidade, a ausência de tipos começa a afetar a qualidade do mesmo, pelo menos é isso que afirma Dustin Ingram no talk Static Typing in Python:
Se você tiver 28 minutos, dê uma pausa neste artigo e confira a apresentação na íntegra:
Há o argumento de que se o código fosse claro, seguindo padrões e boas práticas, não precisaríamos de tais artifícios. Acho difícil discordar dessa afirmação. Tão difícil quanto encontrar tal código.
Código legível é subjetivo, enquanto que tipagem estática serve como uma forma de documentação e (supostamente) auxilia na manutenibilidade de software. Esse é um dos argumentos apresentados no paper "An empirical study on the impact of static typing on software maintainability".
Em suma, com o passar do tempo, fazendo parte de grandes projetos front-end em React, e utilizando de tipagem para entender o que estava acontecendo, eu enxergo e entendo o argumento a favor do uso de type hints em Python.
A melhor forma de começar é através do mypy:
$ pip install mypy
O próximo passo é adicionar as anotações ao código, indicando os tipos:
# calculadora.py
def soma(a: int, b: int) -> int:
return a + b
result = soma(2.5, 1)
print(result)
Anotamos os parâmetros da função através de : int
, e o retorno com -> int
. Com o utilitário de linha
de comando executamos a checagem:
$ mypy calculadora.py
Success: no issues found in 1 source file
Se alterarmos um dos parâmetros passados para float
, o mypy
nos alertará que há uma inconsistência:
$ mypy calculadora.py
calculadora.py:5: error: Argument 1 to "soma" has incompatible type "float"; expected "int"
Found 1 error in 1 file (checked 1 source file)
Mas ainda assim será possível executar o código sem problemas.
Com o VS Code e o plugin Python instalado, basta o interpretador configurado ter acesso ao mypy, ou que você explicitamente passe o caminho do executável nas configurações do editor:
Leia mais sobre como configurar o Mypy com VS Code.
No Vim há diferentes alternativas. Talvez a forma mais simples e prática seja com o uso do vim-ale.
Caso você se aventure pelo mundo do LSP, uma combinação interessante é a do vim-lsp + Python Language Server:
$ pip install pip install python-lsp-server pylsp-mypy
Volto a mencionar a minha experiência em um projeto complexo em Javascript: Ter os tipos declarados e explícitos foi de muita ajuda, e sem dúvida acelerou o desenvolvimento e o onboarding de novos membros à equipe com o passar do tempo. Não é tão difícil de enxergar as vantagens por esses termos.
Mas o debate sobre Python em si ser um bom candidato a esse tipo de prática é muito válido. Fazendo um contraponto ao parágrafo acima, um código bem escrito poderia sim ser o suficiente para resolver alguns dos problemas que estamos tentando resolver com type hint.
Eu ainda trocaria um código mal escrito
por um bem escrito
, ao invés de um mal escrito
por um mal escrito com type hints
.
Em cima do muro :)
Quem sabe no próximo grande projeto...
Até a próxima.