[[ch_type_hints_def]]
== Dicas de tipo em funções
:example-number: 0
:figure-number: 0

[quote, Guido van Rossum&#44; Jukka Lehtosalo&#44; and Łukasz Langa, PEP 484—Type Hints]
____
É preciso enfatizar que *Python continuará sendo uma linguagem de tipagem dinâmica,
e os autores não têm qualquer intenção de algum dia tornar dicas de tipo obrigatórias,
mesmo por mera convenção*.footnote:[«_PEP 484—Type Hints_» [.small]#&#91;fpy.li/8-1&#93;#, seção _Rationale and Goals_; negritos mantidos do original.]
____

Dicas de tipo((("functions, type hints in",
"benefits and drawbacks of")))((("type hints (type annotations)", "benefits and drawbacks of")))
foram a maior mudança na história de Python desde
«a unificação de tipos e classes» [.small]#&#91;fpy.li/descr101&#93;#
no Python 2.2, lançado em 2001.
Entretanto, as dicas de tipo não beneficiam igualmente a todas as pessoas que usam Python.
Por isso deverão ser sempre opcionais.

A
«_PEP 484—Type Hints_ (Dicas de Tipo)» [.small]#&#91;fpy.li/pep484&#93;#
introduziu a sintaxe e a semântica para declarações explícitas de tipo
em argumentos de funções, valores de retorno e variáveis.
O objetivo é ajudar ferramentas de desenvolvimento a encontrarem bugs no código-fonte de programas em Python
através de análise estática, isto é, sem executar o código através de testes.

Os maiores beneficiários são desenvolvedores profissionais que usam
IDEs (_Ambientes Integrados de Desenvolvimento_)
e CI (_Integração Contínua_).
A análise de custo-benefício que torna as dicas de tipo atrativas para esse grupo não se aplica a todas pessoas que programam em Python.

A base de usuários de Python vai muito além dessa classe de profissionais.
Ela inclui cientistas, comerciantes, jornalistas, artistas, inventores, analistas e estudantes de inúmeras áreas, entre outros.
Para a maioria dessas pessoas, o custo de aprender dicas de tipo será maior—exceto para
a minoria que já conhece outra linguagem com tipos estáticos, subtipos e tipos genéricos.
Os benefícios serão menores para muitas pessoas, dada a forma como que elas interagem com Python,
o tamanho menor de suas bases de código e de suas equipes—muitas vezes "equipes solo".

A tipagem dinâmica, default de Python, é mais simples e mais expressiva quando
programamos para explorar dados e ideias, como é o caso em ciência de dados, computação criativa e para aprender a programar.

<<<
Este capítulo se concentra nas dicas de tipo de Python nas assinaturas de função.
O «Capítulo 15» [.small]#&#91;vol.2, fpy.li/54&#93;# explora as dicas de tipo no contexto de classes e outros recursos do módulo `typing`.

Os((("functions, type hints in", "topics covered")))((("type hints (type annotations)", "topics covered")))
tópicos mais importantes aqui são:

* Uma introdução prática à tipagem gradual com Mypy
* As perspectivas complementares da duck typing (_tipagem pato_) e da tipagem nominal
* A revisão das principais categorias de tipos que podem surgir em anotações (cerca de 60% do capítulo)
* Dicas de tipo em parâmetros variádicos (`\*args`, `**kwargs`)
* Limitações e desvantagens das dicas de tipo e da tipagem estática.

=== Novidades neste capítulo

Este((("functions, type hints in", "significant changes to")))((("type hints (type annotations)", "significant changes to")))
capítulo é completamente novo.
As dicas de tipo apareceram no Python 3.5, após eu ter terminado de escrever a primeira edição de _Python Fluente_.

Dadas as limitações de um sistema de tipagem estática,
a melhor ideia da PEP 484 foi propor um _sistema de tipagem gradual_.
Vamos começar definindo o que isso significa.

=== Sobre tipagem gradual

A PEP 484((("functions, type hints in", "gr adual typing",
id="FTHgrad08")))((("type hints (type  annotations)", "gradual typing",
id="THgrad0 8")))((("gradual type system", "basics of")))
introduziu no Python um _sistema de tipagem gradual_.
Outras linguagens com sistemas de tipagem gradual são Typescript da Microsoft,
Dart (a linguagem do SDK Flutter, criado pelo Google),
e Hack (um dialeto de PHP que é compilado para a máquina virtual HHVM do Facebook).
O próprio checador de tipos MyPy começou como uma linguagem:
um dialeto de Python de tipagem gradual com seu próprio interpretador.
Guido van Rossum convenceu o criador do MyPy, Jukka Lehtosalo,
a transformá-lo em uma ferramenta para checar código Python anotado.

Eis uma função com anotações de tipos:

[source, python]
----
def tokenize(s: str) -> list[str]:
    "Convert a string into a list of tokens."
    return s.replace('(', ' ( ').replace(')', ' ) ').split()
----

A assinatura informa: `tokenize` recebe uma `str`
e devolve uma `list[str]`: uma lista de strings.
Usaremos esta função em um _parser_ na «Seção 18.3.3» [.small]#&#91;vol.3, fpy.li/5e&#93;#.

Um sistema de tipagem gradual:

É opcional::
Por default, o checador de tipos não deve emitir avisos para código que não tenha dicas de tipo.
Em vez disso, o verificador supõe o tipo `Any` quando não consegue determinar o tipo de um objeto.
O tipo `Any` é considerado compatível com todos os outros tipos.
Não captura erros de tipagem durante a execução do código::
Dicas de tipo são usadas por checadores de tipos, analisadores de código-fonte (_linters_) e IDEs para emitir avisos.
Eles não evitam que valores inconsistentes sejam passados para funções ou atribuídos a variáveis durante a execução.
Por exemplo, nada impede que alguém chame
`tokenize(42)`, apesar da anotação de tipo do argumento `s: str`.
A chamada ocorrerá, e teremos um erro de execução no corpo da função.
Não melhora o desempenho::
Anotações de tipo fornecem dados que poderiam, em tese, permitir otimizações do bytecode gerado.
Mas, até julho de 2021, tais otimizações não ocorrem em nenhum ambiente Python que eu
conheça.footnote:[Um compilador JIT ("just-in-time", compiladores que transformam o bytecode
gerado pelo interpretador em código da máquina-alvo no momento da execução)
como o do PyPy tem informações muito melhores que as dicas de tipo:
ele monitora o programa Python durante a execução, detecta os tipos concretos em uso,
e gera código de máquina otimizado para aqueles tipos concretos.]

O melhor aspecto de usabilidade da tipagem gradual é que as anotações são sempre opcionais.

Nos sistemas de tipagem estáticos, a maioria das restrições de tipo são fáceis de expressar,
muitas são desajeitadas, muitas são difíceis e algumas são impossíveis.
Por exemplo, em julho de 2021,
tipos recursivos não tinham suporte—veja as questões
«_182, Define a JSON type_» [.small]#&#91;fpy.li/8-2&#93;#
«_731, Support recursive types_» [.small]#&#91;fpy.li/8-3&#93;# do MyPy.

É possível que você escreva um ótimo programa em Python,
com uma boa cobertura de testes, todos passando,
mas ainda assim não consiga escrever dicas de tipo que satisfaçam um checador de tipos.
Nesse caso, omita as dicas de tipo problemáticas e entregue o programa!

Dicas de tipo são opcionais em todos os níveis:
você pode criar ou usar pacotes inteiros sem dicas de tipo,
pode silenciar o verificador ao importar algum módulo sem dicas de tipo,
e você também pode colocar comentários especiais,
para que o checador de tipos ignore certas linhas do seu código.

[TIP]
====
Tentar impor uma cobertura de 100% de dicas de tipo irá provavelmente estimular seu uso de forma impensada,
apenas para satisfazer essa métrica.
Isso também vai impedir equipes de aproveitarem da melhor forma possível o potencial e a flexibilidade de Python.
Código sem dicas de tipo deve ser aceito sem objeções quando anotações tornam
o uso de uma API menos amigável ou quando complicam demais seu desenvolvimento.
====

=== Tipagem gradual na prática

Vamos ((("gradual type system", "in practice", id="GRSpract08"))) ver como a tipagem gradual funciona na prática,
começando com uma função simples e acrescentando gradativamente a ela dicas de tipo,
guiados pelo((("Mypy type checker", id="mypy08"))) Mypy.

[NOTE]
====
Há muitos((("Python type checkers"))) checadores de tipos para Python compatíveis com a PEP 484,
incluindo o «pytype» [.small]#&#91;fpy.li/8-4&#93;# do Google,
o «Pyright» [.small]#&#91;fpy.li/8-5&#93;# da Microsoft,
o «Pyre» [.small]#&#91;fpy.li/8-6&#93;# do Facebook—além de verificadores incluídos em IDEs como o PyCharm.
Eu escolhi usar o «Mypy» [.small]#&#91;fpy.li/mypy&#93;# nos exemplos por ele ser o mais conhecido.
Entretanto, algum daqueles outros pode ser mais adequado para alguns projetos ou equipes.
O Pytype, por exemplo, foi projetado para lidar com bases de código sem nenhuma dica de tipo e ainda assim gerar recomendações úteis.
Ele é mais tolerante que o MyPy, e consegue também gerar anotações para o seu código.
====

Vamos anotar uma função `show_count`, que retorna uma string com um número e uma palavra no singular ou no plural, dependendo do número:

[source, python]
----
include::../code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT_DOCTEST]
----

<<msgs_no_hints>> mostra o código-fonte de `show_count`, sem anotações.

[[msgs_no_hints]]
.`show_count` de _messages.py_ sem dicas de tipo.
====
[source, python]
----
include::../code/08-def-type-hints/messages/no_hints/messages.py[tags=SHOW_COUNT]
----
====

==== Usando o Mypy

Para começar a checagem de tipos, rodamos o comando `mypy` passando o módulo _messages.py_ como parâmetro:

[source]
----
…/no_hints/ $ pip install mypy
[muitas mensagens omitidas...]
…/no_hints/ $ mypy messages.py
Success: no issues found in 1 source file
----

Na configuração default, o Mypy não reporta problemas com o <<msgs_no_hints>>.

[WARNING]
====
Durante a revisão deste capítulo estou usando Mypy 0.910, a versão mais recente no momento (em julho de 2021).
A «_Introduction_» [.small]#&#91;fpy.li/8-7&#93;# do Mypy adverte que ele "é oficialmente software beta.
Mudanças ocasionais irão quebrar a compatibilidade com versões mais antigas."
O Mypy está gerando pelo menos um relatório diferente daquele que recebi quando escrevi o capítulo, em abril de 2020.
E quando você estiver lendo essas linhas, talvez os resultados também sejam diferentes daqueles mostrados aqui.
====

Se a assinatura de uma função não tem anotações, Mypy a ignora por default—a menos que seja configurado de outra forma.

O <<msgs_test_no_hints>> também inclui testes unitários do `pytest`.
Este é o código de _messages_test.py_:

[[msgs_test_no_hints]]
._messages_test.py_ sem dicas de tipo.
====
[source, python]
----
include::../code/08-def-type-hints/messages/no_hints/messages_test.py[]
----
====

Agora vamos acrescentar dicas de tipo, guiados pelo Mypy.


==== Tornando o Mypy mais rigoroso

A opção de linha de comando `--disallow-untyped-defs` faz o Mypy apontar todas as
definições de funções que não tenham dicas de tipo para todos os argumentos e para o valor de retorno.

Usando `--disallow-untyped-defs` com o arquivo de teste produz três erros e uma observação:

[source]
----
…/no_hints/ $ mypy --disallow-untyped-defs messages_test.py
messages.py:14: error: Function is missing a type annotation
messages_test.py:10: error: Function is missing a type annotation
messages_test.py:15: error: Function is missing a return type annotation
messages_test.py:15: note: Use "-> None" if function does not return a value
Found 3 errors in 2 files (checked 1 source file)
----

Nas primeiras etapas da tipagem gradual, prefiro usar outra opção:

`--disallow-incomplete-defs`

Com ela, o Mypy não me dá nenhuma nova informação num primeiro momento:

[source]
----
…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py
Success: no issues found in 1 source file
----

Agora acrescento apenas o tipo do retorno a `show_count` em _messages.py_:

[source, python]
----
def show_count(count, word) -> str:
----

Isso é suficiente para fazer o Mypy olhar para o código.
Usando a mesma linha de comando anterior para verificar _messages_test.py_
fará o Mypy examinar novamente o _messages.py_:

[source]
----
…/no_hints/ $ mypy --disallow-incomplete-defs messages_test.py
messages.py:14: error: Function is missing a type annotation
for one or more arguments
Found 1 error in 1 file (checked 1 source file)
----

Agora posso gradualmente acrescentar dicas de tipo, função por função,
sem receber avisos sobre as funções onde ainda não adicionei anotações
Essa é uma assinatura completamente anotada que satisfaz o Mypy:

[source, python]
----
def show_count(count: int, word: str) -> str:
----

[TIP]
====
Em vez de digitar opções de linha de comando como [.keep-together]#`--disallow-incomplete-defs`#,
você pode salvar sua configuração favorita da forma descrita na página
«Mypy configuration file» [.small]#&#91;fpy.li/8-8&#93;# na documentação do Mypy.
Você pode incluir configurações globais e configurações específicas para cada módulo.
Aqui está um _mypy.ini_ simples, para servir de base:

----
[mypy]
python_version = 3.9
warn_unused_configs = True
disallow_incomplete_defs = True
----
====


==== Um valor default para um argumento

A função `show_count` no <<msgs_no_hints>> só funciona com substantivos regulares.
Se o plural não pode ser composto acrescentando um `'s'`, devemos deixar o usuário fornecer a forma plural, assim:

[source, python]
----
>>> show_count(3, 'mouse', 'mice')
'3 mice'
----

Vamos experimentar um pouco de "desenvolvimento orientado a tipos."
Primeiro acrescento um teste usando aquele terceiro argumento.
Não esqueça de adicionar a dica do tipo de retorno à função de teste,
senão o Mypy não vai inspecioná-la.

[source, python]
----
def test_irregular() -> None:
    got = show_count(2, 'child', 'children')
    assert got == '2 children'
----

O Mypy detecta o erro:
[source, python]
----
…/hints_2/ $ mypy messages_test.py
messages_test.py:22: error: Too many arguments for "show_count"
Found 1 error in 1 file (checked 1 source file)
----

Então edito `show_count`, acrescentando o argumento opcional `plural`:

[[msgs_optional_str_param]]
.`showcount` de _hints_2/messages.py_ com um argumento opcional
====
[source, python]
----
include::../code/08-def-type-hints/messages/hints_2/messages.py[tags=SHOW_COUNT]
----
====

E agora o Mypy reporta "Success."

[WARNING]
====
Aqui está um erro de digitação que Python não reconhece.
Você consegue encontrá-lo?

[source, python]
----
def hex2rgb(color=str) -> tuple[int, int, int]:
----

O relatório de erros do Mypy não é muito útil:

[source]
----
colors.py:24: error: Function is missing a type
    annotation for one or more arguments
----

A dica de tipo para o argumento `color` deveria ser `color: str`.
Escrevi `color=str`, que não é uma anotação: ele determina que o valor default de `color` é `str`.
Na minha experiência, esse é um erro comum e fácil de passar desapercebido,
especialmente em dicas de tipo complexas.
====

Os seguintes detalhes são considerados um bom estilo para dicas de tipo:

* Sem espaço entre o nome do parâmetro e o `:`; um espaço após o `:`
* Espaços dos dois lados do `=` que precede um valor default de parâmetro.

Por outro lado, a PEP 8 diz que não deve haver espaço em torno de `=`
se não há nenhuma dica de tipo para aquele parâmetro específico.

.Estilo de Código: use flake8 e blue

****
Em vez de((("flake8 tool")))((("blue tool"))) decorar essas regrinhas bobas, use ferramentas como
«_flake8_» [.small]#&#91;fpy.li/8-9&#93;# e «_blue_» [.small]#&#91;fpy.li/8-10&#93;#. O
_flake8_ informa sobre o estilo do código e várias outras questões,
enquanto o _blue_ reescreve o código-fonte com base na (maioria) das regras prescritas pela ferramenta de formatação de código
«_black_» [.small]#&#91;fpy.li/8-11&#93;#.

Se o objetivo é impor um estilo de programação "padrão", _blue_ é melhor que _black_,
porque segue o estilo próprio de Python, de usar aspas simples por default e aspas duplas como alternativa.

[source, python]
----
>>> "I prefer single quotes"
'I prefer single quotes'
----

No CPython, a preferência por aspas simples está incorporada no `repr()`, entre outros lugares.
O módulo «_doctest_» [.small]#&#91;fpy.li/doctest&#93;# depende do `repr()` usar aspas simples por default.

Um dos autores do _blue_ é «Barry Warsaw» [.small]#&#91;fpy.li/8-12&#93;#, co-autor da PEP 8,
core developer de Python desde 1994 e membro de Python's Steering Council desde 2019.
Daí estamos em ótima companhia quando escolhemos usar aspas simples.

Se você precisar mesmo usar o _black_, use a opção `black -S`. Isso deixará suas aspas intocadas.
****


[[dealing_with_none_sec]]
==== Usando None como default
No <<msgs_optional_str_param>>, o parâmetro `plural` está anotado como `str`,
e o valor default é `''`. Assim não há conflito de tipo.

Eu gosto dessa solução, mas em outros contextos `None` é um default melhor.
Se o parâmetro opcional requer um tipo mutável, então `None` é o único default sensato,
como vimos na <<mutable_default_parameter_sec>>.

Com `None` como default para o parâmetro `plural`, a assinatura ficaria assim:

[source, python]
----
from typing import Optional

def show_count(count: int, singular: str, plural: Optional[str] = None) -> str:
----

Vamos destrinchar essa linha:

* `Optional[str]` significa que `plural` pode ser uma `str` ou `None`.
* É obrigatório fornecer explicitamente o valor default `= None`.

Se você não atribuir um valor default a `plural`, o runtime de Python vai tratar o parâmetro como obrigatório.
Lembre-se: durante a execução do programa, as dicas de tipo são ignoradas.

Veja que é preciso importar `Optional` do módulo `typing`. Quando importamos tipos,
é uma boa prática usar a sintaxe `from typing import X`, para reduzir o tamanho das assinaturas das funções.

[WARNING]
====
`Optional` não é um bom nome, pois aquela anotação não torna o argumento opcional.
O que o torna opcional é a atribuição de um valor default ao parâmetro.
`Optional[str]` significa apenas: o tipo desse parâmetro pode ser `str` ou `NoneType`.
Nas linguagens Haskell e Elm, um tipo parecido se chama `Maybe`.
====

Agora que tivemos um primeiro contato concreto com a tipagem gradual,
vamos examinar o que o conceito de _tipo_ significa na
prática.((("", startref="FTHgrad08")))((("", startref="THgrad08")))((("", startref="GRSpract08")))((("", startref="mypy08")))

[[types_defined_by_ops_sec]]
=== Tipos são definidos pelas operações possíveis


[quote, PEP 483—A Teoria das Dicas de Tipo]
____

Há muitas definições do conceito de tipo na literatura.
Aqui vamos assumir que tipo é um conjunto de valores e
um conjunto de funções que podem ser aplicadas àqueles valores.
____

Na((("functions, type hints in", "supported operations and", id="FTHsupport08")))((("type hints (type annotations)",
"supported operations and", id="THsup08"))) prática,
é mais útil considerar o conjunto de operações possíveis como a caraterística que define um tipo.footnote:[Em
Python não há sintaxe para controlar o conjunto de possíveis valores de um tipo,
exceto para tipos `Enum`.
Por exemplo, não é possível, usando dicas de tipo,
definir `Quantity` como um número inteiro entre 1 e 10000,
ou `AirportCode` como uma sequência de 3 letras.
O NumPy oferece `uint8`, `int16`, e outros tipos numéricos ligados à arquitetura do hardware,
mas na biblioteca padrão de Python encontramos apenas
tipos com conjuntos muitos pequenos de valores (`NoneType`, `bool`)
ou conjuntos muito grandes (`float`, `int`, `str`, todas as tuplas possíveis, etc.).]

Por exemplo, pensando nas operações possíveis,
quais são os tipos válidos para `x` na função a seguir?

[source, python]
----
def double(x):
    return x * 2
----

O tipo do parâmetro `x` pode ser numérico (`int`, `complex`, `Fraction`, `numpy.uint32`, etc.),
mas também pode ser uma sequência (`str`, `tuple`, `list`, `array`), uma `numpy.array` N-dimensional,
ou qualquer outro tipo que implemente ou herde um método `+__mul__+` que aceite um inteiro como argumento.

Entretanto, considere a anotação `double` abaixo.
Ignore por enquanto a ausência do tipo do retorno, vamos nos concentrar no tipo do parâmetro:

[source, python]
----
from collections import abc

def double(x: abc.Sequence):
    return x * 2
----

Um checador de tipos irá rejeitar esse código.
Se você informar ao Mypy que `x` é do tipo `abc.Sequence`, ele vai marcar `x * 2` como erro,
pois a «`Sequence` ABC» [.small]#&#91;fpy.li/8-13&#93;# não implementa ou herda o método `+__mul__+`.
Durante a execução, o código vai funcionar com sequências concretas
como `str`, `tuple`, `list`, `array`, etc., bem como com números,
pois durante a execução as dicas de tipo são ignoradas.
Mas o checador de tipos se preocupa apenas com o que
estiver explicitamente declarado, e `abc.Sequence` não suporta `+__mul__+`.

Por essa razão o título dessa seção é  "Tipos São Definidos pelas Operações Possíveis."
O runtime de Python aceita qualquer objeto como argumento `x` nas duas versões da função `double`.
O cálculo de `x * 2` pode funcionar, ou pode causar um `TypeError`,
se a operação não for suportada por `x`.
Por outro lado, Mypy vai marcar `x * 2` como um erro quando analisar o código-fonte anotado de `double`, pois é uma operação não suportada pelo tipo declarado `x: abc.Sequence`.

<<<
Em um sistema de tipagem gradual, acontece uma interação entre duas perspectivas diferentes de tipo:

Duck typing ("tipagem pato")::
A ((("duck typing"))) perspectiva adotada pelo Smalltalk—a primeira linguagem orientada a objetos—bem
como em Python, JavaScript, e Ruby.
Objetos têm tipo, mas variáveis (incluindo parâmetros) não tem.
Na prática, não importa qual o tipo declarado de um objeto, importam apenas as operações que ele efetivamente suporta.
Se eu posso invocar `birdie.quack()` então, nesse contexto, `birdie` é um pato.
Por definição, duck typing só é aplicada durante a execução, quando se tenta aplicar operações sobre os objetos.
Isso é mais flexível que a _tipagem nominal_,
ao preço de permitir mais erros durante a execução.footnote:[Duck typing é uma forma implícita de
_tipagem estrutural_, que as anotações de tipo passaram a suportar explicitamente após a versão 3.8,
com a introdução de `typing.Protocol`.
Vamos falar disso mais adiante nesse capítulo, em <<protocols_in_fn_sec>>, e com mais detalhes no
«Capítulo 13» [.small]#&#91;vol.2, fpy.li/4q&#93;.&#93;#]


Tipagem nominal::
É a ((("nominal typing"))) perspectiva adotada em {cpp}, Java, e C#, e suportada em Python anotado.
Objetos e variáveis têm tipos.
Mas objetos só existem durante a execução, e o checador de tipos só se importa com o código-fonte,
onde as variáveis (incluindo parâmetros de função) têm anotações com dicas de tipo.
Se `Duck` é uma subclasse de `Bird`,
você pode atribuir uma instância de `Duck` a um parâmetro anotado como `birdie: Bird`.
Mas no corpo da função, o verificador considera a chamada `birdie.quack()` ilegal,
pois `birdie` é nominalmente um `Bird`, e aquela classe não fornece o método `.quack()`.
Não interessa que o argumento real, durante a execução, é um `Duck`,
porque a tipagem nominal é verificada estaticamente.
O checador de tipos não executa qualquer pedaço do programa, ele apenas analisa o código-fonte.
Isso é mais rígido que _duck typing_,
com a vantagem de capturar alguns bugs durante o desenvolvimento,
ou mesmo em tempo real, enquanto o código está sendo digitado em um IDE.

O <<birds_module_ex>> é um exemplo bobo que contrapõe duck typing e tipagem nominal,
bem como checagem de tipos estática e comportamento durante a execução.footnote:[Muitas
vezes a herança é sobreutilizada e difícil de justificar em exemplos pequenos.
Então por favor aceite esse exemplo com animais como uma rápida ilustração de sub-tipagem.]

<<<
[[birds_module_ex]]
._birds.py_
====
[source, python]
----
include::../code/08-def-type-hints/birds/birds.py[]
----
====
<1> `Duck` é uma subclasse de `Bird`.
<2> `alert` não tem dicas de tipo, então o verificador a ignora.
<3> `alert_duck` aceita um argumento do tipo `Duck`.
<4> `alert_bird` aceita um argumento do tipo `Bird`.

Verificando _birds.py_ com Mypy, encontramos um problema:

[source]
----
…/birds/ $ mypy birds.py
birds.py:16: error: "Bird" has no attribute "quack"
Found 1 error in 1 file (checked 1 source file)
----

Só de analisar o código-fonte, Mypy percebe que `alert_bird` é problemático:
a dica de tipo declara o parâmetro `birdie` como do tipo `Bird`,
mas o corpo da função invoca `birdie.quack()`, e a classe `Bird` não tem esse método.

Agora vamos tentar usar o módulo `birds` em _daffy.py_ no <<daffy_module_ex>>.

<<<
[[daffy_module_ex]]
._daffy.py_
====
[source, python]
----
include::../code/08-def-type-hints/birds/daffy.py[]
----
====
<1> Chamada válida, pois `alert` não tem dicas de tipo.
<2> Chamada válida, pois `alert_duck` recebe um argumento do tipo `Duck` e `daffy` é um `Duck`.
<3> Chamada válida, pois `alert_bird` recebe um argumento do tipo `Bird`,
e `daffy` também é um `Bird`, a superclasse de `Duck`.

Mypy reporta o mesmo erro em _daffy.py_, sobre a chamada a `quack` na função `alert_bird` definida em _birds.py_:

[source]
----
…/birds/ $ mypy daffy.py
birds.py:16: error: "Bird" has no attribute "quack"
Found 1 error in 1 file (checked 1 source file)
----

Mas o Python não vê qualquer problema com _daffy.py_ em si: as três chamadas de função estão OK.

Agora, rodando  _daffy.py_, o resultado é o seguinte:

[source]
----
…/birds/ $ python3 daffy.py
Quack!
Quack!
Quack!
----

Funciona perfeitamente! Viva o duck typing!

Durante a execução do programa, Python não se importa com os tipos declarados.
Ele usa apenas duck typing.
O Mypy apontou um erro em `alert_bird`, mas a chamada da função com `daffy` funciona corretamente quando executada.
À primeira vista isso pode surpreender muitos pythonistas:
um checador de tipos estático muitas vezes encontra erros em código que sabemos que vai funcionar quanto executado.

Entretanto, se daqui a alguns meses você for encarregado de estender o exemplo bobo do pássaro,
você agradecerá ao Mypy.
Observe esse módulo _woody.py_ module, que também usa `birds`, no <<woody_module_ex>>.

[[woody_module_ex]]
._woody.py_
====
[source, python]
----
include::../code/08-def-type-hints/birds/woody.py[]
----
====

O Mypy encontra dois erros ao verificar _woody.py_:

[source]
----
…/birds/ $ mypy woody.py
birds.py:16: error: "Bird" has no attribute "quack"
woody.py:5: error: Argument 1 to "alert_duck" has incompatible type
"Bird"; expected "Duck"
Found 2 errors in 2 files (checked 1 source file)
----

O primeiro erro é em _birds.py_: a chamada a `birdie.quack()` em `alert_bird`, que já vimos antes.
O segundo erro é em _woody.py_: `woody` é uma instância de `Bird`,
então a chamada `alert_duck(woody)` é inválida, pois aquela função exige um `Duck.`
Todo `Duck` é um `Bird`, mas nem todo `Bird` é um `Duck`.

Durante a execução, nenhuma das duas chamadas em _woody.py_ funcionariam.
A sucessão de falhas é melhor ilustrada em uma sessão no console,
através das mensagens de erro, no <<birdie_errors_ex>>.

<<<
[[birdie_errors_ex]]
.Erros durante a execução e como o Mypy poderia ter ajudado
====
[source, python]
----
>>> from birds import *
>>> woody = Bird()
>>> alert(woody)  # <1>
Traceback (most recent call last):
  ...
AttributeError: 'Bird' object has no attribute 'quack'
>>>
>>> alert_duck(woody) # <2>
Traceback (most recent call last):
  ...
AttributeError: 'Bird' object has no attribute 'quack'
>>>
>>> alert_bird(woody)  # <3>
Traceback (most recent call last):
  ...
AttributeError: 'Bird' object has no attribute 'quack'
----
====
<1> O Mypy não tinha como detectar esse erro, pois não há dicas de tipo em `alert`.
<2> O Mypy avisou do problema: `Argument 1 to "alert_duck" has incompatible type "Bird";
expected "Duck"` (_Argumento 1 para `alert_duck` é do tipo incompatível "Bird"; argumento esperado era "Duck"_)
<3> O Mypy está avisando desde o <<birds_module_ex>> que o corpo da função `alert_bird` está errado:
`"Bird" has no attribute "quack"` (_Bird não tem um atributo "quack"_)

Este pequeno experimento mostra que o duck typing é mais fácil para o iniciante e mais flexível,
porém permite que operações não suportadas causem erros durante a execução.
A tipagem nominal detecta os erros antes da execução,
mas algumas vezes rejeita código que seria executado sem erros—como
a chamada a `alert_bird(daffy)` no <<daffy_module_ex>>.

Mesmo que funcione às vezes, o nome da função `alert_bird` está ruim:
seu código exige um objeto que suporte o método `.quack()`, que não existe em `Bird`.

Nesse exemplo bobo, as funções têm uma linha apenas.
Mas na vida real elas poderiam ser mais longas,
e poderiam passar o argumento `birdie` para outras funções,
e a origem daquele argumento poderia estar a muitas chamadas de função de distância,
tornando difícil localizar a causa do erro durante a execução.
O checador de tipos impede que muitos erros como esse aconteçam durante a execução de um programa.


[NOTE]
====
O valor das dicas de tipo é questionável nos pequenos exemplos que cabem em um livro.
Os benefícios crescem com o tamanho da base de código.
É por essa razão que empresas com milhões de linhas de código em
Python—como Dropbox, Google e Facebook—investiram em equipes e ferramentas para
promover a adoção global de dicas de tipo internamente,
e hoje tem partes significativas e crescentes de sua base de código com
anotações de tipo sendo verificadas em suas _pipelines_ de integração contínua.
====

Nessa seção exploramos as relações de tipos e operações no duck typing e na tipagem nominal,
começando com a função simples `double()`, que deixamos sem dicas de tipo.
Agora vamos dar uma olhada nos tipos mais importantes ao anotar funções.

Veremos um bom modo de adicionar dicas de tipo a `double()` quando examinarmos <<protocols_in_fn_sec>>.
Mas antes disso, há tipos mais importantes para conhecer.((("", startref="FTHsupport08")))((("", startref="THsup08")))


=== Tipos próprios para anotações

Quase((("functions, type hints in", "types usable in annotations",
id="FTHusable08")))((("type hints (type annotations)", "types usable in", id="THTusable08")))
todos os tipos em Python podem ser usados em dicas de tipo, mas há restrições e recomendações.
Além disso, o((("typing module"))) módulo `typing` introduziu anotações especiais com uma semântica às vezes surpreendente.

Essa seção trata dos principais tipos que você pode usar em anotações:

* `typing.Any`
* Tipos e classes simples
* `typing.Optional` e `typing.Union`
* Coleções genéricas, incluindo tuplas e mapeamentos
* Classes base abstratas
* Iteradores genéricos
* Genéricos parametrizados e `TypeVar`
* `typing.Protocols`, crucial para _duck typing estático_
* `typing.Callable`
* `typing.NoReturn`, um bom modo de encerrar essa lista.

Vamos falar de um de cada vez, começando por um tipo que é estranho,
aparentemente inútil, mas de uma importância fundamental.

==== O tipo Any

A((("Any type", id="anytype08")))((("dynamic type", id="dynamic08")))((("gradual type system", "Any type",
id="GTSany08"))) pedra fundamental de qualquer sistema de tipagem gradual é o tipo `Any`,
também conhecido como o _tipo dinâmico_.
Quando um checador de tipos vê uma função sem tipo como esta:

[source, python]
----
def double(x):
    return x * 2
----

ele supõe isto:
[source, python]
----
def double(x: Any) -> Any:
    return x * 2
----

Isso significa que o argumento `x` e o valor de retorno podem ser de qualquer tipo,
inclusive de tipos diferentes.
Assume-se que `Any` pode suportar qualquer operação possível.

Compare `Any` com `object`. Considere essa assinatura:
[source, python]
----
def double(x: object) -> object:
----

Essa função também aceita argumentos de todos os tipos, porque todos os tipos são _subtipo-de_ `object`.

Entretanto, um checador de tipos vai rejeitar essa função:
[source, python]
----
def double(x: object) -> object:
    return x * 2
----
O problema é que `object` não suporta a operação `+__mul__+`. Veja o que diz o Mypy:

[source]
----
…/birds/ $ mypy double_object.py
double_object.py:2: error: Unsupported operand types for * ("object" and "int")
Found 1 error in 1 file (checked 1 source file)
----

Tipos mais gerais têm interfaces mais restritas, isto é, eles suportam menos operações.
A classe `object` implementa menos operações que `abc.Sequence`,
que implementa menos operações que `abc.MutableSequence`,
que por sua vez implementa menos operações que `list`.

Mas `Any` é um tipo mágico que reside tanto no topo quanto na base da hierarquia de tipos.
Ele é simultaneamente o tipo mais geral—então um argumento `n: Any` aceita valores de
qualquer tipo—e o tipo mais especializado, suportando assim todas as operações possíveis.
Pelo menos é assim que o checador de tipos entende `Any`.

Claro, nenhum tipo consegue suportar qualquer operação possível,
então usar `Any` impede o checador de tipos de cumprir sua missão:
detectar operações que podem falhar antes que
seu programa levante uma exceção durante a execução.((("", startref="GTSany08")))

[[consistent_with_sec]]
===== Subtipo-de ou consistente-com

Sistemas((("gradual type system", "subtype-of versus consistent-with relationships",
id="GTSsub08")))((("subtype-of relationships")))
tradicionais de tipagem nominal orientados a objetos se baseiam na relação _subtipo-de_.
Dada uma classe `T1` e uma subclasse `T2`, então `T2` é _subtipo-de_ `T1`.

Observe este código:

[source, python]
----
class T1:
    ...

class T2(T1):
    ...

def f1(p: T1) -> None:
    ...

o2 = T2()

f1(o2)  # OK
----

A chamada `f1(o2)` é uma aplicação do Princípio de Substituição de Liskov
(_Liskov Substitution Principle_, LSP).

Barbara Liskovfootnote:[Professora do MIT, designer de linguagens de programação e homenageada com o Turing Award em 2008.
Wikipedia: «Barbara Liskov» [.small]#&#91;fpy.li/49&#93;#.]
definiu _é subtipo-de_ em termos das operações suportadas.
Se um objeto do tipo `T2` substitui um objeto do tipo `T1` e
o programa continua se comportando de forma correta, então `T2` é _subtipo-de_ `T1`.

Seguindo com o código visto acima, essa parte mostra uma violação do LSP:


[source, python]
----
def f2(p: T2) -> None:
    ...

o1 = T1()

f2(o1)  # type error
----

Do ponto de vista das operações suportadas, faz todo sentido:
como uma subclasse, `T2` herda e precisa suportar todas as operações suportadas por `T1`.
Então uma instância de `T2` pode ser usada em qualquer lugar onde se espera uma instância de `T1`.
Mas o contrário não é necessariamente verdadeiro:
`T2` pode implementar métodos adicionais, então uma instância de `T1`
não pode ser usada onde se espera uma instância de `T2`.
Este((("behavioral subtyping"))) foco nas operações suportadas se reflete no nome
«_behavioral subtyping_ (subtipagem comportamental)» [.small]#&#91;fpy.li/8-15&#93;#,
também usado para se referir ao LSP.

Em((("consistent-with relationships"))) um sistema de tipagem gradual há outra relação,
_consistente-com_ (_consistent-with_),
que se aplica sempre que _subtipo-de_ puder ser aplicado, com regras especiais para o tipo `Any`.

<<<
As regras para _consistente-com_ são:

. Dados `T1` e um subtipo `T2`, então `T2` é _consistente-com_ `T1` (substituição de Liskov).
. Todo tipo é _consistente-com_ `Any`: você pode passar objetos de qualquer tipo em um argumento declarado como `Any`.
. `Any` é _consistente-com_ todos os tipos: você sempre pode passar um objeto de tipo `Any` onde um argumento de outro tipo for esperado.

Considerando as definições anteriores dos objetos `o1` e `o2`,
aqui estão alguns exemplos de código válido, ilustrando as regras #2 e #3:

[source, python]
----
def f3(p: Any) -> None:
    ...

o0 = object()
o1 = T1()
o2 = T2()

f3(o0)  #
f3(o1)  #  tudo certo: regra #2
f3(o2)  #

def f4():  # tipo implícito de retorno: `Any`
    ...

o4 = f4()  # tipo inferido: `Any`

f1(o4)  #
f2(o4)  #  tudo certo: regra #3
f3(o4)  #
----

Todo sistema de tipagem gradual precisa de um tipo coringa como `Any`

[TIP]
====
O verbo "inferir" é um sinônimo bonito para "adivinhar", quando usado no contexto da análise de tipos.
Verificadores de tipo modernos, em Python e outras linguagens,
não precisam de anotações de tipo em todo lugar porque conseguem inferir o tipo de muitas expressões.
Por exemplo, se eu escrever `x = len(s) * 10`,
o verificador não precisa de uma declaração local explícita para saber que `x` é um `int`,
desde que a ferramenta consiga acessar dicas de tipo para `len` em algum lugar.
====

Agora podemos explorar o restante dos tipos usados em anotações.
((("", startref="GTSsub08")))((("", startref="dynamic08")))((("", startref="anytype08")))

==== Tipos simples e classes

Tipos simples((("gradual type system", "simple types and classes")))
como `int`, `float`, `str`, e `bytes` podem ser usados diretamente em dicas de tipo.
Classes concretas da biblioteca padrão,
de pacotes externos ou definidas pelo usuário (ex. `FrenchDeck`, `Vector2d`, e `Duck`)
também podem ser usadas em dicas de tipo.

Classes base abstratas também são úteis aqui.
Voltaremos a elas quando formos estudar os tipos coleção, na <<type_hint_abc_sec>>.

Para classes, _consistente-com_ é definido como _subtipo-de_:
uma subclasse é _consistente-com_ todas as suas superclasses.

Entretanto, "a praticidade se sobrepõe à pureza", então há uma exceção importante, discutida em seguida.

[[int_complex_tip]]
.int é consistente-com complex
[TIP]
====
Não há nenhuma relação nominal de subtipo entre os tipos nativos `int`, `float` e `complex`: eles são subclasses diretas de `object`.
Mas a PEP 484 «decretou» [.small]#&#91;fpy.li/cardxvi&#93;#
que `int` é _consistente-com_ `float`, e `float` é _consistente-com_ `complex`.
Na prática, faz sentido:
`int` implementa todas as operações que `float` implementa,
e `int` implementa operações adicionais para os operadores binários `&`, `|`, `<<`, etc.
O resultado final é o seguinte: `int` é _consistente-com_ `complex`.
Para `i = 3`, `i.real` é `3` e `i.imag` é `0`.
====


==== Os tipos Optional e Union

Vimos((("gradual type system", "Optional and Union types")))((("Union type")))((("Optional type")))
o tipo especial `Optional` em <<dealing_with_none_sec>>.
Ele resolve o problema de ter `None` como default, como no exemplo daquela seção:

[source, python]
----
from typing import Optional

def show_count(count: int, singular: str, plural: Optional[str] = None) -> str:
----

A sintaxe `Optional[str]` é na verdade um atalho para `Union[str, None]`,
que significa que o tipo de `plural` pode ser `str` ou `None`.

.Uma sintaxe melhor para Optional e Union em Python 3.10

[TIP]
====
Desde o Python 3.10 é possível escrever `str | bytes` em vez de `Union[str, bytes]`.
É menos digitação, e não precisamos importar `Optional` ou `Union` de `typing`.
Compare a sintaxe antiga com a nova para a dica de tipo do parâmetro `plural` em `show_count`:

[source, python]
----
plural: Optional[str] = None    # até Python 3.9
plural: str | None = None       # a partir do Python 3.10
----

O operador `|` também funciona com `isinstance` e `issubclass` para declarar o segundo argumento: `isinstance(x, int | str)`.
Para saber mais, veja «_PEP 604—Complementary syntax for Union&#91;&#93;_» [.small]#&#91;fpy.li/pep604&#93;#.
====

A assinatura da função nativa `ord` é um exemplo simples de `Union` - ela aceita `str` or `bytes`,
e retorna um `int`:footnote:[Para ser mais preciso, `ord` só aceita `str` ou `bytes` com `len(s) == 1`.
Mas no momento o sistema de tipagem não consegue expressar essa restrição.]

[source, python]
----
def ord(c: Union[str, bytes]) -> int: ...
----

Veja a seguir um função que aceita uma `str`, mas pode retornar `str` ou `float`:

<<<
[source, python]
----
from typing import Union

def parse_token(token: str) -> Union[str, float]:
    try:
        return float(token)
    except ValueError:
        return token
----

Se possível, evite criar funções com `Union` no tipo de retorno,
pois isso exige um esforço extra do usuário:
para saber o que fazer com o valor recebido da função pode ser necessário verificar o tipo daquele valor durante a execução.
Mas a `parse_token` no código acima é um caso de uso razoável no contexto de interpretador de expressões simples.

[TIP]
====
Na <<dual_mode_api_sec>>, vimos funções que aceitam tanto `str` quanto `bytes` como argumento,
mas retornam uma `str` se o argumento for `str` ou `bytes`, se o argumento for `bytes`.
Nesses casos, o tipo de retorno é determinado pelo tipo da entrada,
então `Union` não é uma solução precisa.
Para anotar tais funções corretamente,
precisamos usar uma variável de tipo—apresentada em
<<param_generics_typevar_sec>>—ou sobrecarga (_overloading_), que veremos na «Seção 15.2» [.small]#&#91;vol.2, fpy.li/5f&#93;#.
====

`Union[]` exige pelo menos dois tipos.
Tipos `Union` aninhados têm o mesmo efeito que uma `Union` "achatada" .
Então esta dica de tipo:

[source, python]
----
Union[A, B, Union[C, D, E]]
----

Tem o mesmo efeito desta:

[source, python]
----
Union[A, B, C, D, E]
----

`Union` é mais útil com tipos que não sejam consistentes entre si.
Por exemplo: `Union[int, float]` é redundante, pois `int` é _consistente-com_ `float`.
Se você usar apenas `float` para anotar um parâmetro, ele vai também aceitar valores `int`.


[[simple_collections_type_sec]]
==== Coleções genéricas

A maioria((("gradual type system", "generic collections", id="GTSgeneric08")))((("generic collections", "type annotations and", id="generic08"))) das coleções em Python são heterogêneas.

Por exemplo, você pode inserir qualquer combinação de tipos diferentes em uma `list`.
Entretanto, na prática isso não é muito útil: se você colocar objetos em uma coleção,
você certamente vai querer executar alguma operação com eles mais tarde, e normalmente isso significa que eles precisam compartilhar pelo menos um método comum.footnote:[Em ABC - a linguagem que mais influenciou o design inicial de Python - cada lista estava restrita a aceitar valores de um único tipo: o tipo do primeiro item que você colocasse ali.]

Tipos genéricos podem ser declarados com parâmetros de tipo, para especificar o tipo de item com o qual eles conseguem trabalhar.
Por exemplo, uma `list` pode ser parametrizada para restringir o tipo de elemento ali contido, como se pode ver no <<tokenize_ex>>.

[[tokenize_ex]]
.`tokenize` com dicas de tipo para Python ≥ 3.9
====
[source, python]
----
def tokenize(text: str) -> list[str]:
    return text.upper().split()
----
====

Em Python ≥ 3.9, isso significa que `tokenize` retorna uma `list` onde todos os elementos são do tipo `str`.

As anotações `stuff: list` e `stuff: list[Any]` significam a mesma coisa: `stuff` é uma lista de objetos de qualquer tipo.

[TIP]
====
Se estiver usando Python 3.8 ou anterior, o conceito é o mesmo,
mas você precisa de mais código para funcionar, como explicado em
<<legacy_deprecated_typing_box>>.
====

A «PEP 585—Type Hinting Generics In Standard Collections» [.small]#&#91;fpy.li/8-16&#93;#
lista as coleções da biblioteca padrão que aceitam dicas de tipo genéricas.
A lista a seguir mostra apenas as coleções que usam a forma mais simples de dica de tipo genérica, `container[item]`:

[source]
----
list        collections.deque        abc.Sequence   abc.MutableSequence
set         abc.Container            abc.Set        abc.MutableSet
frozenset   abc.Collection
----

Os tipos `tuple` e mapping aceitam dicas de tipo mais complexas, como veremos em suas respectivas seções.

No Python 3.10, não há uma boa maneira de anotar `array.array`, levando em consideração o argumento `typecode` do construtor, que determina se o array contém inteiros ou floats.
Um problema ainda mais complicado é verificar a faixa dos inteiros, para prevenir `OverflowError` durante a execução, ao se adicionar novos elementos.
Por exemplo, um `array` com `typecode=B` só pode receber valores `int` de 0 a 255.
Até Python 3.11, o sistema de tipagem estática de Python não consegue lidar com esse desafio.((("", startref="GTSgeneric08")))((("", startref="generic08")))

[[legacy_deprecated_typing_box]]
.Suporte a tipos de coleção descontinuados
****

(Você pode pular esse box se usa apenas Python 3.9 ou posterior.)

Em((("deprecated collection types")))((("gradual type system", "legacy support and deprecated collection types"))) Python 3.7 e 3.8, você precisa importar um `+__future__+` para fazer a notação `[]` funcionar com as coleções nativas, tal como `list`, como ilustrado no <<tokenize_3_7_ex>>.

[[tokenize_3_7_ex]]
.`tokenize` com dicas de tipo para Python ≥ 3.7
====
[source, python]
----
from __future__ import annotations

def tokenize(text: str) -> list[str]:
    return text.upper().split()
----
====

Este `+__future__+` não funciona com Python 3.6 ou anterior.
O <<tokenize_3_5_ex>> mostra como anotar `tokenize` para funcionar com Python ≥ 3.5.

[[tokenize_3_5_ex]]
.`tokenize` com dicas de tipo para Python ≥ 3.5
====
[source, python]
----
from typing import List

def tokenize(text: str) -> List[str]:
    return text.upper().split()
----
====

Para fornecer um suporte inicial a dicas de tipo genéricas,
os autores da PEP 484 criaram dúzias de tipos genéricos no módulo `typing`.
A <<generic_collections_tbl>> mostra alguns deles.
Para a lista completa, consulte a documentação do módulo «_typing_» [.small]#&#91;fpy.li/4a&#93;#.

[[generic_collections_tbl]]
.Alguns tipos de coleção e seus equivalentes nas dicas de tipo
[options="header"]
|===========================================================
|Collection                        |Type hint equivalent
|`list`                            |`typing.List`
|`set`                             |`typing.Set`
|`frozenset`                       |`typing.FrozenSet`
|`collections.deque`               |`typing.Deque`
|`collections.abc.MutableSequence` |`typing.MutableSequence`
|`collections.abc.Sequence`        |`typing.Sequence`
|`collections.abc.Set`             |`typing.AbstractSet`
|`collections.abc.MutableSet`      |`typing.MutableSet`
|===========================================================

A «PEP 585—Type Hinting Generics In Standard Collections» [.small]#&#91;fpy.li/pep585&#93;# deu início a um processo de vários anos para melhorar a usabilidade das dicas de tipo genéricas.
Podemos resumir esse processo em quatro etapas:


. Introduzir `from {dunder}future{dunder} import annotations` no Python 3.7 para permitir o uso das classes da biblioteca padrão como genéricos com a notação `list[str]`.

. Tornar aquele comportamento o default a partir de Python 3.9: `list[str]` agora funciona sem que  `future` precise ser importado.

. Descontinuar (_deprecate_) todos os tipos genéricos do módulo `typing`.footnote:[Uma de minhas contribuições para a documentação do módulo `typing` foi acrescentar dúzias de avisos de descontinuação, enquanto eu reorganizava as entradas abaixo de «Conteúdo do Módulo» [.small]#&#91;fpy.li/4b&#93;# em subseções, sob a supervisão de Guido van [.keep-together]#Rossum#.] Avisos de descontinuação não serão emitidos pelo interpretador Python, porque os checadores de tipos devem sinalizar os tipos descontinuados quando o programa sendo verificado tiver como alvo Python 3.9 ou posterior.

. Remover aqueles tipos genéricos redundantes na primeira versão de Python lançada cinco anos após Python 3.9. No ritmo atual, esse deverá ser Python 3.14, também conhecido como Python Pi.
****

Agora veremos como anotar tuplas genéricas.

[[tuple_type_sec]]
==== Tipos tuple

Há ((("gradual type system", "tuple types", id="GTStupple08")))((("tuples", "type hints (type annotations)", id="Thint08")))
três maneiras de anotar o tipo `tuple`, dependendo de como você usa a tupla:

* Tupla como registro
* Tupla como registro com campos nomeados
* Tupla como sequência imutável

===== Tuplas como registros

Se você está usando uma `tuple` como um registro, use o tipo `tuple` nativo e declare os tipos dos campos dentro dos `[]`.
Por exemplo, para anotar uma tupla com nome da cidade, população e país:
`('Shanghai', 24.28, 'China')`, use `tuple[str, float, str]` na dica de tipo.

Observe uma função que recebe um par de coordenadas geográficas e retorna um «Geohash» [.small]#&#91;fpy.li/8-18&#93;#, usada assim:

[source, python]
----
>>> shanghai = 31.2304, 121.4737
>>> geohash(shanghai)
'wtw3sjq6q'
----

O <<geohash_ex_1>> mostra a função `geohash`, que usa o pacote `geolib` do PyPI.

[[geohash_ex_1]]
._coordinates.py_ com a função `geohash`
====
[source, python]
----
include::../code/08-def-type-hints/coordinates/coordinates.py[tags=GEOHASH]
----
====
<1> Comentário para que o Mypy ignore que `geolib` não tem dicas de tipo.
<2> O parâmetro `lat_lon`, anotado como uma `tuple` com dois campos `float`.

<<<
===== Tuplas como registros com campos nomeados

Para a anotar uma tupla com muitos campos,
ou tipos específicos de tupla que seu código usa com frequência,
recomendo fortemente usar `typing.NamedTuple`, como visto no <<ch_dataclass>>.
O <<geohash_ex_2>> mostra uma variante de <<geohash_ex_1>> com `NamedTuple`.

[[geohash_ex_2]]
._coordinates_named.py_ com `NamedTuple`, `Coordinates` e a função `geohash`
====
[source, python]
----
include::../code/08-def-type-hints/coordinates/coordinates_named.py[tags=GEOHASH]
----
====

Como explicado na <<data_class_overview_sec>>,
`typing.NamedTuple` é uma factory de subclasses de `tuple`,
então `Coordinate` é _consistente-com_ `tuple[float, float]`,
mas o inverso não é verdadeiro—afinal, `Coordinate` tem métodos extras adicionados por `NamedTuple`,
como `._asdict()`, e também poderia ter métodos definidos pelo usuário.

Na prática, isso significa que é seguro (do ponto de vista do tipo do argumento)
passar uma instância de `Coordinate` para a função `display`, definida assim:

[source, python]
----
include::../code/08-def-type-hints/coordinates/coordinates_named.py[tags=DISPLAY]
----

<<<
===== Tuplas como sequências imutáveis

Para anotar tuplas de tamanho desconhecido, usadas como listas imutáveis, você precisa especificar um único tipo,
seguido de uma vírgula e `\...`
(isto é o símbolo de reticências de Python, formado por três caracteres `.`, não o caractere Unicode `U+2026`—`HORIZONTAL ELLIPSIS`).

Por exemplo, `tuple[int, \...]` é uma tupla com itens `int`.

As reticências indicam que qualquer número de elementos >= 1 é aceitável.
Não há como especificar campos de tipos diferentes para tuplas de tamanho arbitrário.

As anotações `stuff: tuple[Any, \...]` e `stuff: tuple` são equivalentes:
`stuff` é uma tupla de tamanho desconhecido contendo objetos de qualquer tipo.

Aqui temos uma função `columnize`, que transforma uma sequência em uma tabela de colunas e células,
na forma de uma lista de tuplas de tamanho desconhecido.
É útil para mostrar os itens em colunas, assim:

[source, python]
----
>>> animals = 'drake fawn heron ibex koala lynx tahr xerus yak zapus'.split()
>>> table = columnize(animals)
>>> table
[('drake', 'koala', 'yak'), ('fawn', 'lynx', 'zapus'), ('heron', 'tahr'),
 ('ibex', 'xerus')]
>>> for row in table:
...     print(''.join(f'{word:10}' for word in row))
...
drake     koala     yak
fawn      lynx      zapus
heron     tahr
ibex      xerus
----

O <<columnize_ex>> mostra a implementação de `columnize`.
Observe o tipo((("", startref="Thint08")))((("", startref="GTStupple08"))) do retorno:

[source, python]
----
list[tuple[str, ...]]
----

[[columnize_ex]]
._columnize.py_ retorna uma lista de tuplas de strings
====
[source, python]
----
include::../code/08-def-type-hints/columnize.py[tags=COLUMNIZE]
----
====


[[mapping_type_sec]]
==== Mapeamentos genéricos

Tipos de mapeamento genéricos((("gradual type system", "generic mappings")))((("generic mapping types")))
são anotados como `MappingType[KeyType, ValueType]`.
O tipo nativo `dict` e os tipos de mapeamento em `collections` e `collections.abc`
aceitam essa notação em Python ≥ 3.9.
Para versões mais antigas, use `typing.Dict` e outros tipos de mapeamento do módulo `typing`,
como discutimos em <<legacy_deprecated_typing_box>>.

O <<charindex_ex>> mostra um uso na prática de uma função que retorna um
«índice invertido» [.small]#&#91;fpy.li/8-19&#93;# para permitir a busca de caracteres Unicode pelo nome—uma variação do <<ex_cfpy>>
mais adequada para código server-side, que estudaremos no «Capítulo 21» [.small]#&#91;vol.3, fpy.li/57&#93;#.
Dado o início e o final dos códigos de caractere Unicode,
`name_index` retorna um `dict[str, set[str]]`,
que é um índice invertido mapeando cada palavra para um conjunto de caracteres que tem aquela palavra em seus nomes.
Por exemplo, após indexar os caracteres ASCII de 32 a 64,
aqui estão os conjuntos de caracteres mapeados para as palavras `'SIGN'` e `'DIGIT'`,
e a forma de encontrar o caractere chamado `'DIGIT EIGHT'`:

[source, python]
----
>>> index = name_index(32, 65)
>>> index['SIGN']
{'$', '>', '=', '+', '<', '%', '#'}
>>> index['DIGIT']
{'8', '5', '6', '2', '3', '0', '1', '4', '7', '9'}
>>> index['DIGIT'] & index['EIGHT']
{'8'}
----

O <<charindex_ex>> mostra o código-fonte de _charindex.py_ com a função `name_index`.
Além de uma dica de tipo `dict[]`, o exemplo tem as novidades `:=` e `yield`.

[[charindex_ex]]
._charindex.py_
====
[source, python]
----
include::../code/08-def-type-hints/charindex.py[tags=CHARINDEX]
----
====
<1> `tokenize` é uma função geradora, assunto do «Capítulo 17» [.small]#&#91;vol.3, fpy.li/4s&#93;#.
<2> A variável local `index` está anotada.
Sem a dica, o Mypy diz:
`Need type annotation for 'index' (hint: "index: dict[<type>, <type>] = ...")`.
<3> O operador morsa (_walrus operator_) `:=` atribui a `name` o resultado da chamada a `unicodedata.name()`, e o valor da expressão inteira é aquele resultado. Quando o resultado é `''`, ele é falso, e o `index` não é atualizado.footnote:[Uso `:=` quando faz sentido em alguns exemplos, mas não trato desse operador no livro.
Veja «_PEP 572—Assignment Expressions_ (Expressões de atribuição)» [.small]#&#91;fpy.li/pep572&#93;# para entender os detalhes dos operadores de atribuição.]

[NOTE]
====
Ao armazenar um registro JSON em um `dict`, todas as chaves são `str`,
mas o valor associado a cada chave pode ter um tipo diferente.
Isso é tratado na «Seção 15.3» [.small]#&#91;vol.2, fpy.li/29&#93;#, sobre `typing.TypedDict`.
====


[[type_hint_abc_sec]]
==== ABC: classes bases abstratas

[quote, lei de Postel, ou o Princípio da Robustez]

____
Seja conservador no que envia, mas liberal no que aceita.
____

A <<generic_collections_tbl>> apresenta((("gradual type system", "abstract base classes", id="GTSabstract08")))((("ABCs (abstract base classes)", "type hints (type annotations)", id="ABChint08"))) várias classes abstratas de `collections.abc`.
Idealmente, uma função deveria aceitar argumentos desses tipos abstratos--ou seus equivalentes de `typing` antes de Python 3.9--e não tipos concretos.
Isso dá mais flexibilidade a quem chama a função.


Considere essa assinatura de função:

[source, python]
----
from collections.abc import Mapping

def name2hex(name: str, color_map: Mapping[str, int]) -> str:
----

Usar  `abc.Mapping` permite ao usuário da função fornecer uma instância de `dict`, `defaultdict`, `ChainMap`, uma subclasse de `UserDict` subclass, ou qualquer outra classe que seja um _subtipo-de_ `Mapping`.

Por outro lado, veja essa assinatura:

[source, python]
----
def name2hex(name: str, color_map: dict[str, int]) -> str:
----

Agora `color_map` precisa ser um `dict` ou um de seus subtipos, tal como `defaultdict` ou `OrderedDict`.
Especificamente, uma subclasse de `collections.UserDict` não passaria pela verificação de tipo para `color_map`,
apesar de ser a maneira recomendada de criar mapeamentos definidos pelo usuário, como vimos na <<sublcassing_userdict_sec>>.
O Mypy rejeitaria um `UserDict` ou uma instância de classe derivada dele, porque `UserDict` não é uma subclasse de `dict`; elas são irmãs.
Ambas são subclasses `abc.MutableMapping`.footnote:[Na verdade,
`dict` é uma subclasse virtual de `abc.MutableMapping`.
O conceito de subclasse virtual será explicado no «Capítulo 13» [.small]#&#91;vol.2, fpy.li/4q&#93;#.
Por hora, basta saber que `issubclass(dict, abc.MutableMapping)` é `True`,
apesar de `dict` ser implementada em C, herdando apenas de `object`.]

Assim, em geral é melhor usar `abc.Mapping` ou `abc.MutableMapping` em dicas de tipos de parâmetros, em vez de `dict` (ou `typing.Dict` em código antigo).
Se a função `name2hex` não precisar modificar o `color_map` recebido, a dica de tipo mais precisa para `color_map` é `abc.Mapping`.
Desse jeito, quem chama não precisa fornecer um objeto que implemente métodos como `setdefault`, `pop`, e `update`, que fazem parte da interface de `MutableMapping`, mas não de `Mapping`.
Isso reflete a segunda parte da lei de Postel:
 "[seja]  liberal no que aceita."

A lei de Postel também nos diz para sermos conservadores no que enviamos.
O valor de retorno de uma função é sempre um objeto concreto,
então a dica de tipo do valor de saída deve ser um tipo concreto,
como no exemplo em  <<simple_collections_type_sec>>—que usa `list[str]`:

[source, python]
----
def tokenize(text: str) -> list[str]:
    return text.upper().split()
----

No verbete de «`typing.List`» [.small]#&#91;fpy.li/4c&#93;#, a documentação do Python 3.10 diz
(NT: tradução abaixo não oficial)

[quote]
____
Versão genérica de `list`. Útil para anotar tipos de retorno.
Para anotar argumentos é preferível usar um tipo de coleção abstrata, como `Sequence` ou `Iterable`.
____

Comentários similares aparecem nos verbetes de «`typing.Dict`» [.small]#&#91;fpy.li/8-21&#93;#
e «`typing.Set`» [.small]#&#91;fpy.li/8-22&#93;#.

Lembre-se que a maioria das ABCs de `collections.abc` e outras classes concretas de `collections`,
bem como as coleções nativas, suportam notação de dica de tipo genérica como `collections.deque[str]` desde o Python 3.9.
As coleções correspondentes em `typing` só servem para suportar código escrito em Python 3.8 ou anterior.
A lista completa de classes que se tornaram genéricas aparece em na seção «_Implementation_» [.small]#&#91;fpy.li/8-16&#93;# da
«_PEP 585—Type Hinting Generics In Standard Collections_» [.small]#&#91;fpy.li/pep585&#93;#  .

Para encerrar nossa discussão de ABCs em dicas de tipo, precisamos falar sobre as ABCs `numbers`.

<<<
[[numeric_tower_warning]]
===== A queda da torre numérica

O((("numbers package")))((("numeric tower"))) pacote «`numbers`» [.small]#&#91;fpy.li/4d&#93;# define a assim chamada _torre numérica_ (_numeric tower_) descrita na «_PEP 3141—A Type Hierarchy for Numbers_» [.small]#&#91;fpy.li/pep3141&#93;#  .
A torre é uma hierarquia linear de ABCs, com `Number` no topo:

* `Number`
* `Complex`
* `Real`
* `Rational`
* `Integral`

Esses ABCs funcionam perfeitamente para checagem de tipo durante a execução, mas eles não são suportados para checagem de tipo estática.
A seção «_Numeric Tower_» [.small]#&#91;fpy.li_/cardxvi&#93;# da PEP 484 rejeita as ABCs `numbers` e
manda tratar os tipos nativos `complex`, `float`, e `int` como casos especiais,
como explicado em <<int_complex_tip>>.
Voltaremps a essa questão na «Seção 13.6.8» [.small]#&#91;vol.2, fpy.li/5g&#93;#, que é dedicada a comparar protocolos e ABCs

Na prática, se você quiser anotar argumentos numéricos para checagem estática de tipos, há algumas opções:

. Usar um dos tipos concretos (`int`, `float`, ou `complex`) como recomendado pela PEP 488.
. Declarar um tipo union como `Union[float, Decimal, Fraction]`.
. Se você quiser evitar a codificação explícita de tipos concretos, usar protocolos numéricos como `SupportsFloat`, tratados na «Seção 13.6.2» [.small]#&#91;vol.2, fpy.li/5h&#93;#.

A <<protocols_in_fn_sec>> abaixo é um pré-requisito para entender protocolos numéricos.

Antes disso, vamos examinar uma ABC muito útil em dicas de tipo: `Iterable`.((("", startref="GTSabstract08")))((("", startref="ABChint08")))

<<<
==== Iterable

A((("gradual type system", "Iterable", id="GTSiterable08")))((("Iterable interface",
id="iterable08")))((("interfaces", "Iterable interface")))
documentação de «`typing.List`» [.small]#&#91;fpy.li/4c&#93;#
que citei acima recomenda `Sequence` e `Iterable` para dicas de tipo de parâmetros de função.

Esse é um exemplo de argumento `Iterable`, na função `math.fsum` da biblioteca padrão:

[source, python]
----
def fsum(__seq: Iterable[float]) -> float:
----

.Arquivos Stub e o Projeto Typeshed
[TIP]
====
Até((("Typeshed project"))) Python 3.10, a biblioteca padrão não tem anotações,
mas Mypy, PyCharm, etc. conseguem encontrar as dicas de tipo necessárias no projeto
«_Typeshed_» [.small]#&#91;fpy.li/8-26&#93;#,
na forma de _arquivos stub_: arquivos de código-fonte especiais,
com a extensão _.pyi_, que contém assinaturas anotadas de métodos e funções,
sem a implementação—semelhante a arquivos _.h_ na linguagem C.

A assinatura para `math.fsum` está em «`/stdlib/2and3/math.pyi`» [.small]#&#91;fpy.li/8-27&#93;#.
Os sublinhados iniciais em `__seq` são uma convenção estabelecida na PEP 484 para parâmetros apenas posicionais,
como explicado em <<arbitrary_arguments_sec>>.
====

O <<replacer_ex>> é outro exemplo do uso de um parâmetro `Iterable`, que produz itens que são `tuple[str, str]`. A função é usada assim:

[source, python]
----
>>> l33t = [('a', '4'), ('e', '3'), ('i', '1'), ('o', '0')]
>>> text = 'mad skilled noob powned leet'
>>> from replacer import zip_replace
>>> zip_replace(text, l33t)
'm4d sk1ll3d n00b p0wn3d l33t'
----

O <<replacer_ex>> mostra a implementação.

[[replacer_ex]]
._replacer.py_
====
[source, python]
----
include::../code/08-def-type-hints/replacer.py[tags=ZIP_REPLACE]
----
====
<1> `FromTo` é um _apelido de tipo_: atribui `tuple[str, str]` a `FromTo`,
para tornar a assinatura de `zip_replace` mais concisa.
<2> `changes` precisa ser um `Iterable[FromTo]`;
é o mesmo que escrever `Iterable[tuple[str, str]]`, mas é mais curto e mais fácil de ler.

.O TypeAlias Explícito em Python 3.10
[TIP]
====
«_PEP 613—Explicit Type Aliases_» [.small]#&#91;fpy.li/pep613&#93;# introduziu um tipo especial,
`TypeAlias`, tornando a criação de apelidos tipos mais explícita para os checadores de tipos.
A partir do Python 3.10, a sintaxe preferencial para apelidos de tipo é:

[source, python]
----
from typing import TypeAlias

FromTo: TypeAlias = tuple[str, str]
----
====


===== abc.Iterable versus abc.Sequence

Tanto((("abc.Iterable")))((("abc.Sequence"))) `math.fsum` quanto `replacer.zip_replace`
tem que percorrer todos os argumentos do `Iterable` para produzir um resultado.
Dado um iterável sem fim tal como o gerador `itertools.cycle` como entrada,
essas funções consumiriam toda a memória e derrubariam o processo Python.
Apesar desse perigo potencial, é muito comum no Python moderno se oferecer funções que aceitam um `Iterable` como argumento,
mesmo se elas tem que processar a estrutura inteira para obter um resultado.
Isso dá a quem chama a função a opção de fornecer um gerador como dado de entrada,
em vez de uma sequência pré-construída,
podendo economizar bastante memória se o número de itens de entrada for grande.

Por outro lado, a função `columnize` no <<columnize_ex>> requer uma `Sequence`,
não um `Iterable`, pois ela precisa obter o `len()` do argumento para calcular previamente o número de linhas.

Assim como `Sequence`, o melhor uso de `Iterable` é como tipo de argumento.
Ele é muito vago como um tipo de saída.
Uma função deve ser mais precisa sobre o tipo concreto que retorna.

O  tipo `Iterator`, usado como tipo do retorno no <<charindex_ex>>,
está intimamente relacionado a `Iterable`.
Voltaremos a ele em «Capítulo 17» [.small]#&#91;vol.3, fpy.li/4s&#93;#, que trata de geradores e
iteradores clássicos.((("", startref="GTSiterable08")))((("", startref="iterable08")))

[[param_generics_typevar_sec]]
==== Genéricos parametrizados e TypeVar

Um((("gradual type system", "parameterized generics and TypeVar",
id="GTSparam08")))((("generic collections", "parameterized generics and TypeVar",
id="GCtypvar08")))((("TypeVar", id="typevar08")))
genérico parametrizado é um tipo genérico, escrito na forma `list[T]`,
onde `T` é uma variável de tipo que será vinculada a um tipo específico a cada uso.
Isso permite que o tipo de um argumento determine o tipo do resultado da função.

O <<generic_sample_ex>> define `sample`, uma função que recebe dois argumentos:
uma `Sequence` de elementos de tipo `T` e um `int`.
Ela retorna uma `list` de elementos do mesmo tipo `T`, escolhidos aleatoriamente do primeiro argumento.

O <<generic_sample_ex>> mostra a implementação.

[[generic_sample_ex]]
._sample.py_
====
[source, python]
----
include::../code/08-def-type-hints/sample.py[tags=SAMPLE]
----
====

<<<
Aqui estão dois exemplos que ilustram porque é usei uma variável de tipo em `sample`:

* Se chamada com uma tupla de tipo `tuple[int, \...]` (que é _consistente-com_ `Sequence[int]`)
então o tipo parametrizado é `int`, e o tipo de retorno é `list[int]`.
* Se chamada com uma `str` (que é _consistente-com_ `Sequence[str]`)
então o tipo parametrizado é `str`, e o tipo do retorno é `list[str]`.


[role="man-height-2in"]
.Por que TypeVar é necessário?
[NOTE]
====
Os autores da PEP 484 queriam introduzir dicas de tipo ao acrescentar o módulo `typing`,
sem mudar nada mais na linguagem.
Com uma metaprogramação esperta, eles poderiam fazer o operador `[]` funcionar para classes como `Sequence[T]`.
Mas o nome da variável `T` dentro dos colchetes precisa ser definido em algum lugar—do contrário
o interpretador Python necessitaria de mudanças mais profundas,
para suportar a notação de tipos genéricos como um caso especial de `[]`.
Por isso o construtor `typing.TypeVar` é necessário:
para introduzir o nome da variável no _namespace_ (_espaço de nomes_) corrente.
Linguagens como Java, C# e TypeScript não exigem que o nome da variável seja declarado previamente,
então elas não precisam de algo equivalente à pseudo-classe `TypeVar` de Python.
====


Outro exemplo é a função `statistics.mode` da biblioteca padrão, que retorna o ponto de dado mais comum de uma série.

Aqui é um exemplo de uso da «documentação» [.small]#&#91;fpy.li/4e&#93;#:

[source, python]
----
>>> mode([1, 1, 2, 3, 3, 3, 3, 4])
3
----

Sem o uso de `TypeVar`, `mode` poderia ter uma assinatura como a apresentada no <<mode_float_ex>>.

[[mode_float_ex]]
._mode_float.py_: `mode` que opera com `float` e seus subtipos footnote:[A implementação aqui é mais simples que aquela do módulo «`statistics`» [.small]#&#91;fpy.li/8-29&#93;# na biblioteca padrão de Python]
====
[source, python]
----
include::../code/08-def-type-hints/mode/mode_float.py[tags=MODE_FLOAT]
----
====

Muitos dos usos de `mode` envolvem valores `int` ou `float`, mas Python tem outros tipos numéricos, e é desejável que o tipo de retorno siga o tipo dos elementos do `Iterable` recebido.
Podemos melhorar aquela assinatura usando `TypeVar`. Vamos começar com uma assinatura parametrizada simples, mas errada.

[source, python]
----
from collections.abc import Iterable
from typing import TypeVar

T = TypeVar('T')

def mode(data: Iterable[T]) -> T:
----

Quando aparece pela primeira vez na assinatura, o parâmetro de tipo `T` aceita qualquer tipo.
Da segunda vez que aparece, só aceitará o mesmo tipo vinculado da primeira vez.

Assim, qualquer iterável é _consistente-com_ `Iterable[T]`,
incluindo iterável de tipos _unhashable_ que `collections.Counter` não consegue tratar.
Precisamos restringir os tipos possíveis de se atribuir a `T`.
Veremos diferentes modos de fazer isso nas duas seções seguintes.

<<<
[[typevar_constraints_sec]]
===== TypeVar restrita

O `TypeVar` aceita argumentos posicionais adicionais para restringir o tipo parametrizado.
Podemos melhorar a assinatura de `mode` para aceitar um número específico de tipos, assim:

[source, python]
----
from collections.abc import Iterable
from decimal import Decimal
from fractions import Fraction
from typing import TypeVar

NumberT = TypeVar('NumberT', float, Decimal, Fraction)

def mode(data: Iterable[NumberT]) -> NumberT:
----

Está melhor que antes, e era a assinatura de `mode` em
«`statistics.pyi`» [.small]#&#91;fpy.li/8-30&#93;#, o arquivo stub em `typeshed` em 25 de maio de 2020.

Entretanto, a documentação em «`statistics.mode`» [.small]#&#91;fpy.li/8-28&#93;# inclui esse exemplo:

[source, python]
----
>>> mode(["red", "blue", "blue", "red", "green", "red", "red"])
'red'
----

Na pressa, poderíamos apenas adicionar `str` à definição de `NumberT`:

[source, python]
----
NumberT = TypeVar('NumberT', float, Decimal, Fraction, str)
----

Com certeza funciona, mas `NumberT` estaria muito mal batizado se aceitasse `str`.
Mais importante, não podemos ficar listando tipos para sempre, cada vez que percebermos que `mode` pode lidar com outro deles.
Podemos fazer melhor com um outro recurso de `TypeVar`, como veremos a seguir.

[[bounded_typevar_sec]]
===== TypeVar delimitada

Examinando o corpo de `mode` no <<mode_float_ex>>,
vemos que a classe `Counter` é usada para ordenação.
`Counter` é baseada em `dict`, então o tipo do elemento do iterável `data` precisa ser _hashable_.

A princípio, essa assinatura pode parecer que funciona:

[source, python]
----
from collections.abc import Iterable, Hashable

def mode(data: Iterable[Hashable]) -> Hashable:
----

Agora o problema é que o tipo do item retornado é `Hashable`:
um ABC que implementa apenas o método `+__hash__+`.
Então o checador de tipos não vai permitir que façamos nada com o valor retornado,
exceto chamar seu método `hash()`. Não é muito útil.

A solução está em outro parâmetro opcional de `TypeVar`:
o parâmetro representado pela palavra-chave `bound`.
Ele estabelece um limite superior para os tipos aceitos.
No <<mode_hashable_ex>>, temos `bound=Hashable`.
Isso significa que o tipo do parâmetro pode ser `Hashable` ou
qualquer _subtipo-de_ `Hashable`.footnote:[Forneci essa solução para `typeshed`,
e em 26 de maio de 2020 `mode` aparecia anotado assim em
«`statistics.pyi`» [.small]#&#91;fpy.li/8-32&#93;#.]

[[mode_hashable_ex]]
._mode_hashable.py_: comoo o <<mode_float_ex>>, com assinatura mais flexível
====
[source, python]
----
include::../code/08-def-type-hints/mode/mode_hashable.py[tags=MODE_HASHABLE_T]
----
====

Em resumo:

* Uma variável de tipo restrita será vinculada um dos tipos nomeados na declaração do `TypeVar`.
* Uma variável de tipo delimitada será vinculada ao tipo inferido da expressão,
desde que o tipo inferido seja _consistente-com_ o limite declarado pelo argumento `bound=` do TypeVar.

[NOTE]
====
É uma pena que a palavra-chave do argumento para declarar uma `TypeVar` delimitada tenha sido chamado `bound=`,
(_limite_, como substantivo) pois o verbo "bound" (passado do verbo "to bind", _ligar ou vincular_)
é muinto usado em inglês para descrever a associação de um do valor a uma variável.
Seria menos confuso se a palavra-chave do argumento fosse `boundary=`,
um substantivo mais comum e explícito que também significa _limite_.
====

O construtor de `typing.TypeVar` tem outros parâmetros opcionais (`covariant` e `contravariant`)
que envolvem conceitos avançados que vamos deixar para a «Seção 15.7» [.small]#&#91;vol.2, fpy.li/5j&#93;#.
Vamos concluir essa introdução a `TypeVar` com `AnyStr`.

===== A variável de tipo pré-definida AnyStr

O((("AnyStr"))) módulo `typing` inclui uma `TypeVar` pré-definida chamada `AnyStr`,
restrita aos tipos `bytes` e `str`.
Ele é definido assim:

[source, python]
----
AnyStr = TypeVar('AnyStr', bytes, str)
----

O tipo `AnyStr` é usado em funções da biblioteca padrão que aceitam tanto `bytes` quanto `str`,
e retornam valores do tipo recebido.

Agora veremos `typing.Protocol`, um novo recurso de Python 3.8,
que permite um uso mais pythônico de dicas de
tipo.((("", startref="GTSparam08")))((("", startref="GCtypvar08")))((("", startref="typevar08")))

[[protocols_in_fn_sec]]
==== Protocolos estáticos

[NOTE]
====
Em ((("gradual type system", "static protocols",
id="GTSstatic08")))((("static protocols", "type hints (type annotations)", id="stprot08")))
programação orientada a objetos, o conceito de um "protocolo" como uma interface informal é tão antigo quanto Smalltalk,
e tem sido parte essencial de Python desde o início.
Entretanto, no contexto de dicas de tipo, um protocolo é uma subclasse de `typing.Protocol`,
definindo uma interface que um checador de tipos pode analisar.
Os dois tipos de protocolo são tratados no «Capítulo 13» [.small]#&#91;vol.2, fpy.li/4q&#93;#.
Aqui apresento apenas uma rápida introdução no contexto de anotações de função.
====

O((("Protocol type", id="proto08"))) tipo `Protocol`, como descrito em
«_PEP 544—Protocols: Structural subtyping (static duck typing)_» [.small]#&#91;fpy.li/pep544&#93;#,
é similar às interfaces em Go: um tipo protocolo é definido especificando um ou mais métodos,
e o checador de tipos analisa se aqueles métodos estão implementados onde um tipo daquele protocolo é exigido.

Em Python, uma definição de protocolo é escrita como uma subclasse de `typing.Protocol`.
Entretanto, classes que _implementam_ um protocolo não precisam herdar,
registrar ou declarar qualquer relação com a classe que _define_ o protocolo.
É função do checador de tipos encontrar os tipos de protocolos disponíveis e verificar sua utilização.

Abaixo temos um problema que pode ser resolvido com a ajuda de `Protocol` e `TypeVar`.
Suponha que você quisesse criar uma função `top(it, n)`, que retorna os `n` maiores elementos do iterável `it`:

[source, python]
----
include::../code/08-def-type-hints/comparable/top.py[tags=TOP_DOCTEST]
----

A função genérica parametrizada `top` poderia ser implementada como no <<top_undefined_t_ex>>.

[[top_undefined_t_ex]]
.a função `top` function com um parâmetro de tipo `T` indefinido
====
[source, python]
----
def top(series: Iterable[T], length: int) -> list[T]:
    ordered = sorted(series, reverse=True)
    return ordered[:length]
----
====

O problema é, como restringir `T`?
Ele não pode ser `Any` ou `object`, pois `series` precisa funcionar com `sorted`.
A `sorted` nativa na verdade aceita `Iterable[Any]`,
mas só porque o parâmetro opcional `key` recebe uma função que calcula uma chave de ordenação arbitrária para cada elemento.
O que acontece se você passar para `sorted` uma lista de objetos simples,
mas não fornecer um argumento `key`?
Vamos tentar:

[source, python]
----
>>> l = [object() for _ in range(4)]
>>> l
[<object object at 0x10fc2fca0>, <object object at 0x10fc2fbb0>,
<object object at 0x10fc2fbc0>, <object object at 0x10fc2fbd0>]
>>> sorted(l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'object' and 'object'
----

A mensagem de erro mostra que `sorted` usa o operador `<` nos elementos do iterável.
É só isso? Vamos tentar outro experimento rápido:footnote:[É
otimo poder abrir um console iterativo e contar com o duck typing para explorar recursos da linguagem,
como acabei de fazer.
Sinto muita falta deste tipo de exploração quando uso linguagem que não tem esse recurso.]

[source, python]
----
>>> class Spam:
...     def __init__(self, n): self.n = n
...     def __lt__(self, other): return self.n < other.n
...     def __repr__(self): return f'Spam({self.n})'
...
>>> l = [Spam(n) for n in range(5, 0, -1)]
>>> l
[Spam(5), Spam(4), Spam(3), Spam(2), Spam(1)]
>>> sorted(l)
[Spam(1), Spam(2), Spam(3), Spam(4), Spam(5)]
----

Isso confirma a suspeita: eu consigo passar uma lista de `Spam` para `sort`,
porque `Spam` implementa `+__lt__+`, o método especial do operador `<`.

Então o parâmetro de tipo `T` no <<top_undefined_t_ex>> deveria ser limitado a tipos que implementam `+__lt__+`.
No <<mode_hashable_ex>>, precisávamos de um parâmetro de tipo que implementava `+__hash__+`, para poder usar `typing.Hashable` como limite superior do parâmetro de tipo.
Mas agora não há um tipo adequado em `typing` ou `abc` para usarmos, então precisamos criar um.

O <<comparable_protocol_ex>> mostra o novo tipo `SupportsLessThan`, um `Protocol`.

[[comparable_protocol_ex]]
._comparable.py_: a definição de um tipo `Protocol`, `SupportsLessThan`
====
[source, python]
----
include::../code/08-def-type-hints/comparable/comparable.py[]
----
====
<1> Um protocolo é uma subclasse de `typing.Protocol`.
<2> O corpo do protocolo tem uma ou mais definições de método, com `\...` no lugar da implementação.

Um tipo `T` é _consistente-com_ um protocolo `P` se `T` implementa todos os métodos definidos em `P`,
com assinaturas de tipo correspondentes.

Dado `SupportsLessThan`, agora podemos definir essa versão funcional de `top` no <<top_protocol_ex>>.

[[top_protocol_ex]]
._top.py_: definição da função `top` usando uma `TypeVar` com `bound=SupportsLessThan`
====
[source, python]
----
include::../code/08-def-type-hints/comparable/top.py[tags=TOP]
----
====

Vamos testar `top`. O <<top_protocol_test>> mostra parte de uma bateria de testes para uso com o `pytest`.
Ele tenta chamar `top` primeiro com um gerador de expressões que produz `tuple[int, str]`, e depois com uma lista de `object`.
Com a lista de `object`, esperamos receber uma exceção de `TypeError`.

<<<
[[top_protocol_test]]
._top_test.py_: visão parcial da bateria de testes para `top`
====
[source, python]
----
include::../code/08-def-type-hints/comparable/top_test.py[tags=TOP_IMPORT]

# muitas linhas omitidas

include::../code/08-def-type-hints/comparable/top_test.py[tags=TOP_TEST]
----
====
<1> A constante `typing.TYPE_CHECKING` é sempre `False` durante a execução do programa, mas os checadores de tipos fingem que ela é `True` quando estão fazendo a verificação.
<2> Declaração de tipo explícita para a variável `series`, para tornar mais fácil a leitura da saída do
Mypy.footnote:[Sem essa dica de tipo, o Mypy inferiria o tipo de `series` como
`Generator[Tuple[builtins.int, builtins.str*&#x5d;, None, None&#x5d;`,
que é prolixo mas _consistente-com_ `Iterator[tuple[int, str&#x5d;&#x5d;`,
como veremos na «Seção 17.12» [.small]#&#91;vol.3, fpy.li/5k&#93;.&#93;#]
<3> Esse `if` evita que as três linhas seguintes sejam executadas durante o teste.
<4> `reveal_type()` não pode ser chamada durante a execução, porque não é uma função regular, mas sim um mecanismo de depuração do Mypy - por isso não há `import` para ela.
Mypy vai produzir uma mensagem de depuração para cada chamada à pseudo-função `reveal_type()`, mostrando o tipo inferido do argumento.
<5> Essa linha será marcada pelo Mypy como um erro.

Os testes anteriores são bem sucedidos - mas eles funcionariam de qualquer forma, com ou sem dicas de tipo em _top.py_.
Mais precisamente, se eu verificar aquele arquivo de teste com o Mypy, verei que o `TypeVar` está funcionando como o esperado.
Veja a saída do comando `mypy` no <<top_protocol_mypy_output>>.

[WARNING]
====
Desde o Mypy 0.910 (julho de 2021), em alguns casos a saída de `reveal_type` não mostra precisamente os tipos que declarei, mas mostra tipos compatíveis.
Por exemplo, eu não usei `typing.Iterator` e sim `abc.Iterator`.
Pode ignorar esse detalhe.
O relatório do Mypy ainda é útil.
Vou fingir que esse problema do Mypy já foi corrigido quando for discutir os resultados.
====

[[top_protocol_mypy_output]]
.Saída do _mypy top_test.py_ (linhas quebradas para facilitar a leitura)
====
[source]
----
…/comparable/ $ mypy top_test.py
top_test.py:32: note:
    Revealed type is "typing.Iterator[Tuple[builtins.int, builtins.str]]" <1>
top_test.py:33: note:
    Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]"
top_test.py:34: note:
    Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]" <2>
top_test.py:41: note:
    Revealed type is "builtins.list[builtins.object*]" <3>
top_test.py:43: error:
    Value of type variable "LT" of "top" cannot be "object"  <4>
Found 1 error in 1 file (checked 1 source file)
----
====
<1> Em `test_top_tuples`, `reveal_type(series)` mostra que ele é um `Iterator[tuple[int, str]]` que declarei explicitamente.
<2> `reveal_type(result)` confirma que o tipo produzido pela chamada a `top` é o que eu queria:
dado o tipo de `series`, o `result` é `list[tuple[int, str]]`.
<3>  Em `test_top_objects_error`, `reveal_type(series)` mostra que ele é uma `list[object*]`.
Mypy põe um `*` após qualquer tipo que tenha sido inferido: não anotei o tipo de `series` nesse teste.
<4> Mypy marca o erro que esse teste produz intencionalmente:
o tipo dos elementos do `Iterable` `series` não pode ser `object` (ele precisa ser do tipo `SupportsLessThan`).

A principal vantagem de um tipo protocolo sobre as ABCs é que
uma classe não precisa de nenhuma declaração especial para ser _consistente-com_ um tipo protocolo.
Isso permite que um protocolo seja criado aproveitando tipos pré-existentes,
ou tipos implementados em bases de código que não estão sob nosso controle.
Eu não tenho que derivar ou registrar `str`, `tuple`, `float`, `set`, etc.
com `SupportsLessThan` para usá-los onde um parâmetro `SupportsLessThan` é esperado.
Eles só precisam implementar `+__lt__+`.
E o checador de tipos ainda será capaz de realizar seu trabalho,
porque `SupportsLessThan` está explicitamente declarado como um
`Protocol`—diferente dos protocolos implícitos comuns no duck typing,
que são invisíveis para o checador de tipos.

A classe especial `Protocol` foi introduzida na
«_PEP 544—Protocols: Structural subtyping (static duck typing)_» [.small]#&#91;fpy.li/pep544&#93;#.
O <<top_protocol_ex>> demonstra((("static duck typing"))) porque esse recurso
é conhecido como _duck typing estático_ (_static duck typing_):
a solução para anotar o parâmetro `series` de `top` era dizer "O tipo nominal de `series` não importa,
desde que ele implemente o método `+__lt__+`."
Em Python, o duck typing sempre permitiu dizer isso de forma implícita,
deixando os checadores de tipos estáticos sem ação.
Um checador de tipos não consegue ler o código-fonte em C do CPython,
ou executar experimentos no console para descobrir que `sorted` só requer que seus elementos suportem `<`.

Agora podemos tornar o duck typing explícito para os verificadores estáticos de tipo.
Por isso faz sentido dizer que `typing.Protocol` nos oferece _duck typing estático_.footnote:[Eu não sei quem inventou a expressão _duck tying estático_, mas ela se tornou mais popular com a linguagem Go, que tem uma semântica de interfaces que é mais parecida com os protocolos de Python que com as interfaces nominais de Java.]

Há mais para falar sobre `typing.Protocol`.
Vamos voltar a ele na Parte IV, onde o «Capítulo 13» [.small]#&#91;vol.2, fpy.li/4q&#93;# compara as abordagens da tipagem estrutural,
do duck typing e das ABCs—outro modo de formalizar protocolos.
Além disso, a «Seção 15.2» [.small]#&#91;vol.2, fpy.li/5f&#93;# explica como
declarar assinaturas de funções com sobrecarga (_overload_) com `@typing.overload`,
e inclui um exemplo bastante extenso usando `typing.Protocol` e uma `TypeVar` delimitada.

[NOTE]
====
O `typing.Protocol` torna possível anotar a função `double` na <<types_defined_by_ops_sec>> sem perder funcionalidade.
O segredo é definir uma classe de protocolo com o método `+__mul__+`.
Convido você a fazer isso como um exercício.
A solução está na «Seção 13.6.1» [.small]#&#91;vol.2, fpy.li/5m&#93;#.((("", startref="GTSstatic08")))((("", startref="stprot08")))((("", startref="proto08")))
====


==== Callable

Para((("gradual type system", "Callable type", id="GTScallable08")))((("Callable type", id="callable08"))) anotar parâmetros de callback ou objetos _callable_ retornados por funções de ordem superior, o módulo `collections.abc` oferece o tipo `Callable`, disponível no módulo `typing` para quem ainda não estiver usando Python 3.9.
Um tipo `Callable` é parametrizado assim:

[source, python]
----
Callable[[ParamType1, ParamType2], ReturnType]
----

A lista de parâmetros `[ParamType1, ParamType2]` pode ter zero ou mais tipos.

Aqui está um exemplo no contexto de uma função `repl`,
parte do interpretador iterativo simples que veremos na
«Seção 18.3» [.small]#&#91;vol.3, fpy.li/22&#93;#:footnote:[REPL
significa Read-Eval-Print-Loop (_Ler-Calcular-Imprimir-Recomeçar_), o comportamento básico de interpretadores iterativos.]

[source, python]
----
def repl(input_fn: Callable[[Any], str] = input]) -> None:
----

Durante a utilização normal, a função `repl` usa a função `input` nativa de Python para ler expressões inseridas pelo usuário.
Entretanto, para testagem automatizada ou para integração com outras fontes de entrada,
`repl` aceita um parâmetro `input_fn` opcional:
um `Callable` com o mesmo parâmetro e tipo de retorno de `input`.

A função embutida `input` tem a seguinte assinatura no typeshed:

[source, python]
----
def input(__prompt: Any = ...) -> str: ...
----

<<<
A assinatura de `input` é _consistente-com_ esta dica de tipo `Callable`:

[source, python]
----
Callable[[Any], str]
----
Não existe sintaxe para a declarar o tipo de argumentos opcionais ou nomeados.
A
«documentação» [.small]#&#91;fpy.li/4f&#93;#
de `typing.Callable` diz "tais funções são raramente usadas como tipo de callback."
Se você precisar de uma dica de tipo para acompanhar uma função com assinatura mais flexível,
substitua a lista de parâmetros por `\...` - assim:

[source, python]
----
Callable[..., ReturnType]
----

A interação de parâmetros de tipo genéricos com uma hierarquia de tipos introduz um novo conceito: variância.

[[callable_variance_sec]]
===== Variância em tipos callable

Imagine um sistema de controle de temperatura com uma função `update` simples, como mostrada no <<callable_variance_ex>>.((("variance", "in callable types")))((("covariance", see="variance")))((("contravariance", see="variance")))
A função `update` chama a função `probe` para obter a temperatura atual, e chama `display` para mostrar a temperatura para o usuário.
`probe` e `display` são ambas passadas como argumentos para `update`, por motivos didáticos.
O objetivo do exemplo é contrastar duas anotações de `Callable`: uma como tipo de retorno e outra como tipo de parâmetro.

[[callable_variance_ex]]
.Ilustrando a variância.
====
[source, python]
----
include::../code/08-def-type-hints/callable/variance.py[]
----
====
<1> `update` recebe duas funções callable como argumentos.
<2> `probe` precisa ser uma callable que não recebe nenhum argumento e retorna um `float`
<3> `display` recebe um argumento `float` e retorna `None`.
<4> `probe_ok` é _consistente-com_ `Callable[[], float]` porque retornar um `int` não quebra código que espera um `float`.
<5> `display_wrong` não é _consistente-com_ `Callable[[float], None]` porque não há garantia que uma função esperando um `int` consiga lidar com um `float`; por exemplo, a função `hex` de Python aceita  um `int` mas rejeita um `float`.
<6> O Mypy marca essa linha porque `display_wrong` é incompatível com a dica de tipo no parâmetro `display` em `update`.
<7> `display_ok` é _consistente_com_ `Callable[[float], None]` porque uma função que aceita um `complex` também consegue lidar com um argumento `float`.
<8> Mypy está satisfeito com essa linha.

Resumindo,
não há problema em fornecer uma função de callback que retorne um `int` quando o código espera uma função callback que retorne um `float`, porque um valor `int` sempre pode ser usado onde um `float` é esperado.

Formalmente, dizemos que `Callable[[], int]` é _subtipo-de_ `Callable[[], float]`,
assim como `int` é _subtipo-de_ `float`.
Isso significa que `Callable` é _covariante_ no que diz respeito aos tipos de retorno,
porque a relação _subtipo-de_ dos tipos `int` e `float` aponta na mesma direção
que os tipos `Callable` que os usam como tipos de retorno.

Por outro lado, é um erro de tipo fornecer uma função callback que recebe um argumento `int`
quando é se espera um callback que possa processar um `float`.

Formalmente, `Callable[[int], None]` não é _subtipo-de_ `Callable[[float], None]`.
Apesar de `int` ser _subtipo-de_ `float`, no `Callable` parametrizado a relação é invertida:
`Callable[[float], None]` é _subtipo-de_ `Callable[[int], None]`.
Assim dizemos que aquele `Callable` é _contravariante_ a respeito dos tipos de parâmetros declarados.

A «Seção 15.7» [.small]#&#91;vol.2, fpy.li/5j&#93;# explica variância em mais detalhes e
com exemplos de tipos invariantes, covariantes e contravariantes.

[TIP]
====
Por hora, saiba que a maioria dos tipos genéricos parametrizados são _invariantes_, portanto mais simples.
Por exemplo, se eu declaro `scores: list[float]`,
isso me diz exatamente o que posso atribuir a `scores`.
Não posso atribuir objetos declarados como `list[int]` ou `list[complex]`:

* Um objeto `list[int]` não é aceitável porque ele não pode conter valores `float` que meu código pode precisar colocar em `scores`.
* Um objeto `list[complex]` não é aceitável porque meu código pode precisar ordenar `scores` para encontrar a mediana, mas `complex` não fornece o método `+__lt__+`, então `list[complex]` não é ordenável.
====

Agora chegamos ou último tipo especial que examinaremos nesse capítulo.

[[noreturn_sec]]
==== NoReturn

Esse((("gradual type system", "NoReturn type")))((("NoReturn type"))) é um tipo especial usado apenas para anotar o tipo de retorno de funções
que nunca devolvem um resultado.
Normalmente, elas existem para gerar exceções.
Há dúzias dessas funções na biblioteca padrão.
Por exemplo, `sys.exit()` levanta `SystemExit` para encerrar o processo Python.
A assinatura no `typeshed` é:

[source, python]
----
def exit(__status: object = ...) -> NoReturn: ...
----

O parâmetro `+__status__+` é apenas posicional, e tem um valor default.
Arquivos stub não contém valores default, em vez disso eles usam `\...`.
O tipo de `__status` é `object`, o que significa que pode também ser `None`,
assim seria redundante escrever `Optional[object]`.

Na «Seção 24.5» [.small]#&#91;vol.3, fpy.li/5n&#93;#,
o método `__flag_unknown_attrs` tem o tipo de retorno `NoReturn`,
pois sua função é produzir uma mensagem de erro detalhada e amigável,
e então levantar `AttributeError`.

A última seção desse capítulo épico é sobre parâmetros posicionais e variádicos
((("", startref="THTusable08")))((("", startref="FTHusable08")))

[[arbitrary_arguments_sec]]
=== Anotando parâmetros apenas posicionais e variádicos

Lembra((("functions, type hints in", "annotating positional only and variadic parameters")))((("type hints (type annotations)", "annotating positional only and variadic parameters")))((("parameters", "annotating positional only and variadic parameters")))((("variadic parameters"))) da função `tag` do <<tagger_ex>>?
Da última vez que vimos sua assinatura foi em <<positional_only_params>>:

[source, python]
----
def tag(name, /, *content, class_=None, **attrs):
----

Aqui está `tag`, completamente anotada e ocupando várias linhas - uma convenção comum para assinaturas longas,
com quebras de linha como o formatador «_blue_» [.small]#&#91;fpy.li/8-10&#93;# faria:

[source, python]
----
from typing import Optional

def tag(
    name: str,
    /,
    *content: str,
    class_: Optional[str] = None,
    **attrs: str,
) -> str:
----

Observe a dica de tipo `*content: str`, para parâmetros posicionais arbitrários;
Isso significa que todos aqueles argumentos precisa ser do tipo `str`.
O tipo da variável local `content` no corpo da função será `tuple[str, \...]`.

A dica de tipo para argumentos nomeados arbitrários é `+**attrs: str+` neste exemplo,
portanto o tipo de `attrs` dentro da função será `dict[str, str]`.
Para uma dica de tipo como `+**attrs: float+`,
o tipo de `attrs` na função seria `dict[str, float]`.

Se for necessário que o parâmetro `attrs` aceite valores de tipos diferentes,
é preciso usar uma `Union[]` ou `Any`: `+**attrs: Any+`.

A notação `/` para parâmetros puramente posicionais só está disponível com Python ≥ 3.8.
Em Python 3.7 ou anterior, isso é um erro de sintaxe.
A «convenção da PEP 484» [.small]#&#91;fpy.li/8-36&#93;# é prefixar o nome cada parâmetro puramente posicional com dois sublinhados.
Veja a assinatura de `tag` novamente, agora em duas linhas, usando a convenção da PEP 484:

[source, python]
----
from typing import Optional

def tag(__name: str, *content: str, class_: Optional[str] = None,
        **attrs: str) -> str:
----

O Mypy entende sabe verificar as duas formas de declarar parâmetros puramente posicionais.

Para encerrar esse capítulo, vamos considerar brevemente os limites das dicas de tipo e do sistema de tipagem estática que elas suportam.

=== Tipos imperfeitos e testes poderosos

Os mantenedores((("functions, type hints in",
"flawed typing and strong testing")))((("type hints (type annotations)",
"flawed typing and strong testing")))((("flawed typing")))((("strong testing")))
de grandes bases de código corporativas relatam que muitos bugs são encontrados por checadores de tipos estáticos,
e o custo de resolvê-los é menor que se os mesmos bugs fossem descobertos apenas após o código estar rodando em produção.
Entretanto, é essencial observar que testes automatizados já eram uma boa prática
largamente adotada muito antes da tipagem estática ser introduzida nas empresas que conheço usando Python.

Mesmo em contextos onde ela é mais benéfica, a tipagem estática não pode ser elevada a árbitro final da correção.
Não é difícil encontrar:

Falsos Positivos:: Ferramentas indicam erros de tipagem em código correto.
Falsos Negativos:: Ferramentas não indicam erros em código incorreto.

Além disso, se formos forçados a checar o tipo de tudo, perdemos um pouco do poder expressivo de Python:

* Alguns recursos convenientes não podem ser checados de forma estática: por exemplo,
o desempacotamento de argumentos como em `config(**settings)`.
* Recursos avançados como propriedades, descritores, metaclasses e metaprogramação em geral,
têm suporte muito deficiente ou estão além da compreensão dos verificadores de tipo.
* Verificadores de tipo ficam obsoletos e/ou incompatíveis após o lançamento de novas versões de Python,
rejeitando ou mesmo quebrando ao analisar código com novos recursos da linguagem—às vezes com atrasos de um ano ou até mais.

Restrições comuns de dados não podem ser expressas no sistema de tipo, mesmo restrições simples.
Por exemplo, dicas de tipo são incapazes de assegurar que "quantidade deve ser um inteiro > 0"
ou que "label deve ser uma string com 6 a 12 letras em ASCII."
Em geral, dicas de tipo não são úteis para localizar erros na lógica do negócio subjacente ao código.

Dadas essas ressalvas, dicas de tipo não podem ser o pilar central da qualidade do software,
e torná-las obrigatórias sem qualquer exceção só amplificaria os aspectos negativos.

Considere o checador de tipos estático como uma das ferramentas
na infraestrutura moderna de integração de código, ao lado de testadores,
analisadores de código (_linters_), etc.
O objetivo de uma estrutura de produção de integração de código é reduzir as falhas no software,
e testes automatizados podem encontrar muitos bugs que estão fora do alcance de dicas de tipo.
Qualquer código que possa ser escrito em Python pode ser testado em Python—com ou sem dicas de tipo.

<<<
[NOTE]
====
O título e a conclusão dessa seção foram inspirados pelo artigo
«_Strong Typing vs. Strong Testing_» [.small]#&#91;fpy.li/8-37&#93;# de Bruce Eckel,
também publicado na antologia «_The Best Software Writing I_» [.small]#&#91;fpy.li/8-38&#93;#,
editada por Joel Spolsky (Apress).
Bruce é um fã de Python, e autor de livros sobre C&#x2b;&#x2b;, Java, Scala, e Kotlin.
Naquele texto, ele conta como foi um defensor da tipagem estática até aprender Python, e conclui:
"Se um programa em Python tem testes unitários adequados, ele poderá ser tão robusto quanto um programa em C&#x2b;&#x2b;, Java, ou C# com testes unitários adequados (mas será mais rápido escrever os testes em Python).
====

// [role="pagebreak-before less_space"]
Isso encerra nossa cobertura das dicas de tipo em Python por agora.
Elas serão também o ponto central do «Capítulo 15» [.small]#&#91;vol.2, fpy.li/54&#93;#, que trata de classes genéricas,
variância, assinaturas sobrecarregadas, coerção de tipos (_type casting_), entre outros tópicos.
Até lá, as dicas de tipo aparecerão em várias funções ao longo do livro.


=== Resumo do capítulo

Começamos((("functions, type hints in", "overview of")))((("type hints (type annotations)", "overview of")))
com uma pequena introdução ao conceito de tipagem gradual, depois adotamos uma abordagem prática.
É difícil ver como a tipagem gradual funciona sem uma ferramenta que efetivamente leia as dicas de tipo,
então desenvolvemos uma função anotada guiados pelos relatórios de erro do Mypy.

Voltando à ideia de tipagem gradual, vimos como ela é um híbrido do duck typing tradicional
de Python e da tipagem nominal mais familiar aos usuários de Java, {cpp} e de outras linguagens de tipagem estática.

A maior parte do capítulo foi dedicada a apresentar os principais grupos de tipos usados em anotações.
Muitos dos tipos discutidos estão relacionados a tipos conhecidos de objetos de Python,
como coleções, tuplas e callables - estendidos para suportar notação genérica do tipo `Sequence[float]`.
Muitos daqueles tipos são substitutos temporários,
implementados no módulo `typing` antes que os tipos padrão fossem modificados para suportar genéricos, no Python 3.9.
Alguns desses tipos são entidades especiais:
`Any`, `Optional`, `Union`, e `NoReturn` não tem qualquer relação com objetos reais na memória,
existem apenas no domínio abstrato do sistema de tipos.

Estudamos genéricos parametrizados e variáveis de tipo,
que trazem mais flexibilidade para as dicas de tipo sem sacrificar a segurança da tipagem.

Genéricos parametrizáveis se tornam ainda mais expressivos com o uso de `Protocol`.
Como só surgiu no Python 3.8, `Protocol` ainda não é muito usado—mas é muito importante.
`Protocol` permite duck typing estático:
é a ponte fundamental entre o núcleo de Python, construído sobre duck typing,
e a tipagem nominal que permite a checadores de tipos estáticos encontrarem bugs.

Ao discutir alguns desses tipos, usamos o Mypy para localizar erros de checagem de tipo e tipos inferidos,
com a ajuda da função mágica `reveal_type()` do Mypy.

A seção final mostrou como anotar parâmetros exclusivamente posicionais e parâmetros variádicos.

Dicas de tipo são um tópico complexo e em constante evolução.
Felizmente são um recurso opcional.
Vamos manter Python acessível para a maior base de usuários possível,
e parar de defender que todo código Python precisa ter dicas de tipo—como
já vi em sermões públicos de evangelistas da tipagem estática.

O BDFL emérito liderou o movimento de inclusão de dicas de tipo em Python,
então é muito justo que esse capítulo comece e termine com suas palavras.

[quote, Guido van Rossum]
____
Não gostaria de uma versão de Python na qual eu fosse moralmente obrigado a adicionar dicas de tipo o tempo todo.
Realmente acho que dicas de tipo tem seu lugar, mas há muitas ocasiões em que elas não valem a pena,
e é maravilhoso que possamos escolher usá-las.footnote:[Do vídeo no Youtube,
«_Type Hints by Guido van Rossum (March 2015)_» [.small]#&#91;fpy.li/8-39&#93;#  .
A citação começa em «13'40"» [.small]#&#91;fpy.li/8-40&#93;#.
Editei levemente a transcrição para manter a clareza.]
____


=== Para saber mais

Bernát Gábor escreveu((("functions, type hints in", "further reading on")))((("type hints (type annotations)", "further reading on"))) em
«_The state of type hints in Python_» [.small]#&#91;fpy.li/8-41&#93;#:

[quote]
____
Dicas de tipo devem ser usadas sempre que vale a pena escrever testes unitários.
____

Sou um grande fã de testes, mas também escrevo muito código exploratório.
Quando estou explorando, testes e dicas de tipo não ajudam.
São um entrave.

Esse post do Gábor é uma das melhores introduções a dicas de tipo em Python que já encontrei, junto com o texto de Geir Arne Hjelle,
«_Python Type Checking (Guide)_» [.small]#&#91;fpy.li/8-42&#93;#.
«_Hypermodern Python Chapter 4: Typing_» [.small]#&#91;fpy.li/8-43&#93;#,
de Claudio Jolowicz, é uma introdução mas curta que também fala de validação de checagem de tipo durante a execução.

Para uma abordagem mais aprofundada, a «documentação do Mypy» [.small]#&#91;fpy.li/8-44&#93;#
é a melhor fonte.
Ela é útil independente do checador de tipos que você esteja usando, pois tem páginas de tutorial e de referência sobre tipagem em Python em geral - não apenas sobre o próprio Mypy.

Lá você também encontrará uma conveniente
«página de referência (ou _cheat sheet)» [.small]#&#91;fpy.li/8-45&#93;#
e uma página muito útil sobre
«problemas comuns e suas soluções» [.small]#&#91;fpy.li/8-46&#93;#  .

A documentação do módulo «`typing`» [.small]#&#91;fpy.li/4a&#93;# é uma boa referência rápida, mas não entra em muitos detalhes.

A «_PEP 483—The Theory of Type Hints_» [.small]#&#91;fpy.li/pep483&#93;# inclui uma explicação aprofundada sobre variância, usando `Callable` para ilustrar a contravariância.
As referências definitivas são as PEP relacionadas a tipagem.
Já existem mais de 20 delas.
O público-alvo das PEPs são os _core developers_ (desenvolvedores principais da linguagem em si) e o Steering Council de Python,
então elas pressupõe muito conhecimento prévio, e certamente não são uma leitura leve.

Como já mencionado, o «Capítulo 15» [.small]#&#91;vol.2, fpy.li/54&#93;# cobre outros tópicos sobre tipagem, e a
«Seção 15.10» [.small]#&#91;vol.2, fpy.li/5p&#93;# traz referências adicionais,
incluindo uma tabela com a longa lista das PEPs
sobre tipagem aprovadas ou em discussão até o final de 2021.

«_Awesome Python Typing_» [.small]#&#91;fpy.li/8-47&#93;# é uma ótima coleção de links para ferramentas e referências.

<<<
[[type_hints_in_def_soapbox]]
.Ponto de vista
****

[role="soapbox-title"]
**Apenas Pedale**

[quote, Grant Petersen, <em>Just Ride: A Radically Practical Guide to Riding Your Bike (Apenas Pedale: Um Guia Radicalmente Prático sobre o Uso de sua Bicicleta) </em> (Workman Publishing)]
____
Esqueça((("functions, type hints in", "Soapbox discussion", id="FTHsoap08")))((("type hints (type annotations)", "Soapbox discussion", id="THsoag08"))) as desconfortáveis bicicletas ultraleves, as malhas brilhantes, os sapatos desajeitados que se prendem a pedais minúsculos, o esforço de quilômetros intermináveis.
Em vez disso, faça como você fazia quando era criança - suba na sua bicicleta e descubra o puro prazer de pedalar.
____


Se((("Soapbox sidebars", "type hints (type annotations)", id="SStypehints08"))) programar não é sua profissão principal, mas uma ferramenta útil no seu trabalho ou algo que você faz para aprender, experimentar e se divertir, você provavelmente não precisa de dicas de tipo mais que a maioria dos ciclistas precisa de sapatos com solas rígidas e presilhas metálicas.

Apenas programe.

[role="soapbox-title"]
**Duck Typing FTW**

Duck typing((("Soapbox sidebars", "duck typing")))((("duck typing")))
encaixa bem no meu cérebro, e duck typing estático é um bom compromisso,
permitindo checagem estática de tipo sem perder muito da flexibilidade que
alguns sistemas de tipagem nominal só permitem ao custo de muita complexidade—quando permitem.

Antes da PEP 544, toda essa ideia de dicas de tipo me parecia completamente não-pythônica.
Fiquei muito feliz quando vi `typing.Protocol` surgir em Python.
Ele traz equilíbrio para a Força.
****

<<<
****
[role="soapbox-title"]
**O Efeito Cognitivo da Tipagem**

Eu me preocupo com o efeito que as dicas de tipo terão sobre o estilo de programação em Python.

Concordo que usuários da maioria das APIs se beneficiam de dicas de tipo.
Mas Python me atraiu—entre outras razões—porque oferece funções tão poderosas
que substituem APIs inteiras, e podemos escrever nós mesmos funções poderosas similares.
Considere a função nativa «`max()`» [.small]#&#91;fpy.li/8-48&#93;#.
Ela é poderosa, mas é fácil de entender.
Porém, vou mostrar na «Seção 15.2.1» [.small]#&#91;vol.2, fpy.li/5q&#93;# que são necessárias
14 linhas de dicas de tipo para anotar corretamente essa função—sem contar a definição de um `typing.Protocol`
e algumas definições de `TypeVar` para sustentar aquelas dicas de tipo.

Me inquieta que a adoção de dicas de tipo em bibliotecas desencorajem programadores
de oferecer funções assim no futuro.

De acordo com o verbete em inglês na Wikipedia, «relatividade linguística» [.small]#&#91;fpy.li/8-49&#93;#—ou a hipótese Sapir–Whorf—é
um "princípio alegando que a estrutura de uma linguagem afeta a visão de mundo ou a cognição de seus falantes."

A Wikipedia continua:

* A versão _forte_ diz que a linguagem _determina_ o pensamento,
e que categorias linguísticas limitam e determinam as categorias cognitivas.
* A versão _fraca_ diz que as categorias linguísticas e seu uso
apenas _influenciam_ o pensamento e as decisões.

Linguistas em geral concordam que a versão forte é falsa,
mas há evidência empírica apoiando a versão fraca.

Não conheço estudos específicos com linguagens de programação,
mas elas tiveram grande impacto sobre a forma como eu abordo problemas.
A primeira linguagem de programação que usei profissionalmente foi o Applesoft BASIC,
na era dos computadores de 8 bits.
Recursão não era diretamente suportada pelo BASIC.
Você teria que gerenciar uma pilha na unha para implementar um algoritmo recursivo.
Então eu nunca pensava em usar algoritmos ou estruturas de dados recursivos.
Eu sabia, em algum nível conceitual, que tais coisas existiam,
mas elas não eram parte de meu arsenal de técnicas de resolução de problemas.

Décadas mais tarde, quando aprendi Elixir,
gostei de resolver problemas com recursão e usei essa técnica além da conta—até
descobrir que muitas das minhas soluções seriam mais simples se que usasse funções existentes nos módulos `Enum` e `Stream` do Elixir.
Aprendi que o código idiomático de aplicações em Elixir raramente contém chamadas recursivas
explícitas—em vez disso, usam enums e streams que implementam recursão por trás dos panos.

A relatividade linguística pode explicar a ideia recorrente (e também não provada) que aprender linguagens de programação diferentes torna alguém um programador melhor, especialmente quando as linguagens em questão suportam diferentes paradigmas de programação.
Praticar com Elixir me tornou mais propenso a aplicar padrões funcionais quando escrevo programas em Python ou Go.

Agora voltando à Terra.

O pacote _requests_ provavelmente teria uma API muito diferente se Kenneth Reitz tivesse decidido
anotar todas as suas funções (ou tivesse recebido ordens de seu chefe para fazê-lo)
Seu objetivo era escrever uma API que fosse fácil de usar, flexível e poderosa.
Ele conseguiu, dada a fantástica popularidade de _requests_: em maio de 2020,
ela estava em 4º lugar nas «_PyPI Stats_» [.small]#&#91;fpy.li/8-50&#93;#, com 2,6 milhões de downloads diários.
A 1ª era a _urllib3_, uma dependência de _requests_.

Em 2017 os mantenedores de _requests_ «decidiram» [.small]#&#91;fpy.li/8-51&#93;#
não perder seu tempo escrevendo dicas de tipo.
Um deles, Cory Benfield, escreveu um email dizendo:

[quote]
____
Acho que bibliotecas com APIs 'pythônicas' são as menos propensas a adotar esse sistema de tipagem, pois ele vai adicionar muito pouco valor a elas.
____

<<<
Naquela mensagem, Benfield incluiu esse exemplo extremo de uma tentativa de definição de tipo para o
argumento nomeado `files` em «`requests.request()`» [.small]#&#91;fpy.li/8-53&#93;#:

----
Optional[
  Union[
    Mapping[
      basestring,
      Union[
        Tuple[basestring, Optional[Union[basestring, file]]],
        Tuple[basestring, Optional[Union[basestring, file]],
              Optional[basestring]],
        Tuple[basestring, Optional[Union[basestring, file]],
              Optional[basestring], Optional[Headers]]
      ]
    ],
    Iterable[
      Tuple[
        basestring,
        Union[
          Tuple[basestring, Optional[Union[basestring, file]]],
          Tuple[basestring, Optional[Union[basestring, file]],
                Optional[basestring]],
          Tuple[basestring, Optional[Union[basestring, file]],
                Optional[basestring], Optional[Headers]]
      ]
    ]
  ]
]
----

E tudo isto ainda depende desta definição:

----
Headers = Union[
  Mapping[basestring, basestring],
  Iterable[Tuple[basestring, basestring]],
]
----

<<<
Você acha que _requests_ seria como é se os mantenedores insistissem em ter uma cobertura de dicas de tipo de 100%?
_SQLAlchemy_ é outro pacote importante que não trabalha muito bem com dicas de tipo.

O que torna essas bibliotecas fabulosas é incorporarem a natureza dinâmica de Python.

Apesar das dicas de tipo trazerem benefícios, há também um preço a ser pago.

Primeiro, há o investimento significativo de aprender como o sistema de tipos funciona.
Esse é um preço alto, cobrado uma vez.

Mas há também um custo recorrente, eterno.

Perdemos parte do poder expressivo de Python se insistimos que tudo precisa estar sob a checagem de tipos.
Recursos excelentes estão além da capacidade de compreensão dos checadores de tipos,
por exemplo o desempacotamento de argumentos: `config(**settings)`.

Se quiser ter uma chamada como `config(**settings)` verificada quanto ao tipo,
precisa explicitar cada argumento.
Isso me traz lembranças de programas em Turbo Pascal, que escrevi 35 anos atrás.

Bibliotecas que usam metaprogramação são difíceis ou impossíveis de anotar.
Claro que a metaprogramação pode ser mal usada,
mas isso também é algo que torna muitos pacotes de Python divertidos de usar.

Se dicas de tipo se tornarem obrigatórias sem exceções,
por uma decisão superior em grande empresas,
aposto que logo veremos pessoas usando geração de código
para reduzir a verbosidade em programas Python,
uma prática comum com linguagens menos dinâmicas.

Para alguns projetos e contextos, dicas de tipo simplesmente não fazem sentido.
Mesmo em contextos onde elas fazer muito sentido, não fazem sentido o tempo todo.
Qualquer política razoável sobre o uso de dicas de tipo precisa conter exceções.

Alan Kay, que recebeu o prêmio Turing e foi um dos pioneiros da programação orientada a objetos,
certa vez disse:

[quote]
____
Algumas pessoas são completamente religiosas no que diz respeito a sistemas de tipo,
e como um matemático eu adoro a ideia de sistemas de tipos,
mas ninguém até agora inventou um que tenha alcance o suficiente.footnote:[Fonte:
«_A Conversation with Alan Kay_» [.small]#&#91;fpy.li/8-54&#93;#.]
____

Obrigado, Guido, pela tipagem opcional.
Vamos usá-la como foi pensada, e não tentar anotar tudo em conformidade estrita com
um estilo de programação que se parece com Java 1.5.((("", startref="SStypehints08")))

[role="soapbox-title"]
**Generics ou specifics?**

De((("Soapbox sidebars", "generic collections")))((("generic collections",
"Soapbox discussion"))) uma perspectiva de Python,
o uso do termo "genérico" na tipagem é invertido.
Os sentidos comuns do termo "genérico" são
"aplicável integralmente a um grupo ou uma classe" ou "sem uma marca distintiva."

Considere `list` versus `list[str]`.
O primeiro tipo é genérico: a lista aceita qualquer objeto.
O segundo é específico: só aceita itens do tipo `str`.

Por outro lado, o termo faz sentido em Java.
Antes de Java 1.5, todas as coleções de Java (exceto a mágica `array`) eram "specific":
só podiam conter referências a `Object`,
então era necessário converter os itens que saíam de uma coleção antes que eles pudessem ser usados.
Com Java 1.5, as coleções ganharam parâmetros de tipo,
e se tornaram "generic."((("", startref="THsoag08")))((("", startref="FTHsoap08")))

****

// para dar um respiro antes das notas
&nbsp;
