A linguagem Python é uma linguagem de tipagem dinâmica, o que significa que o tipo de uma variável é determinado em tempo de execução, não em tempo de compilação. Isso significa que você não precisa especificar o tipo de dados de uma variável ao declará-la, e uma variável pode conter valores de tipos diferentes em momentos diferentes.
No entanto, existem algumas vantagens relacionadas a um sistema de tipagem estática, como a possibilidade de verificar erros de tipo em tempo de compilação. Para podermos ter as vantagens de um sistema de tipagem estática sem perder a flexibilidade de uma linguagem de tipagem dinâmica, o Python 3.5 introduziu o recurso de Type Hints.
Neste artigo, vamos aprender o que são Type Hints no Python, como usá-los e como eles podem nos ajudar a escrever código Python mais claro e previsível.
Tipagem estática e dinâmica
Antes de mais nada, é importante entender a diferença entre tipagem estática e dinâmica.
Em suma, em uma linguagem de tipagem estática, o tipo de uma variável é determinado em tempo de compilação e não pode ser alterado em tempo de execução. Isso significa que você deve especificar o tipo de dado de uma variável ao declará-la e não pode atribuir um valor de tipo diferente a essa variável. As linguagens de tipagem estática geralmente têm regras mais rígidas para verificação de tipo, o que pode torná-las mais difíceis de aprender e usar, mas também podem fornecer alguns benefícios em termos de clareza de código e verificação de erros.
Contudo, em uma linguagem de tipagem dinâmica, o tipo de uma variável é determinado em tempo de execução, não em tempo de compilação. Isso significa que você não precisa informar o tipo de uma variável ao declará-la, e a mesma pode conter valores de tipos diferentes em momentos diferentes. Linguagens de tipagem dinâmica geralmente são mais fáceis de aprender e usar, porque são mais flexíveis e permitem que você escreva código rapidamente. No entanto, elas também podem ser menos previsíveis e podem levar a erros de tempo de execução se você não for cuidadoso.
Além disso, aqui no blog da TreinaWeb nós temos o artigo “Quais as diferenças entre tipagens: estática ou dinâmica e forte ou fraca” que explica melhor esses conceitos e também mostra exemplos de código em diferentes linguagens.
O que são Type Hints?
Primeiramente, Type Hints são um recurso que permite especificar o tipo de dados de uma variável em seu código. Type Hints não são executadas em tempo de execução, mas podem ser usadas por ferramentas como IDEs e type checkers para fornecer informações adicionais sobre o código. Por exemplo, se você tiver uma função que aceita um número inteiro como argumento, poderá usar um Type Hint para indicar que o argumento deve ser um int
.
Contudo, é importante notar que Type Hints não tornam o Python uma linguagem estaticamente tipada. Além disso, Types Hints são completamente opcionais e não são necessários para escrever código Python. Você pode usá-los se quiser, mas eles não alteram a natureza dinâmica da linguagem.
Quando o recurso de Type Hints foi adicionado ao Python?
A princípio, os Type Hints foram adicionados ao Python na versão 3.5, lançada em setembro de 2015. Antes disso, não havia sintaxe na linguagem para especificar o tipo de dados de uma variável. No entanto, a ideia de usar Type Hints em Python existe há muito mais tempo. O conceito foi proposto pela primeira vez na forma de uma PEP (Python Enhancement Proposal) em 2000, mas não ganhou muita força na época. Ele acabou sendo aceito e implementado em 2015, após o lançamento do Python 3.
PEPs relacionadas ao Type Hints
Existem várias PEPs relacionadas a Type Hints. Elas são:
-
PEP 484, “Type Hints”, introduziu a sintaxe para especificar Type Hints em Python e definiu a semântica de como elas devem ser interpretadas por ferramentas como IDEs e verificadores de tipo. Esta PEP também introduziu o módulo
typing
, que fornece uma biblioteca padrão de definições de Type Hints. -
PEP 526, “Syntax for Variable Annotations”, estendeu a sintaxe de Type Hints para permitir que sejam usadas em variáveis, bem como em argumentos de função e valores de retorno. Esta PEP também introduziu o caractere
:
como um atalho para especificar o tipo de uma variável. -
PEP 589, “TypedDict: Type Hints for Dictionaries with a Fixed Set of Keys”, introduziu o tipo
TypedDict
, que permite que você especifique um tipo de dados para um dicionário que tenha um conjunto fixo de chaves. Isso permite que você especifique um tipo de dados mais específico para um dicionário, em vez de usar o tipo genéricoDict
. - PEP 563, “Postponed Evaluation of Annotations”, introduziu suporte para avaliação adiada de Type Hints. Isso permite que os Type Hints se refiram a nomes que ainda não foram definidos no momento em que o Type Hint é avaliado, o que pode ser útil em determinados casos.
Essas PEPs fornecem a base para o recurso de Type Hints no Python e definem a sintaxe e a semântica de como os Type Hints devem ser usados e interpretados.
Por que usar Type Hints?
Certamente existem várias razões pelas quais você pode querer usar Type Hints em seu código Python. Aqui estão algumas delas:
- Type Hints podem ser usadas por ferramentas como IDEs e verificadores de tipo para fornecer informações adicionais sobre o código. Por exemplo, uma IDE pode usar Type Hints para fornecer sugestões de código e verificar se você está usando o tipo correto de dados em uma variável.
- Type Hints podem ser usadas para documentar o código. Isso pode ser útil se você estiver trabalhando em um projeto com várias pessoas, pois permite que você especifique o tipo de dados que uma variável deve conter.
- Type Hints podem ser usadas para verificar se o código está usando o tipo correto de dados em uma variável. Isso pode ser útil para encontrar erros de digitação ou erros de lógica que podem ser difíceis de encontrar em tempo de execução.
- Type Hints podem ser usadas para otimizar o código em tempo de execução. Por exemplo, se você usar Type Hints para especificar que uma variável contém um número inteiro, o interpretador Python pode usar uma implementação mais rápida de operações matemáticas em vez de uma implementação genérica que funciona com qualquer tipo de dados.
No geral, o uso de Type Hints pode fornecer vários benefícios, mas não substitui testes e depurações cuidadosos. Elas são apenas uma ferramenta em seu kit de ferramentas para escrever código Python de alta qualidade.
Como usar Type Hints?
Agora que você sabe o que são Type Hints e por que elas são úteis, vamos ver como usá-las em seu código Python. A sintaxe de Type Hints é muito simples e consiste em dois operadores:
-
:
é usado para especificar o tipo de dados de uma variável. -
->
é usado para especificar o tipo de dados de um valor de retorno.
Por exemplo, aqui está uma função que aceita um argumento nome
e retorna uma string:
def saudacao(nome: str) -> str:
return f"Olá {nome}"
Tipos básicos
Para especificar o tipo de uma variável ou valor de retorno, você pode usar os tipos básicos do Python (por exemplo, str
, int
, bool
, etc.).
string: str = "Olá"
inteiro: int = 123
booleano: bool = True
ponto_flutuante: float = 1.23
string_bytes: bytes = b"Olá"
Tipos de coleções
Também é possível utilizar tipos referentes a coleções.
lista: list = [1, 2, 3]
tupla: tuple = (1, 2, 3)
conjunto: set = {1, 2, 3}
dicionario: dict = {"a": 1, "b": 2, "c": 3}
Ainda mais, é possível especificar o tipo de dados que cada elemento da coleção deve conter.
lista_inteiros: list[int] = [1, 2, 3]
tupla_inteiros: tuple[int, int, int] = (1, 2, 3)
conjunto_inteiros: set[int] = {1, 2, 3}
dicionario_strings_inteiros: dict[str, int] = {"a": 1, "b": 2, "c": 3}
No entanto, é importante notar que essa sintaxe só funciona a partir do Python 3.9. Se você estiver usando uma versão anterior do Python, você pode usar a sintaxe typing.List
, typing.Tuple
, typing.Set
e typing.Dict
para especificar o tipo de dados que cada elemento da coleção deve conter.
from typing import List, Tuple, Set, Dict
lista_inteiros: List[int] = [1, 2, 3]
tupla_inteiros: Tuple[int, int, int] = (1, 2, 3)
conjunto_inteiros: Set[int] = {1, 2, 3}
dicionario_strings_inteiros: Dict[str, int] = {"a": 1, "b": 2, "c": 3}
Tipagem de classes
Também é possível utilizar classes para especificar o tipo de dados de uma variável.
class Pessoa:
def __init__(self, nome: str, idade: int):
self.nome = nome
self.idade = idade
pessoa: Pessoa = Pessoa("João", 20)
Tipos especiais
Existem alguns tipos especiais que podem ser usados para especificar o tipo de dados de uma variável.
-
Any
: pode conter qualquer tipo de dados; -
Union
: pode conter um dos tipos de dados especificados; -
Optional
: pode conter um dos tipos de dados especificados ouNone
; -
Literal
: pode conter um dos valores literais especificados; -
Final
: pode especificar uma constante. -
Iterable
: pode conter qualquer tipo de dados que possa ser iterado, ou seja, qualquer tipo de dados que possa ser usado em um loopfor
. -
Sequence
: pode conter qualquer tipo de dados que possa ser indexado, ou seja, qualquer tipo que suporte à notação de colchetes; -
Mapping
: pode conter qualquer tipo de dados que possa ser mapeado; -
MutableMapping
: pode conter qualquer tipo de dados que possa ser mapeado e mutável; -
Callable
: pode conter qualquer tipo de dados que possa ser chamado.
from typing import (
Any,
Union,
Optional,
Literal,
Final,
Iterable,
Sequence,
Mapping,
MutableMapping,
Callable
)
variavel_any: Any = 123
variavel_union: Union[int, str] = 123
variavel_optional: Optional[int] = 123
variavel_literal: Literal[1, 2, 3] = 1
variavel_final: Final = 123
variavel_iterable: Iterable[int] = [1, 2, 3]
variavel_sequence: Sequence[int] = [1, 2, 3]
variavel_mapping: Mapping[str, int] = {"a": 1, "b": 2, "c": 3}
variavel_mutable_mapping: MutableMapping[str, int] = {"a": 1, "b": 2, "c": 3}
variavel_callable: Callable[[int, int], int] = lambda x, y: x + y
Para podermos entender melhor o que cada um desses tipos especiais faz, vamos criar uma função que recebe uma lista de números e retorna a soma de todos os números da lista.
from typing import Iterable
def soma_numeros(numeros: Iterable[int]) -> int:
soma = 0
for numero in numeros:
soma += numero
return soma
Para especificar que a função recebe uma lista de números, nós utilizamos o tipo Iterable[int]
. Com a finalidade de especificar que a função retorna um número, nós utilizamos o tipo int
. Aqui o tipo Iterable[int]
foi utilizado porque a função pode receber qualquer tipo de dados que possa ser iterado, ou seja, qualquer tipo de dados que possa ser usado em um loop for
. Por exemplo, a função pode receber uma lista de números, uma tupla de números, um conjunto de números, um dicionário de números, etc.
Agora vamos testar a função soma_numeros
com alguns tipos de dados diferentes.
print(soma_numeros([1, 2, 3]))
print(soma_numeros((1, 2, 3)))
print(soma_numeros({1, 2, 3}))
print(soma_numeros({"a": 1, "b": 2, "c": 3}))
Açúcar sintático para Union
Um açúcar sintático é uma sintaxe adicionada a uma linguagem de programação para tornar o código mais legível.
A partir do Python 3.10, foi adicionado um açúcar sintático para o tipo Union
. Agora nós podemos utilizar o operador |
para especificar que uma variável pode conter um dos tipos de dados especificados.
from typing import Union
variavel_union: Union[int, str] = 123
variavel_union: int | str = 123
Em ambos os exemplos acima, a variável variavel_union
pode conter um número inteiro ou uma string.
Integração com ferramentas
Como dito no começo deste artigo, o Python é uma linguagem de programação dinâmica e o uso de Type Hints não muda isso. Logo, o uso de Type Hints não impede que um tipo de dados errado seja passado para uma função. Por exemplo, se nós passarmos um tipo diferente do que foi informado com Type Hint, não haverá nenhum problema na execução do código.
Vejamos o seguinte exemplo:
def soma(x: int, y: int) -> int:
return x + y
print(soma("1", "2"))
Neste exemplo, nós definimos uma função chamada soma
que recebe dois parâmetros do tipo int
e retorna um tipo int
. Logo em seguida, nós chamamos a função soma
passando dois números do tipo str
como parâmetros. A função soma
irá somar os dois números e retornar o resultado. Porém, como os números passados como parâmetros são do tipo str
, a função soma
irá concatenar os dois números ao invés de somar.
A execução do código acima irá retornar o seguinte resultado:
12
Como podemos ver, o Python não impede que um tipo de dados errado seja passado para uma função. Porém, podemos utilizar ferramentas para nos ajudar a evitar esse tipo de problema.
Existem diversas ferramentas que podem ser utilizadas para verificar se os tipos de dados passados como parâmetros para uma função são os mesmos que foram definidos com Type Hints. Algumas dessas ferramentas são:
Neste artigo, nós iremos utilizar a ferramenta mypy.
Mypy
O mypy é uma ferramenta que pode ser utilizada para verificar se os tipos definidos pelos Types Hints são os mesmos que estão sendo utilizados.
Instalação do Mypy
Para podermos utilizar o mypy, nós precisamos instalar a ferramenta. Para isso, nós utilizamos o gerenciador de pacotes pip.
Caso esteja utilizando o Linux ou macOS, execute o seguinte comando no terminal:
pip3 install mypy
Caso esteja utilizando o Windows, execute este outro comando:
pip install mypy
Utilizando o Mypy
Após a instalação do mypy, nós podemos utilizar a ferramenta para realizar a verificação de tipagem em nosso código.
Para realizar um teste, vamos criar um arquivo chamado teste.py
e adicionar o seguinte código:
def soma(x: int, y: int) -> int:
return x + y
print(soma("1", "2"))
Em seguida, vamos executar o mypy para verificar se os tipos de dados passados como parâmetros para a função soma
são os mesmos que foram definidos com Type Hints.
Para isso, vamos executar o seguinte comando no terminal:
mypy teste.py
O resultado da execução do comando acima irá ser o seguinte:
teste.py:4: error: Argument 1 to "soma" has incompatible type "str"; expected "int" [arg-type]
teste.py:4: error: Argument 2 to "soma" has incompatible type "str"; expected "int" [arg-type]
Found 2 errors in 1 file (checked 1 source file)
Como podemos ver, o mypy retornou dois erros. O primeiro erro indica que o primeiro parâmetro passado para a função soma
é do tipo str
ao invés de int
. O segundo erro indica que o segundo parâmetro passado para a função soma
é do tipo str
ao invés de int
.
Nesse exemplo nós utilizamos o mypy para verificar um arquivo chamado teste.py
. Porém, nós podemos utilizar o mypy para verificar todos os arquivos de um projeto. Para isso, nós podemos utilizar o seguinte comando:
mypy .
O .
, no comando acima, indica que o mypy irá verificar todos os arquivos do diretório atual.
Configurando o Mypy
Por fim, também é possível utilizar um arquivo de configuração para o mypy. Para isso, nós podemos criar um arquivo chamado mypy.ini
e adicionar o seguinte conteúdo:
[mypy]
ignore_missing_imports = True
Com o arquivo de configuração acima, nós estamos informando ao mypy para ignorar os erros de importação de módulos.
Existem diversas outras opções que podem ser utilizadas no arquivo de configuração do mypy. Para mais informações, acesse a documentação do mypy referente ao arquivo de configuração.
Conclusão
Neste artigo, nós aprendemos o que são Type Hints, como essa funcionalidade pode ser utilizada para nos ajudar a escrever códigos mais seguros e como podemos utilizar a ferramenta mypy para realizar a verificação dos tipos.
Este é um recurso muito poderoso e pode ser utilizado para nos ajudar a escrever código mais seguro. Porém, é importante lembrar que Type Hints não são uma solução mágica para todos os problemas de tipagem.
Por fim, é importante lembrar que Type Hints não são uma funcionalidade obrigatória. Sendo assim, é importante que você avalie se essa funcionalidade é realmente necessária para o seu projeto.