Hoje estamos publicando a segunda parte de uma tradução do material, que é dedicada às anotações de tipo em Python.

→
A primeira parteComo o Python suporta tipos de dados?
Python é uma linguagem de tipo dinâmico. Isso significa que os tipos de variáveis usadas são verificados apenas durante a execução do programa. No exemplo dado na parte anterior do artigo, era possível ver que um programador que escreve em Python não precisava planejar os tipos de variáveis e pensar em quanta memória seria necessária para armazenar seus dados.
Aqui está o que acontece quando você prepara seu código Python para execução: “No Python, o código-fonte é convertido, usando CPython, em uma forma muito mais simples chamada bytecode. O bytecode consiste em instruções que, em essência, são semelhantes às instruções do processador. Mas eles não são executados pelo processador, mas por um sistema de software chamado máquina virtual. (Não se trata de máquinas virtuais cujos recursos permitem executar sistemas operacionais inteiros neles. No nosso caso, este é um ambiente que é uma versão simplificada do ambiente disponível para programas em execução no processador). "
Como o CPython sabe quais tipos de variáveis devem ser quando prepara um programa para execução? Afinal, não indicamos esses tipos. O CPython não sabe disso. Ele só sabe que variáveis são objetos. Tudo no Python é um
objeto , pelo menos até que algo tenha um tipo mais específico.
Por exemplo, o Python considera como uma string tudo o que está entre aspas simples ou duplas. Se o Python encontrar um número, ele considerará que o valor correspondente é de um tipo numérico. Se tentarmos fazer algo com uma entidade que não pode ser feita com uma entidade de seu tipo, o Python nos informará mais tarde.
Considere a seguinte mensagem de erro que aparece quando você tenta adicionar uma seqüência de caracteres e um número:
name = 'Vicki' seconds = 4.71; --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-9-71805d305c0b> in <module> 3 4 ----> 5 name + seconds TypeError: must be str, not float
O sistema nos diz que não pode adicionar seqüências de caracteres e números de ponto flutuante. Além disso, o fato de o
name
ser uma string e de
seconds
ser um número não interessava ao sistema até que fosse feita uma tentativa de adicionar
name
e
seconds
.
Em outras palavras, pode ser descrito da
seguinte forma : “A digitação do pato é usada ao realizar a adição. Python não está interessado em qual tipo um objeto específico possui. Tudo o que o sistema está interessado é se ele retorna uma chamada significativa para o método de adição. Caso contrário, será emitido um erro. "
O que isso significa? Isso significa que, se escrevermos programas em Python, não receberemos uma mensagem de erro até que o intérprete do CPython esteja envolvido na execução da mesma linha na qual há um erro.
Essa abordagem foi inconveniente quando aplicada em equipes que trabalham em grandes projetos. O fato é que, nesses projetos, eles trabalham não com variáveis separadas, mas com estruturas de dados complexas. Nesses projetos, algumas funções são chamadas por outras, e essas, por sua vez, são chamadas por algumas outras funções. Os membros da equipe devem poder verificar rapidamente o código de seus projetos. Se eles não conseguirem escrever bons testes que detectam erros nos projetos antes de serem colocados em produção, isso significa que esses projetos podem esperar grandes problemas.
A rigor, aqui chegamos à conversa sobre anotações de tipo em Python.
Podemos dizer que, em geral, o uso de anotações de tipo tem muitos
pontos fortes . Se você trabalha com estruturas ou funções complexas de dados que recebem muitos valores de entrada, o uso de anotações simplifica bastante o trabalho com estruturas e funções semelhantes. Especialmente - algum tempo após a sua criação. Se você tiver apenas uma função com um parâmetro, como nos exemplos fornecidos aqui, trabalhar com essa função é, em qualquer caso, muito simples.
E se precisarmos trabalhar com funções complexas que usam muitos valores de entrada semelhantes a isso na documentação do
PyTorch :
def train(args, model, device, train_loader, optimizer, epoch): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() optimizer.step() if batch_idx % args.log_interval == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item()))
O que é
model
? Obviamente, podemos nos aprofundar na base de código e descobrir:
model = Net().to(device)
Mas seria bom se você pudesse apenas especificar o tipo de
model
na assinatura da função e se poupar de análises de código desnecessárias. Talvez fosse assim:
def train(args, model (type Net), device, train_loader, optimizer, epoch):
E o
device
? Se você vasculhar o código, poderá descobrir o seguinte:
device = torch.device("cuda" if use_cuda else "cpu")
Agora, estamos diante da questão do que
torch.device
. Este é um tipo especial de PyTorch. Sua descrição pode ser encontrada na
seção correspondente da documentação do PyTorch.
Seria bom se pudéssemos especificar o tipo de
device
na lista de argumentos da função. Assim, economizaríamos muito tempo para aqueles que precisariam analisar esse código.
def train(args, model (type Net), device (type torch.Device), train_loader, optimizer, epoch):
Essas considerações podem continuar por muito tempo.
Como resultado, as anotações de tipo são muito úteis para quem escreve código. Mas eles também beneficiam quem lê o código de outras pessoas. É muito mais fácil ler o código digitado do que o código, para entender o que você precisa lidar com o que é uma entidade. As anotações de tipo melhoram a legibilidade do código.
Então, o que foi feito no Python para trazer o código para o mesmo nível de legibilidade que distingue o código escrito em linguagens estaticamente tipadas?
Anotações de tipo em Python
Agora estamos prontos para falar seriamente sobre anotações de tipo no Python. Ao ler programas escritos em Python 2, era possível ver que os programadores forneciam seu código com dicas dizendo aos leitores do código que tipo de variáveis ou valores retornados pelas funções são.
Código semelhante era originalmente assim:
users = []
As anotações de tipo costumavam ser comentários simples. Mas aconteceu que o Python começou a mudar gradualmente para uma maneira mais uniforme de lidar com anotações. Em particular, estamos falando sobre o surgimento do documento
PEP 3107 , dedicado à anotação de funções.
Em seguida, começaram os trabalhos no
PEP 484 . Este documento, dedicado às anotações de tipo, foi desenvolvido em estreita conexão com o mypy, o projeto DropBox, que visa verificar os tipos antes de executar scripts. Usando mypy, vale lembrar que a verificação de tipo não é realizada durante a execução do script. Uma mensagem de erro pode ser recebida em tempo de execução se, por exemplo, você tentar criar algo que não seja compatível com esse tipo. Diga - se você tentar
.pop()
um dicionário ou chamar o método
.pop()
para uma sequência.
Aqui está o que você pode aprender com o PEP 484 sobre os detalhes da implementação de anotação: “Embora essas anotações estejam disponíveis em tempo de execução por meio do atributo de
annotations
regulares, as verificações de tipo não são executadas em tempo de execução. Em vez disso, essa proposta prevê a existência de uma ferramenta independente separada para verificação de tipos, com a qual o usuário, se desejar, pode verificar o código fonte de seus programas. Em geral, uma ferramenta de verificação de tipo semelhante funciona como um linter muito poderoso. "Embora, é claro, usuários individuais possam usar uma ferramenta semelhante para verificar tipos em tempo de execução, seja para a implementação da metodologia Design By Contract ou para a implementação da otimização de JIT. Mas deve-se notar que essas ferramentas ainda não atingiram maturidade suficiente".
Como é o trabalho com anotações de tipo na prática?
Por exemplo, seu uso significa a possibilidade de facilitar o trabalho em vários IDEs. Portanto, o
PyCharm oferece, com base nas informações de tipo, conclusão e verificação de código. Recursos semelhantes estão disponíveis no VS Code.
As anotações de tipo são úteis por mais um motivo: protegem o desenvolvedor de erros estúpidos.
Aqui está um ótimo exemplo dessa proteção.
Suponha que adicionemos os nomes das pessoas ao dicionário:
names = {'Vicki': 'Boykis', 'Kim': 'Kardashian'} def append_name(dict, first_name, last_name): dict[first_name] = last_name append_name(names,'Kanye',9)
Se permitirmos isso, haverá muitas entradas formadas incorretamente no dicionário.
Vamos consertar isso:
from typing import Dict names_new: Dict[str, str] = {'Vicki': 'Boykis', 'Kim': 'Kardashian'} def append_name(dic: Dict[str, str] , first_name: str, last_name: str): dic[first_name] = last_name append_name(names_new,'Kanye',9.7) names_new
Agora verifique este código com mypy e obtenha o seguinte:
(kanye) mbp-vboykis:types vboykis$ mypy kanye.py kanye.py:9: error: Argument 3 to "append_name" has incompatible type "float"; expected "str"
Pode-se ver que mypy não permite que você use o número em que a string é esperada. Aqueles que desejam usar esses testes regularmente são aconselhados a incluir o mypy, onde os testes de código são realizados em seus sistemas de integração contínua.
Digite dicas em vários IDEs
Um dos benefícios mais importantes do uso de anotações de tipo é que eles permitem que os programadores Python usem os mesmos recursos de conclusão de código em vários IDEs disponíveis para linguagens de tipo estaticamente.
Por exemplo, suponha que você tenha um pedaço de código semelhante ao seguinte. Estas são algumas funções dos exemplos anteriores agrupadas em classes.
from typing import Dict class rainfallRate: def __init__(self, hours, inches): self.hours= hours self.inches = inches def calculateRate(self, inches:int, hours:int) -> float: return inches/hours rainfallRate.calculateRate() class addNametoDict: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name self.dict = dict def append_name(dict:Dict[str, str], first_name:str, last_name:str): dict[first_name] = last_name addNametoDict.append_name()
O bom é que, por nossa própria iniciativa, adicionamos descrições de tipo ao código, podemos observar o que acontece no programa quando os métodos de classe são chamados:
Dicas do tipo IDEIntrodução às anotações de tipo
Você pode encontrar boas recomendações na documentação do mypy sobre o que começar ao começar a digitar a base de código:
- Comece pequeno - verifique se alguns arquivos que contêm várias anotações são validados com mypy.
- Escreva um script para executar o mypy. Isso ajudará a obter resultados consistentes de teste.
- Execute mypy nos pipelines de IC para evitar erros de tipo.
- Anote gradualmente os módulos usados com mais freqüência no projeto.
- Adicione anotações de tipo ao código existente que você está modificando; equipe-os com o novo código que você escreve.
- Use MonkeyType ou PyAnnotate para anotar automaticamente o código antigo.
Antes de começar a anotar seu próprio código, será útil lidar com algo.
Primeiro, você precisará importar o módulo de
digitação para o código se usar algo diferente de cadeias, números inteiros, booleanos e os valores de outros tipos básicos de Python.
Em segundo lugar, este módulo permite trabalhar com vários tipos complexos. Entre eles estão
Dict
,
Tuple
,
List
e
Set
. Uma construção do formulário
Dict[str, float]
significa que você deseja trabalhar com um dicionário cujos elementos usem uma string como chave e um número de ponto flutuante como valor. Também existem tipos chamados
Optional
e
Union
.
Em terceiro lugar, você precisa se familiarizar com o formato das anotações de tipo:
import typing def some_function(variable: type) -> return_type: do_something
Se você quiser saber mais sobre como começar a aplicar anotações de tipo em seus projetos, gostaria de observar que muitos bons tutoriais são dedicados a isso.
Aqui está um deles. Eu considero o melhor. Depois de dominado, você aprenderá sobre a anotação de código e sua verificação.
Os resultados. Vale a pena usar anotações de tipo em Python?
Agora vamos nos perguntar se você deve usar anotações de tipo em Python. Na verdade, isso depende dos recursos do seu projeto. Aqui está o que Guido van Rossum diz na documentação do mypy sobre isso: “O objetivo do mypy não é convencer todos a escrever código Python estaticamente digitado. A digitação estática é totalmente opcional agora e no futuro. O objetivo de Mypy é oferecer aos programadores Python mais opções. É para tornar o Python uma alternativa mais competitiva a outras linguagens estaticamente usadas em grandes projetos. É para aumentar a produtividade dos programadores e melhorar a qualidade do software ".
O tempo necessário para configurar o mypy e planejar os tipos necessários para um determinado programa não se justifica em pequenos projetos e durante experimentos (por exemplo, aqueles realizados no Jupyter). Qual projeto deve ser considerado pequeno? Provavelmente aquele cujo volume, de acordo com estimativas cuidadosas, não exceda 1000 linhas.
As anotações de tipo fazem sentido em projetos maiores. Lá eles podem, em particular, economizar muito tempo. Estamos falando de projetos desenvolvidos por grupos de programadores, de pacotes e de código, que são usados no desenvolvimento de sistemas de controle de versão e pipelines de CI.
Acredito que as anotações de tipo nos próximos dois anos se tornem muito mais comuns do que agora, sem mencionar o fato de que elas podem se transformar em uma ferramenta diária comum. E acredito que alguém que começa a trabalhar com eles antes dos outros não perde nada.
Caros leitores! Você usa anotações de tipo em seus projetos Python?
