Type Hints em Python 3

A nova (nem tão nova assim) versão da linguagem Python adicionou suporte à type hints. Essa funcionalidade chegou para melhorar o mecanismo de tipagem da linguagem. Sabemos que Python é uma linguagem dinamicamente tipada e que há vários debates sobre os benefícios e maleficios desse sistema de tipos nas comunidades de pessoas desenvolvedoras. Há os que são favoráveis a tipos estáticos e outros que são aos tipos dinâmicos.
Quando falamos de tipos estáticos, estamos dizendo que o compilador ou interpretador conhece todos os tipos que estão no seu código antes de executar o programa, assim, ele pode checar se há alguma inconsistência entre os tipos. Caso sim, o compilador retornará um erro e não executará o programa. Com tipos dinâmicos isso não acontece. O compilador/interpretador não faz nenhuma checagem inicial dos tipos e seus relacionamentos, executando o programa imediatamente. Caso a pessoa tenha cometido alguma inconsistência entre tipos, ela pode não ser avisada pelo compilador/interpretador e o programa provavelmente não funcionará da maneira pretendida.
Digamos que você acidentalmente quis somar o número 1 com a string "1". Sabemos que 1 é do tipo numeral e que "1" é do tipo textual.
Logo, não faz sentido somar um número com um texto. Vamos ver como linguagens estáticas e dinâmicas se comportam com esse problema?

Em Haskell (estaticamente tipada)

Prelude> 1 + "1"

<interactive>:2:1: error:
• No instance for (Num [Char]) arising from a use of ‘+’
• In the expression: 1 + "1"
In an equation for ‘it’: it = 1 + "1"

Em JavaScript (dinamicamente tipada)

> 1 + "1"
'11'

Viu como as linguagens retornam coisas diferentes? O JavaScript retornou um resultado errado pois ela faz a coerção de tipos implícita entre strings e números. Felizmente Python possui um sistema de tipos mais robusto, mas ainda deixa passar alguns erros.

Python2 (dinamicamente tipada)

>>> 1 + "1"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Mas se fizermos:

>>> [1] * -1
[]
# Tentamos multiplicar uma lista com um número negativo, mas não fomos avisados do erro :(

Então surge nossa salvação para esse problema: Type Hints!
Com type hints, podemos dizer explicitamente a qual tipo tal dado pertence. Assim ajudamos o interpretador a executar o código corretamente, pois ele não precisa “adivinhar” o tipo, e, caso haja alguma inconsistência, ela será avisada ao invés de fazer uma coerção ou qualquer outro malabarismo que interpretadores podem fazer.

Type Annotations

Com anotações de tipos, dizemos explicitamente qual tipo variáveis, funções, parâmetros pertencem. Isso permite que se escreva códigos auto-documentáveis, mais legíveis e até mesmo usar type-checkers separados da plataforma oficial Python, como o mypy. Versões Python 3.5 e anteriores suportam somente anotação de funcões. A partir da versão 3.6, temos suporte a anotação de variáveis.
As anotações são feitas da seguinte forma:

# Anotação de variável
username: str

# Anotação de funções
def circle_area(radius: float) -> float:
from math import pi
return pi * (radius ** 2)

def say_hi(username: str) -> None:
print('Hi ' + username)

Type Aliases

Agora imagine que você tem uma função que recebe tipos mais complexos…

from typing import List, Tuple

def rate_users(users: List[str], rates: List[int]) -> List[Tuple[str, int]]:
return list(zip(users, rates))

O código acima parece bem feio e deselegante, né? Ainda bem que temos type aliases para simplificar e deixar nosso código lindoso.

from typing import List, Tuple

UserList = List[str]
Rates = List[int]
RatedUsers = List[Tuple[str, int]]

def rate_users(users: UserList, rates: Rates) -> RatedUsers:
return list(zip(users, rates))

Viu como nosso código ficou mais expressivo? Type aliases servem justamente para isso: dar apelidos para tipos (geralmente mais complexos) com o objetivo de simplificar ou expressar melhor nossas intenções.
Para o interpretador, esses dois códigos são equivalentes, mas para nossos olhos humanos a segunda versão é bem melhor.