[[ch_ifaces_prot_abc]]
== Interfaces, protocolos, e ABCs
:example-number: 0
:figure-number: 0

[quote, Gamma&#x2c; Helm&#x2c; Johnson&#x2c; Vlissides, First Principle of Object-Oriented Design]
____
Programe mirando uma interface,
não uma implementação.footnote:[Design Patterns:
Elements of Reusable Object-Oriented Software, Introduction, p. 18.]
____

A programação orientada a objetos((("interfaces", "role in object-oriented programming")))
tem tudo a ver com interfaces.
A melhor forma de entender um tipo em Python é conhecer os métodos que
aquele tipo oferece—sua interface—como vimos na
https://fpy.li/8s[«Seção 8.4»] (vol.1).
Desde o Python 3.8, temos quatro maneiras de definir e usar interfaces.
Elas estão ilustradas no _Mapa de Sistemas de Tipagem_ (<<type_systems_described>>).

[[type_systems_described]]
.Na metade superior, checagens de tipo dinâmicas (em tempo de execução) usando só o interpretador Python; a metade inferior requer um checador estático externo como o Mypy, ou um IDE como o PyCharm. Os quadrantes da esquerda se referem à tipagem baseada na estrutura do objeto—isto é, os métodos oferecidos pelo objeto, independente de sua classe ou superclasses; os quadrantes da direita dependem de tipos explicitamente nomeados no código: a classe do objeto, ou suas superclasses.
image::../images/mapa-da-tipagem.png[align="center",pdfwidth=12cm]

Podemos as quatro abordagens assim:

Tipagem pato (_duck typing_)::
    O((("duck typing"))) tratamento padrão para tipos em Python desde o início.
    Estamos estudando tipagem pato desde o primeiro capítulo do volume 1.
Tipagem ganso (_goose typing_)::
    A((("goose typing", "definition of term"))) abordagem suportada pelas classes base abstratas
    (ABCs, _sigla em inglês para Abstract Base Classes_) desde Python 2.6,
    que depende de checar objetos contra ABCs durante a execução.
    A tipagem ganso é um dos principais temas deste capítulo.
Tipagem estática::
    A((("static typing"))) abordagem tradicional das linguagens de tipos estáticos como C e Java;
    suportada desde o Python 3.5 pelo módulo `typing`,
    e aplicada por checadores de tipos externos compatíveis com a
    https://fpy.li/pep484[PEP 484—Type Hints].
    Este não é o foco deste capítulo.
    A maior parte do https://fpy.li/8[«Capítulo 8»] (vol.1) e do <<ch_more_types>>
    mais adiante são sobre tipagem estática.
Tipagem pato estática (_static duck typing_)::
    Uma((("static duck typing"))) abordagem popularizada pela linguagem Go;
    suportada por subclasses de `typing.Protocol`—lançada no Python 3.8 e também
    aplicada com o suporte de checadores de tipos externos. Tratamos desse tema
    pela primeira vez na https://fpy.li/8m[«Seção 8.5.10»] (vol.1), e
    continuamos neste capítulo.


=== O mapa de tipagem

As((("interfaces", "typing map")))((("typing map"))) quatro abordagens retratadas na
<<type_systems_described>> são complementares: elas têm diferentes prós e contras.
Não faz sentido descartar qualquer uma delas.

Cada uma dessas quatro abordagens depende de interfaces para funcionar, mas a
tipagem estática pode ser implementada de forma limitada usando apenas tipos
concretos em vez de abstrações de interfaces como protocolos e classes base
abstratas.
Este capítulo é sobre tipagem pato, tipagem ganso, e
tipagem pato estática—disciplinas de tipagem com foco em interfaces.

O((("interfaces", "topics covered")))((("protocols", "topics covered")))
capítulo está dividido em quatro seções principais, tratando de três dos quatro
quadrantes no Mapa de Sistemas de Tipagem. (<<type_systems_described>>):

* A <<two_kinds_protocols_sec>> compara duas formas de tipagem estrutural com
protocolos—o lado esquerdo do Mapa.

* A <<prog_ducks_sec>> se aprofunda na tipagem pato, que já é familiar para
quem programa em Python. Vamos ver como fazê-la mais segura,
preservando sua melhor qualidade: a flexibilidade.

* A <<goose_typing_sec>> explica o uso de ABCs para uma checagem de tipo mais
estrita durante a execução do código. É a seção mais longa, não por ser a mais
importante, mas porque há mais seções sobre tipagem pato, tipagem pato estática e
tipagem estática em outras partes do livro.

* A <<static_protocols_sec>> cobre o uso, a implementação e o design de subclasses
de `typing.Protocol`—para checagem de tipo estática e durante a execução.


=== Novidades neste capítulo

Editei((("interfaces", "significant changes to")))((("protocols",
"significant changes to"))) profundamente este capítulo,
e ele ficou cerca de 24% mais longo que o capítulo correspondente
(o capítulo 11) na primeira edição de _Python Fluente_.
Apesar de algumas seções e muitos parágrafos serem idênticos, há muito
conteúdo novo.
Estes são os principais acréscimos e modificações:

* A introdução do capítulo e o Mapa de Sistemas de Tipagem
(<<type_systems_described>>) são novos. Essa é a chave da maior parte do
conteúdo novo—e de todos os outros capítulos relacionados à tipagem em Python
≥ 3.8.

* A <<two_kinds_protocols_sec>> compara
protocolos dinâmicos e estáticos.

* Atualizei a <<defensive_duck_prog_sec>> e dei um título que destaca sua
importância: Programação defensiva e "falhe logo"

* A <<static_protocols_sec>> é toda nova. Ela se apoia na apresentação inicial na
https://fpy.li/8m[«Seção 8.5.10»] (vol.1).

* Atualizei os diagramas de classe de `collections.abc` para incluir a ABC
`Collection`, introduzida no Python 3.6.

Na primeira edição de _Python Fluente_ escrevi uma seção encorajando o uso das
ABCs do módulo `numbers` para tipagem ganso.
Na <<numbers_abc_proto_sec>> explico por que, atualmente, é melhor usar
protocolos numéricos estáticos do módulo `typing` como `SupportsFloat` se você
planeja usar checadores de tipos estáticos, ou checagem durante a execução no
estilo da tipagem ganso.


[[two_kinds_protocols_sec]]
=== Dois tipos de protocolos

A((("protocols", "meanings of protocol"))) palavra _protocolo_ tem significados
diferentes na ciência da computação, dependendo do contexto.
Um protocolo de
rede como o HTTP especifica comandos que um cliente pode enviar para um
servidor, como `GET`, `PUT` e `HEAD`.

Vimos na <<protocol_duck_sec>> que um protocolo especifica métodos
que um objeto precisa oferecer para cumprir um papel.

O exemplo `FrenchDeck` no https://fpy.li/1[«Capítulo 1»] (vol.1) demonstra um protocolo, o
protocolo de sequência: os métodos que permitem a um objeto Python se comportar
como uma sequência.

Implementar um protocolo completo pode exigir muitos métodos, mas muitas vezes
não há problema em implementar apenas parte dele.
Considere a classe `Vowels` no
<<ex_minimal_sequence>>.


[[ex_minimal_sequence]]
.Implementação parcial do protocolo de sequência usando `+__getitem__+`
====
[source, python]
----
>>> class Vowels:
...     def __getitem__(self, i):
...         return 'AEIOU'[i]
...
>>> v = Vowels()
>>> v[0]
'A'
>>> v[-1]
'U'
>>> for c in v: print(c)
...
A
E
I
O
U
>>> 'E' in v
True
>>> 'Z' in v
False
----
====

Implementar `+__getitem__+` é o suficiente para obter itens pelo índice, e
também para permitir iteração e o operador `in`. O método
`+__getitem__+` é o método essencial do protocolo de sequência.

Veja a seção
https://fpy.li/6k[Protocolo de Sequência]
do Manual de referência da API Python/C:

`int PySequence_Check(PyObject *o)`::
    Retorna `1` se o objeto oferecer o protocolo de sequência,
    caso contrário retorna `0`.
    Note que esta função retorna `1` para classes Python com um método
    `+__getitem__+`, a menos que sejam subclasses de `dict` [...]

Esperamos que uma sequência também suporte `len()`, através da implementação de
`+__len__+`. `Vowels` não tem um método `+__len__+`, mas ainda assim se comporta
como uma sequência em alguns contextos.
E isso pode ser o suficiente para nossos
propósitos.
Por isso gosto de dizer que um protocolo é uma "interface
informal." Também é assim que protocolos são entendidos em Smalltalk, o primeiro
ambiente de programação orientado a objetos a usar esse termo.

Exceto em páginas sobre programação de redes, a maioria dos usos da palavra
"protocolo" na documentação de Python se refere a essas interfaces informais.

Agora, com a adoção da
https://fpy.li/pep544[_PEP 544—Protocols: Structural subtyping (static duck typing)_]
no Python 3.8, a palavra "protocolo" ganhou um novo sentido em Python—um sentido próximo,
mas diferente.
Como vimos na https://fpy.li/8m[«Seção 8.5.10»] (vol.1),
a PEP 544 nos permite criar subclasses de `typing.Protocol` para definir
um ou mais métodos que uma classe
deve implementar (ou herdar) para satisfazer um checador de tipos estático.

Quando precisar ser específico, vou adotar os seguintes termos:

Protocolo dinâmico::
  Os((("dynamic protocols"))) protocolos informais que Python sempre teve.
  Protocolos dinâmicos são implícitos, definidos por convenção e descritos na
  documentação. Os protocolos dinâmicos mais importantes de Python são
  implementados no próprio interpretador, e documentados no capítulo
  https://fpy.li/2j[Modelo de Dados] em _A Referência da Linguagem Python_.

Protocolo estático::
  Um((("static protocols", "definition of"))) protocolo como definido pela
  https://fpy.li/pep544[_PEP 544—Protocols..._],
  a partir de Python 3.8. Um protocolo estático é declarado explicitamente
  como uma subclasse de `typing.Protocol`.

Há duas diferenças fundamentais entre eles:

* Um objeto pode implementar apenas parte de um protocolo dinâmico e ainda assim
ser útil; mas para satisfazer um protocolo estático, o objeto precisa oferecer
todos os métodos declarados na classe do protocolo, mesmo que seu programa não
precise de todos eles.

* Protocolos estáticos podem ser inspecionados por checadores de tipos
estáticos, protocolos dinâmicos não.

Os dois tipos de protocolo têm uma característica essencial: uma classe
nunca precisa declarar que suporta um protocolo pelo nome (por herança).

Antes dos protocolos estáticos, Python já oferecia outra forma de definir uma
interface explícita no código: uma classe base abstrata (ABC).
O restante deste capítulo trata de protocolos dinâmicos e estáticos, bem como
das ABCs.

[[prog_ducks_sec]]
=== Programando patos

Vamos((("protocols", "sequence and iterable protocols",
id="Pseqit13")))((("sequence protocol", id="seqpro13")))((("Iterable interface",
id="itpro13")))((("interfaces", "Iterable interface"))) começar nossa discussão
de protocolos dinâmicos com os dois mais importantes em Python: o protocolo de
sequência e o iterável. O interpretador faz grandes esforços para lidar com
objetos que fornecem mesmo uma implementação mínima desses protocolos, como
explicado na próxima seção.

[[python_digs_seq_sec]]
==== Python curte sequências

A filosofia do Modelo de Dados de Python é cooperar o máximo possível com os
protocolos dinâmicos essenciais. Quando se trata de sequências, Python faz de
tudo para lidar até com implementações mais rudimentares.

A <<sequence_uml_repeat>>((("UML class diagrams", "Sequence ABC and abstract
classes"))) mostra como a interface `Sequence` está formalizada como uma ABC. O
interpretador Python e as sequências embutidas como `list`, `str`, etc., não
dependem de forma alguma daquela ABC. Só estou usando a figura para descrever o
que uma `Sequence` completa deve oferecer.

[role="width-90"]
[[sequence_uml_repeat]]
.Diagrama de classe UML para a ABC `Sequence` e classes abstratas relacionadas de `collections.abc`. As setas de herança apontam de uma subclasse para suas superclasses. Nomes em itálico são métodos abstratos. Antes de Python 3.6, não existia uma ABC `Collection`—`Sequence` era uma subclasse direta de `Container`, `Iterable` e `Sized`.
image::../images/flpy_1302.png[align="center",pdfwidth=10cm]

[TIP]
====
A maior parte das ABCs no módulo `collections.abc` existe para formalizar
interfaces que já eram implementadas por objetos nativos e implicitamente
suportadas pelo interpretador, muito antes daquele módulo existir.
As ABCs são úteis como pontos de partida para novas classes, e
para permitir checagem de tipo explícita durante a execução (tipagem ganso),
bem como para servirem de dicas de tipo para checadores de tipos estáticos.
====

Estudando a <<sequence_uml_repeat>>, vemos que uma subclasse concreta de
`Sequence` deve implementar `+__getitem__+` e `+__len__+` (de `Sized`). Todos os
outros métodos `Sequence` são concretos, então as subclasses podem herdar suas
implementações ou fornecer versões melhores.

Agora, lembre-se da classe `Vowels` no <<ex_minimal_sequence>>. Ela não herda de
`abc.Sequence` e implementa apenas `+__getitem__+`.

As instâncias de `Vowels` são iteráveis porque, na falta de um `+__iter__+`,
Python tenta iterar invocando `+__getitem__+` com índices inteiros começando em
`0`. Da mesma forma que Python é esperto o suficiente para iterar sobre
instâncias de `Vowels`, ele também consegue fazer o operador `in` funcionar
mesmo quando o método `+__contains__+` não existe: ele faz uma busca sequencial
para verificar se o item está presente.

Em resumo, dada a importância das sequências como estruturas de dados, Python consegue
fazer a iteração e o operador `in` funcionarem invocando `+__getitem__+` quando
`+__iter__+` e `+__contains__+` não estão presentes.

O `FrenchDeck` original do https://fpy.li/1[«Capítulo 1»] (vol.1) também não é subclasse de
`abc.Sequence`, mas ele implementa os dois métodos do protocolo de sequência:
`+__getitem__+` e `+__len__+`. Veja o <<ex_pythonic_deck_repeat>>.

[[ex_pythonic_deck_repeat]]
.Um baralho como uma sequência de cartas, como https://fpy.li/8x[«Exemplo 1 do Capítulo 1»] (vol.1)
====
[source, python]
----
include::../code/01-data-model/frenchdeck.py[]
----
====

Muitos dos exemplos no https://fpy.li/1[«Capítulo 1»] (vol.1) funcionam por causa do tratamento
especial que Python dá a estruturas vagamente semelhantes a uma sequência.
O protocolo iterável em Python representa uma forma extrema de tipagem pato:
o interpretador tenta dois métodos diferentes para iterar sobre objetos.

Para deixar mais claro, os comportamentos que descrevi nessa seção estão
implementados no próprio interpretador, na maioria dos casos em C. Eles não
dependem dos métodos da ABC `Sequence`. Por exemplo, os métodos concretos
`+__iter__+` e `+__contains__+` na classe `Sequence` emulam comportamentos
internos do interpretador Python. Se tiver curiosidade, veja o código-fonte
destes métodos em https://fpy.li/13-3[_Lib/_collections_abc.py_].

Agora vamos estudar um exemplo que demonstra por que checadores de tipos
estáticos não têm como lidar com protocolos
dinâmicos.((("", startref="Pseqit13")))((("", startref="seqpro13")))((("",
startref="itpro13")))


==== Monkey patching: implementando um protocolo em runtime

_Monkey patching_((("protocols", "implementing at runtime",
id="Prun13")))((("monkey-patching", id="monkey13"))) é o ato de remendar (_patch_)
dinamicamente um programa durante a execução do código (_runtime_),
para acrescentar funcionalidade ou corrigir bugs. Por exemplo, a biblioteca de
rede https://www.gevent.org/api/gevent.monkey.html[_gevent_] faz "monkey patch"
em partes da biblioteca padrão de Python, para permitir concorrência sem threads ou
`async`/`await`.footnote:[O artigo https://fpy.li/13-4["Monkey patch"] na
Wikipedia tem um exemplo engraçado em Python.]
O monkey patch não lê nem altera o código-fonte do programa,
apenas os objetos na memória que representam as partes do programa,
como módulos, classes e funções.

Vamos fazer _monkey patch_ na classe `FrenchDeck` do <<ex_pythonic_deck_repeat>>
para superar uma grande limitação: ela não pode ser embaralhada. Anos atrás,
quando escrevi pela primeira vez o exemplo `FrenchDeck`, implementei um método
`shuffle`. Depois tive uma sacada pythônica: se um `FrenchDeck` funciona como
uma sequência, não precisa ter um método `shuffle`, pois já existe a função
`random.shuffle`, que "embaralha a sequência x internamente" conforme a
https://fpy.li/6m[documentação oficial]. 

A função `random.shuffle` é usada assim:

[source, python]
----
>>> from random import shuffle
>>> l = list(range(10))
>>> shuffle(l)
>>> l
[5, 2, 9, 7, 8, 3, 1, 4, 0, 6]
----

[TIP]
====
Ao adotar protocolos estabelecidos, aumenta muito suas chances de aproveitar o
código já existente na biblioteca padrão e em bibliotecas de terceiros, graças à
tipagem pato.
====

Entretanto, se tentamos usar shuffle com uma instância de `FrenchDeck`
ocorre uma exceção, como visto no <<ex_unshuffable>>.

[[ex_unshuffable]]
.`random.shuffle` não funciona com `FrenchDeck`
====
[source, python]
----
>>> from random import shuffle
>>> from frenchdeck import FrenchDeck
>>> deck = FrenchDeck()
>>> shuffle(deck)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../random.py", line 265, in shuffle
    x[i], x[j] = x[j], x[i]
TypeError: 'FrenchDeck' object does not support item assignment
----
====

A mensagem de erro é clara: "o objeto 'FrenchDeck' não suporta a atribuição de
itens". O problema é que `shuffle` opera internamente, trocando os itens de
lugar dentro da coleção, mas `FrenchDeck` só implementa o protocolo de sequência
imutável. Para ser uma sequência mutável, `FrenchDeck` precisa oferecer um
método `+__setitem__+`.

Como Python é dinâmico, podemos consertar isso durante a execução, até mesmo no
console interativo. O <<ex_monkey_patch>> mostra como fazer isso.

[[ex_monkey_patch]]
."Monkey patching" o `FrenchDeck` para torná-lo mutável e compatível com `random.shuffle` (continuação do <<ex_unshuffable>>)
====
[source, python]
----
>>> def set_card(deck, position, card):  <1>
...     deck._cards[position] = card
...
>>> FrenchDeck.__setitem__ = set_card  <2>
>>> shuffle(deck)  <3>
>>> deck[:5]
[Card(rank='3', suit='hearts'), Card(rank='4', suit='diamonds'), Card(rank='4',
suit='clubs'), Card(rank='7', suit='hearts'), Card(rank='9', suit='spades')]
----
====
<1> Cria uma função que recebe `deck`, `position`, e `card` como argumentos.

<2> Atribui aquela função a um atributo chamado `+__setitem__+` na classe
`FrenchDeck`.

<3> `deck` agora pode ser embaralhado, pois acrescentei o método necessário do
protocolo de sequência mutável.

A assinatura do método especial `+__setitem__+` está definida na
Referência da Linguagem Python em
https://fpy.li/6n[Emulando tipos contêineres].
Aqui nomeei os argumentos `deck, position, card`&#x2014;e não `self, key, value` como na
referência da linguagem—para mostrar que todo método Python começa sua vida como
uma função comum, e nomear o primeiro argumento `self` é só uma convenção.
Fugir da convenção é OK em uma sessão no console onde o código é descartável,
mas em um arquivo de código-fonte de Python é muito melhor usar
`self`, `key`, e `value`, seguindo a documentação.

O truque é que `set_card` pressupõe que o `deck` tem um atributo chamado
`+_cards+`, e seu valor deve ser uma sequência mutável. A função `set_cards` é
então anexada à classe `FrenchDeck` como o método especial
`+__setitem__+`. Isso é um exemplo de _monkey patching_: modificar uma classe ou
módulo durante a execução, sem tocar no código-fonte. O "monkey patching" é
poderoso, mas o código que executa a modificação fica muito intimamente acoplado
ao programa sendo modificado, muitas vezes trabalhando com atributos privados e
não-documentados.

Além de ser um exemplo de monkey patching, o <<ex_monkey_patch>> enfatiza a
natureza dinâmica dos protocolos na tipagem pato: `random.shuffle` não
se importa com a classe do argumento, ela só precisa que o objeto implemente
métodos do protocolo de sequência mutável. Não importa sequer se o objeto
"nasceu" com os métodos necessários ou se eles foram de alguma forma adquiridos
depois.

Quando bem aplicada, tipagem pato não é loucamente insegura ou difícil de depurar e
manter. A próxima seção mostra alguns padrões de programação úteis para detectar
protocolos dinâmicos sem recorrer a checagens explícitas.((("",
startref="monkey13")))((("", startref="Prun13")))

[[defensive_duck_prog_sec]]
==== Programação defensiva e "falhe logo"

Programação defensiva((("protocols", "defensive programming",
id="Pdefens13")))((("fail-fast philosophy", id="failfast13")))((("defensive programming",
id="defprog13"))) é como direção defensiva: um conjunto de
práticas para melhorar a segurança, mesmo na presença de programadores
(ou motoristas) descuidados.

Muitos bugs não podem ser encontrados exceto durante a execução—mesmo nas
principais linguagens de tipagem estática.footnote:[Por isso a necessidade de
testes automatizados.] Em uma linguagem de tipagem dinâmica, "falhe logo" 
(_fail fast_) é um ótimo conselho para codar programas mais seguros e mais fáceis de manter.
Falhar logo significa assegurar que os erros em tempo de execução
sejam detectados o mais cedo possível.
Por exemplo, rejeitando argumentos inválidos no início do corpo de uma
função.

Exemplo prático: quando você escreve código que aceita uma sequência de
itens para processar internamente como uma `list`, não valide o argumento
só por checagem de tipo. Em vez disso, receba o argumento e construa
imediatamente uma `list` a partir dele. Um exemplo desse padrão de programação é
o método `+__init__+` no <<ex_lotto>>, que veremos mais à frente nesse capítulo:

[source, python]
----
    def __init__(self, iterable):
        self._balls = list(iterable)
----

Desta forma você torna seu código mais flexível, pois o construtor de `list()`
processa qualquer iterável que caiba na memória. Se o argumento não for
iterável, a chamada vai falhar logo com uma exceção de `TypeError`
bastante clara, no exato momento em que o objeto for inicializado. Se
quiser ser mais explícito, pode colocar a chamada a `list()` em um
`try/except`, para adequar a mensagem de erro—mas eu escreveria este código extra
apenas em uma API externa, pois a falha já estaria bem visível para os
mantenedores que conhecem a base de código. De toda forma, a chamada errônea vai aparecer
perto do final do traceback, tornando-a fácil de corrigir. Se você não barrar o
argumento inválido no construtor da classe, o programa vai quebrar mais tarde,
quando algum outro método da classe precisar usar a variável `self._balls` e ela
não for uma `list`. Então a causa do problema estará mais distante e 
será mais difícil de encontrar.

Naturalmente, seria ruim passar o argumento para `list()` se os dados não devem
ser copiados, ou por seu tamanho ou porque quem chama a função,
espera que os itens sejam modificados internamente, como no caso de
`random.shuffle`. Neste caso, uma checagem durante a execução como
`isinstance(x, abc.MutableSequence)` seria a melhor opção:
a abordagem da tipagem ganso.

Se tiver receio de consumir um gerador infinito—algo que não é um
problema muito comum—pode começar chamando `len()` com o argumento. Isso
rejeitaria iteradores, mas lidaria de forma segura com tuplas, arrays e outras
classes existentes ou futuras que implementem a interface `Sequence` completa.
Chamar `len()` normalmente não custa muito, e um argumento inválido vai gerar
um erro na hora.

Por outro lado, se qualquer iterável for aceitável, chame `iter(x)` assim que
possível, para obter um iterador, como veremos na https://fpy.li/89[«Seção 17.3»] (vol.3). E
novamente, se `x` não for iterável, isso falhará logo com uma exceção
fácil de depurar.

<<<
Nos casos que acabei de descrever, uma dica de tipo poderia apontar alguns
problemas mais cedo, mas não todos os problemas. Lembre-se de que o tipo `Any` é
_consistente-com_ qualquer outro tipo. Inferência de tipo pode fazer com que uma
variável seja marcada com o tipo `Any`. Quando isso acontece, o checador de
tipos se torna inútil. Além disso, dicas de tipo não são aplicadas durante a
execução. Falhar logo é a última linha de defesa.

Código defensivo usando tipagem pato também pode incluir lógica para lidar
com tipos diferentes sem usar testes com `isinstance()` e `hasattr()`.

Um exemplo é como poderíamos imitar como
https://fpy.li/13-8[`collections.namedtuple`] lida com o argumento
`field_names`: ele aceita uma única string com identificadores
separados por espaços ou vírgulas, ou uma sequência de identificadores. O
<<ex_duck_typing_str_list>> mostra como eu faria isso usando tipagem pato.

[[ex_duck_typing_str_list]]
.Tipagem pato para lidar com uma string ou um iterável de strings
====
[source, python]
----
    try:  <1>
        field_names = field_names.replace(',', ' ').split()  <2>
    except AttributeError:  <3>
        pass  <4>
    field_names = tuple(field_names)  <5>
    if not all(s.isidentifier() for s in field_names):  <6>
        raise ValueError('field_names must all be valid identifiers')
----
====

<1> Supõe que é uma string.

<2> Converte vírgulas em espaços e divide o resultado em uma lista de nomes.

<3> Perdão, `field_names` não grasna como uma `str`: não tem `.replace`, ou
tem um `.replace` que devolve algo que não funciona com `.split`

<4> Se um `AttributeError` aconteceu, então `field_names` não é uma `str`.
Supomos que já é um iterável de nomes.

<5> Para ter certeza de que é um iterável e para manter nossa própria cópia,
criamos uma tupla com o que temos. Uma tuple é mais compacta que uma lista, e
também impede que meu código troque os nomes por acidente.

<6> Usamos `str.isidentifier` para garantir que todos os nomes são válidos.

<<<
O passo `②` do <<ex_duck_typing_str_list>> é uma aplicação de EAFP ou
Princípio de Hopper.footnote:[A pioneira
da computação Grace Hopper dizia que, para inovar em uma burocracia,
é mais fácil pedir perdão do que permissão
(_"(It's) Easier to Ask Forgiveness than Permission"_ ou _EAFP_).]
Em vez de testar se `+field_names+` é uma string,
invocamos métodos como se fosse uma string,
e se não der certo, tratamos a exceção.
Não pedimos licença:
fazemos o que temos que fazer e pedimos perdão se for necessário.

O <<ex_duck_typing_str_list>> mostra uma situação em que a tipagem pato é mais
expressiva que dicas de tipo estáticas. Não há como escrever uma dica de tipo
que diga "o argumento `field_names` deve ser uma string de identificadores separados por
espaços ou vírgulas." Esta é a parte relevante da assinatura de `namedtuple` no
typeshed (veja o código-fonte completo em 
https://fpy.li/13-9[_stdlib/3/collections/__init__.pyi_]):

[source, python]
----
    def namedtuple(
        typename: str,
        field_names: Union[str, Iterable[str]],
        *,
        # outros parâmetros omitidos
----

Como se vê, `field_names` está anotado como `Union[str, Iterable[str]]`,
que ajuda em parte, mas não é suficiente para descrever a estrutura interna da
string.

Após revisar protocolos dinâmicos, passamos para uma forma mais explícita de
checagem de tipo durante a execução: tipagem ganso.((("",
startref="Pdefens13")))((("", startref="failfast13")))((("",
startref="defprog13")))

[[goose_typing_sec]]
=== Tipagem ganso

[quote, Bjarne Stroustrup, criador do {cpp}]
____

Uma classe abstrata representa uma interface.footnote:[No original: "An abstract
class represents an interface", Bjarne Stroustrup, _The Design and Evolution of
{cpp}_ (Addison-Wesley, 1994), p. 278.]

____

Python((("goose typing", "abstract base classes (ABCs)",
id="GTabcs13")))((("ABCs (abstract base classes)", "goose typing and",
id="ABCgoose13"))) não tem uma palavra-chave `interface`. Usamos classes base
abstratas (ABCs) para definir interfaces úteis para checagem explícita de tipo
durante a execução, e também para anotações compatíveis com
checadores de tipos estáticos.


O verbete
https://fpy.li/6p[classe base abstrata]
no Glossário da Documentação de Python tem uma boa explicação do
valor dessas estruturas para linguagens que usam tipagem pato:

[quote]
____

Classes base abstratas complementam a tipagem pato, fornecendo uma maneira de
definir interfaces quando outras técnicas, como `hasattr()`, seriam desajeitadas
ou sutilmente erradas (por exemplo, com métodos mágicos). ABCs introduzem
subclasses virtuais, classes que não herdam de uma classe mas ainda são
reconhecidas por `isinstance()` e `issubclass()`; veja a documentação do módulo
`abc`.
____

A tipagem ganso é uma abordagem à checagem de tipo durante a execução que se
apoia nas ABCs. Vou deixar que Alex Martelli explique, no texto _<<waterfowl_essay>>_.

[NOTE]
====
Sou muito grato a meus amigos Alex Martelli e Anna Ravenscroft. Mostrei a eles a
primeira lista de tópicos do _Python Fluente_ na OSCON 2013, e eles me
encorajaram a submeter à O'Reilly para publicação. Depois os dois
contribuíram com revisões técnicas minuciosas. Alex já era a pessoa mais citada
nesse livro quando se ofereceu para escrever este ensaio.
====

[[waterfowl_essay]]
.Pássaros aquáticos e as ABCs
****

*por Alex Martelli*

Fui https://fpy.li/13-11[creditado na Wikipedia] por ajudar a popularizar
o meme útil e frase de efeito "_duck typing_" (isto é, ignorar o tipo declarado
de um objeto, e em vez disso se dedicar a assegurar que o objeto implementa os
nomes, assinaturas e semântica dos métodos necessários para o uso pretendido).

Em Python, isso essencialmente significa evitar o uso de `isinstance` para
checar o tipo do objeto (sem nem mencionar a abordagem ainda pior de
checar, por exemplo, se `type(foo) is bar`&#x2014;que é corretamente
considerado um anátema, pois inibe até as formas mais simples de herança!).

No geral, a abordagem da tipagem pato continua muito útil em inúmeros
contextos—mas em muitos outros, uma nova abordagem muitas vezes preferível
evoluiu ao longo do tempo. E aqui começa nossa história...

Em gerações recentes, a taxonomia de gênero e espécies (incluindo, mas não
limitada à família de pássaros aquáticos conhecida como Anatidae) foi guiada
principalmente pela _fenética_—uma abordagem centrada nas similaridades de
morfologia e comportamento... principalmente traços _observáveis_. A analogia
com "_duck typing_" era evidente.

Entretanto, a evolução paralela muitas vezes pode produzir características
similares, tanto morfológicas quanto comportamentais, em espécies sem qualquer
relação de parentesco, que apenas calharam de evoluir em nichos ecológicos
similares, porém separados. "Similaridades acidentais" parecidas acontecem
também em programação—por exemplo, considere um exemplo clássico de
programação orientada a objetos:footnote:[NT: O exemplo citado por Martelli é
intraduzível. Ele joga com três significados diferentes do verbo
"to draw": artista desenha; o pistoleiro saca (a arma);
a loteria sorteia um número.]

[source, python]
----
class Artist:
    def draw(self): ...

class Gunslinger:
    def draw(self): ...

class Lottery:
    def draw(self): ...
----

Obviamente, a mera existência de um método chamado `draw`, sem parâmetros,
não é suficiente para garantir que dois objetos `x` e `y`,
que aceitem as invocações `x.draw()` e `y.draw()`,
são de qualquer forma intercambiáveis ou abstratamente equivalentes—nada
pode ser inferido sobre a similaridade da semântica resultante de tais chamadas.
Na verdade, é necessário um programador consciente para,
de alguma forma, _assegurar_ afirmativamente que tal equivalência é verdadeira em algum nível.

Em biologia (e outras disciplinas), este problema levou à emergência (e, em
muitas facetas, à dominância) de uma abordagem alternativa à _fenética_,
conhecida como ((("cladistics"))) __cladística__ — que baseia as escolhas
taxonômicas em características herdadas de ancestrais comuns em vez daquelas que
evoluíram de forma independente (o sequenciamento de DNA cada vez mais barato e
rápido vem tornando a cladística bastante prática em mais casos).

Por exemplo, os Chloephaga, gênero de gansos sul-americanos (antes classificados
como próximos a outros gansos) e as tadornas (gênero de patos sul-americanos)
estão agora agrupados juntos na subfamília Tadornidae (sugerindo que eles são
mais próximos entre si do que de qualquer outro Anatidae, pois compartilham um
ancestral comum mais próximo). Além disso, a análise de DNA mostrou que o
Asarcornis (pato da floresta ou pato de asas brancas) não é tão próximo do
Cairina moschata (pato-do-mato), esse último uma tadorna, como as similaridades
corporais e comportamentais sugeriram por tanto tempo—então o pato da floresta
foi reclassificado em um gênero próprio, inteiramente fora da subfamília!

Isso importa? Depende do contexto! Para o propósito de decidir como cozinhar uma
ave após caçá-la, por exemplo, características observáveis específicas (mas
nem todas—a plumagem, por exemplo, é de mínima importância nesse contexto),
especialmente textura e sabor (a boa e velha fenética), podem ser mais
relevantes que a cladística. Mas para outros problemas, tal como a
suscetibilidade a diferentes patógenos (se quiser criar aves aquáticas em
cativeiro, ou preservá-las na natureza), a proximidade do DNA pode ser mais
importante.

Então, a partir dessa analogia aproximada com as revoluções taxonômicas no mundo
das aves aquáticas, estou recomendando suplementar (não substituir
inteiramente—em determinados contextos ela ainda servirá) o bom e velho _duck
typing_ por... _goose typing_ (tipagem ganso)!

_Goose typing_ significa o seguinte: `isinstance(obj, cls)` agora é plenamente
aceitável... desde que `cls` seja uma classe base abstrata—em outras palavras, a
metaclasse de `cls` é `abc.ABCMeta`.

Você vai encontrar muitas classes abstratas prontas em `collections.abc` (e
outras no módulo `numbers` da biblioteca padrão do Python)footnote:[Você também
pode, claro, definir suas próprias ABCs—mas eu não recomendaria esse caminho a
ninguém, exceto aos mais avançados pythonistas, da mesma forma que os
desencorajaria de definir suas próprias metaclasses customizadas... e mesmo para
os ditos "mais avançados pythonistas", aqueles que exibem o domínio de todos
os recantos por mais obscuros da linguagem, essas não são ferramentas de
uso frequente. Este tipo de "metaprogramação profunda", se alguma vez for
apropriada, o será no contexto dos autores de frameworks abrangentes, projetados
para serem estendidos de forma independente por inúmeras equipes de
desenvolvimento diferentes... menos que 1% dos "mais avançados pythonistas"
precisará disso alguma vez na vida!—_A.M_]

Dentre as muitas vantagens conceituais das ABCs sobre classes concretas (e.g., a
prescrição de Scott Meyer “toda classe não-final (não-folha) deveria ser
abstrata”; veja o https://fpy.li/13-12[Item 33] de seu livro, _More Effective
{cpp}_, Addison-Wesley), as ABCs de Python acrescentam uma grande vantagem
prática: o método de classe `register`, que permite ao código da aplicação
"declarar" que determinada classe é uma subclasse "virtual" de uma ABC (para
este propósito, a classe registrada precisa cumprir os requisitos de nome de
métodos e assinatura da ABC e, mais importante, o contrato semântico
subjacente—mas não precisa ter sido desenvolvida com qualquer conhecimento da
ABC, e especificamente não precisa herdar dela!). Isso é um longo caminho andado
na direção de quebrar a rigidez e o acoplamento forte que torna herança algo
para ser usado com mais cautela que aquela tipicamente praticada pela maioria
dos programadores orientados a objetos.

Em algumas ocasiões você sequer precisa registrar uma classe para que uma ABC a
reconheça como uma subclasse!

Esse é o caso das ABCs cuja essência se resume em alguns métodos especiais.
Por exemplo:footnote:[NT: Outro exemplo intraduzível. A frase "class struggle"
é uma referência bem humorada ao conceito marxista da "luta de classes".]

[source, python]
----
>>> class Struggle:
...     def __len__(self): return 23
...
>>> from collections import abc
>>> isinstance(Struggle(), abc.Sized)
True
----

Como se vê, `abc.Sized` reconhece `Struggle` como uma `subclasse`, sem
necessidade de registro, já que implementar o método especial chamado
`+__len__+` é o suficiente (o método deve ser implementado com a sintaxe e
semântica corretas—deve poder ser chamado sem argumentos e retornar um inteiro
não-negativo indicando o "comprimento" do objeto; mas qualquer código que
implemente um método com nome especial, como `+__len__+`, com uma sintaxe e uma
semântica arbitrárias e incompatíveis tem problemas bem maiores que estes).

Então, aqui está minha mensagem de despedida: sempre que você estiver
implementando uma classe que incorpore quaisquer dos conceitos representados nas
ABCs de `number`, `collections.abc` ou em outro framework que estiver usando,
assegure-se (caso necessário) de ser uma subclasse ou de registrar sua classe com a
ABC correspondente. No início de seu programa que utiliza uma biblioteca ou
framework que define classes que omitiram esse passo, registre você mesmo as
classes. Daí, quando precisar checar se um argumento é, por
exemplo, "uma sequência", verifique se:

[source, python]
----
isinstance(the_arg, collections.abc.Sequence)
----

E _não_ defina ABCs customizadas (ou metaclasses) em código de produção. Se você
sentir uma forte necessidade de fazer isso, aposto que é um caso da síndrome de
"todos os problemas se parecem com um prego" em alguém que acabou de ganhar um
novo martelo brilhante—você (e os futuros mantenedores de seu código) serão
mais felizes se limitando a código simples e direto, e evitando((("",
startref="GTabcs13")))((("", startref="ABCgoose13"))) tais profundezas. _Valē!_

****

Em((("goose typing", "overview of"))) resumo, tipagem ganso implica:

* Criar subclasses de ABCs, para tornar explícito que você está implementando
uma interface previamente definida.

* Checagem de tipo durante a execução usando as ABCs em vez de classes concretas
como segundo argumento para `isinstance` e `issubclass`.

Alex também aponta que herdar de uma ABC é mais que implementar os métodos
necessários: é também uma declaração de intenções clara da parte do
desenvolvedor. A intenção também pode ficar explícita através do registro de uma
subclasse virtual.

[NOTE]
====

Detalhes sobre o uso de `register` são tratados na <<virtual_subclass_sec>>,
mais adiante. Por hora, aqui está um pequeno exemplo: dada a classe
`FrenchDeck`, se eu quiser que ela passe em uma checagem como
`issubclass(FrenchDeck, Sequence)`, posso torná-la uma _subclasse virtual_ da
ABC `Sequence` assim:

[source, python]
----
from collections.abc import Sequence
Sequence.register(FrenchDeck)
----
====

O uso de `isinstance` e `issubclass` se torna mais aceitável se você está
checando ABCs em vez de classes concretas. Se usadas com classes concretas,
checagens de tipo limitam o polimorfismo—um recurso essencial da programação
orientada a objetos. Mas com ABCs esses testes são mais flexíveis. Afinal, se um
componente não implementa uma ABC sendo uma subclasse—mas implementa os métodos
necessários—ele sempre pode ser registrado posteriormente e passar naquelas
checagens de tipo explícitas.

Entretanto, mesmo com ABCs, você deve se precaver contra o uso excessivo de
checagens com `isinstance`, pois isso pode ser sintoma de um design ruim.

Normalmente, não é bom ter uma série de `if/elif/elif` com checagens de
`isinstance` executando ações diferentes, dependendo do tipo de objeto: neste
caso você deveria estar usando polimorfismo—isto é, projetando suas classes para
permitir ao interpretador invocar os métodos corretos, em vez de
codificar diretamente a lógica de despacho em blocos `if/elif/elif`.

Por outro lado, não há problema em executar uma checagem com `isinstance` contra
uma ABC se você quer garantir um contrato de API: "Cara, você precisa
implementar isso se quiser me chamar," como costuma dizer o revisor técnico
Lennart Regebro. Isso é especialmente útil em sistemas com arquiteturas
modulares extensíveis por plug-ins. Fora dos frameworks, tipagem pato muitas
vezes é mais simples e flexível que checagens de tipo explícitas.

Por fim, em seu ensaio Alex reforça mais de uma vez a necessidade de limitar a
criação de ABCs. Uso excessivo de ABCs imporia cerimônia a uma linguagem que se
tornou popular por ser prática e pragmática. Durante o processo de revisão do
_Python Fluente_, Alex colocou num e-mail:

[quote]
____
ABCs servem para encapsular conceitos muito genéricos, abstrações introduzidas
por um framework—coisa como "uma sequência" e "um número exato". [Os leitores]
quase certamente não precisam escrever alguma nova ABC, apenas usar as já
existentes de forma correta, para obter 99% dos benefícios sem qualquer risco
sério de design mal-feito.
____

Agora vamos ver a tipagem ganso na prática.

==== Criando uma subclasse de uma ABC

Seguindo((("inheritance and subclassing", "subclassing ABCs", id="IASabcs13")))((("goose typing", "subclassing ABCs", id="GTsub13")))((("ABCs (abstract base classes)", "subclassing ABCs", id="ABCsub13"))) o conselho de Martelli, vamos aproveitar uma ABC existente, `collections.MutableSequence`, antes de ousar inventar uma nova.
No <<ex_pythonic_deck2>>, `FrenchDeck2` é explicitamente declarada como subclasse de `collections.MutableSequence`.

[[ex_pythonic_deck2]]
.frenchdeck2.py: `FrenchDeck2`, uma subclasse de `collections.MutableSequence`
====
[source, python]
----
include::../code/13-protocol-abc/frenchdeck2.py[]
----
====
[role="pagebreak-before less_space"]
<1> `+__setitem__+` é tudo que precisamos para possibilitar o embaralhamento...
<2> ...mas uma subclasse de `MutableSequence` é forçada a implementar `+__delitem__+`, um método abstrato daquela ABC.
<3> Também precisamos implementar `insert`, o terceiro método abstrato de `MutableSequence`.

Python não verifica a implementação de métodos abstratos durante a importação
(quando o módulo _frenchdeck2.py_ é carregado na memória e compilado),
mas apenas durante a execução, quando tentamos de fato instanciar `FrenchDeck2`.
Ali, se deixamos de implementar qualquer um dos métodos abstratos,
recebemos uma exceção de `TypeError` com uma mensagem como
_Can't instantiate abstract class FrenchDeck2 with abstract methods `+__delitem__+`, ``insert``_
(Impossível instanciar a classe abstrata `FrenchDeck2` com os métodos abstratos `+__delitem__+`, ``insert``).
Por isso precisamos implementar `+__delitem__+` e `insert`,
mesmo se nossos exemplos usando `FrenchDeck2` não precisem desses comportamentos:
a ABC `MutableSequence` os exige.

Como((("UML class diagrams", "MutableSequence ABC and superclasses")))
mostra a <<mutablesequence_uml>>, nem todos os métodos das ABCs `Sequence`
e `MutableSequence` ABCs são abstratos.

[[mutablesequence_uml]]
.Diagrama de classe UML para a ABC `MutableSequence` e suas superclasses em `collections.abc` (as setas de herança apontam das subclasses para as ancestrais; nomes em itálico são classes e métodos abstratos).
image::../images/flpy_1303.png[]

Para escrever `FrenchDeck2` como uma subclasse de `MutableSequence`, tive que
pagar o preço de implementar `+__delitem__+` e `insert`, desnecessários em meus
exemplos. Em troca, `FrenchDeck2` herda cinco métodos concretos de `Sequence`:
`+__contains__+`, `+__iter__+`, `+__reversed__+`, `index`, e `count`. De
`MutableSequence`, ela recebe outros seis métodos: `append`, `reverse`,
`extend`, `pop`, `remove`, e `+__iadd__+`— que suporta o operador `+=` para
concatenação direta.

Os métodos concretos em cada ABC de `collections.abc` são implementados nos
termos da interface pública da classe, então funcionam sem qualquer conhecimento
da estrutura interna das instâncias.


[TIP]
====
Como programador de uma subclasse concreta, você pode sobrescrever os métodos
concretos herdados das ABCs com implementações mais eficientes. Por exemplo,
`+__contains__+` funciona executando uma busca sequencial, mas se a sua classe
de sequência mantém os itens ordenados, você pode escrever um `+__contains__+`
que executa uma busca binária usando a função https://fpy.li/13-13[`bisect`] da
biblioteca padrão. Veja
https://fpy.li/bisect[_Managing Ordered Sequences with Bisect_]
em _https://fluentpython.com_ para conhecer mais sobre esta função.
====


Para usar bem as ABCs, você precisa saber o que está disponível. Vamos então
revisar as ABCs de `collections` a seguir.((("", startref="GTsub13")))((("",
startref="ABCsub13")))((("", startref="IASabcs13")))

[[abc_in_stdlib_sec]]
==== ABCs na biblioteca padrão

Desde((("goose typing", "ABCs in Python standard library",
id="GTstlib13")))((("ABCs (abstract base classes)",
"in Python standard library", secondary-sortas="Python standard library",
id="ABCstndlib13")))((("collections.abc module", "abstract base classes defined in")))
Python 2.6, a biblioteca padrão oferece várias ABCs. A maioria está
definida no módulo `collections.abc`, mas há outras nos pacotes `io` e `numbers`,
por exemplo.
A <<collections_uml>> é((("UML class diagrams", "ABCs in collections.abc"))) um
diagrama de classe resumido (sem os nomes dos atributos) das 17 ABCs definidas
em `collections.abc`. 

[[collections_uml]]
.Diagrama de classes UML para as ABCs em `collections.abc`.
image::../images/flpy_1304.png[align="center",pdfwidth=11cm]




[TIP]
====

Há dois módulos chamados `abc` na biblioteca padrão.
Aqui estamos falando sobre o `collections.abc`.
Para reduzir o tempo de carregamento, desde o Python 3.4 aquele módulo é implementado fora do pacote `collections` — em https://fpy.li/13-14[_Lib/_collections_abc.py_] — então é importado separado de `collections`.
O((("abc.ABC class"))) outro módulo `abc` é apenas `abc` (i.e., https://fpy.li/13-15[_Lib/abc.py_]), onde a classe `abc.ABC` é definida.
Toda ABC depende do módulo `abc`, mas não precisamos importá-lo nós mesmos, exceto para criar uma nova ABC.

====

<<<
A documentação de `collections.abc` inclui uma ótima 
https://fpy.li/13-16[«tabela»] resumindo as ABCs, suas relações e seus
métodos abstratos e concretos (chamados "métodos mixin"). Há muita herança
múltipla acontecendo na <<collections_uml>>. Vamos dedicar a maior parte do
<<ch_inheritance>> à herança múltipla, mas por hora é suficiente dizer que isso
normalmente não causa problemas no caso das ABCs.footnote:[Herança múltipla foi
_considerada nociva_ e excluída do Java, exceto para interfaces:
Interfaces Java podem estender múltiplas interfaces,
e classes Java podem implementar múltiplas interfaces.]
Vamos rever os grupos na <<collections_uml>>:

`Iterable`, `Container`, `Sized`::
Toda coleção deveria herdar destas ABCs ou implementar protocolos compatíveis.
`Iterable` define `+__iter__+` para suportar iteração,
`Container` define `+__contains__+` para o operador `in`,
e `Sized` define `+__len__+` para `len()`.

`Collection`::
Essa ABC não tem nenhum método próprio, mas foi acrescentada no Python 3.6 para
facilitar a criação de subclasses de `Iterable`, `Container`, e `Sized`.

`Sequence`, `Mapping`, `Set`::
Esses são os principais tipos de coleções imutáveis, e cada um tem uma subclasse
mutável. Um diagrama detalhado de `MutableSequence` é apresentado na
<<mutablesequence_uml>>; para `MutableMapping` e `MutableSet`, veja os
diagramas UML no https://fpy.li/3[«Capítulo 3»] (vol.1).

`MappingView`::
No Python 3, os objetos devolvidos pelos métodos de mapeamentos `.items()`,
`.keys()`, e `.values()` implementam as interfaces definidas em `ItemsView`,
`KeysView`, e `ValuesView`, respectivamente. Os dois primeiros também
implementam a rica interface de `Set`, com todos os operadores que vimos na
https://fpy.li/8n[«Seção 3.11.1»] (vol.1).

`Iterator`::
Observe que iterator é subclasse de `Iterable`.
Discutiremos este detalhe em https://fpy.li/17[«Capítulo 17»] (vol.3).

`Callable`, `Hashable`::
Estas não são coleções, mas `collections.abc` foi o primeiro pacote a definir
ABCs na biblioteca padrão, e estas duas foram incluídas por serem importantes.
Elas suportam a checagem de tipos de objetos
que precisam ser invocáveis ou _hashable_.

Para a detecção de invocável, a função embutida `callable(obj)` é mais
conveniente que `insinstance(obj, Callable)`.

Se `insinstance(obj, Hashable)` devolver `False`, pode ter certeza de que
`obj` não é _hashable_. Mas se ela devolver `True`, pode ser um falso positivo.
Isso é explicado no box seguinte.

[[isinstance_mislead_box]]
.`isinstance` com `Hashable` e `Iterable` pode te enganar
****

É fácil interpretar errado os resultados de testes usando `isinstance` e
`issubclass` com as ABCs `Hashable` e `Iterable`. Quando
`isinstance(obj, Hashable)` devolve `True`, significa apenas que a classe de `obj` implementa
ou herda `+__hash__+`. Mas se `obj` é uma tupla contendo itens _unhashable_,
então `obj` não é _hashable_, apesar do resultado positivo da checagem com
`isinstance`.
O revisor técnico Jürgen Gmach mostrou que a tipagem pato oferece
o modo mais preciso de determinar se uma instância é _hashable_: chamar `hash(obj)`.
Essa chamada vai levantar um `TypeError` se `obj` não for _hashable_.

Por outro lado, mesmo quando `isinstance(obj, Iterable)` retorna `False`,
o Python ainda pode ser capaz de iterar sobre `obj` usando `+__getitem__+`
com índices baseados em 0, como vimos em https://fpy.li/1[«Capítulo 1»] (vol.1) e
na <<python_digs_seq_sec>>. A documentação de
https://fpy.li/6q[`collections.abc.Iterable`]
afirma:

[quote]
____
A única maneira confiável de determinar se um objeto é iterável é chamar `iter(obj)`.
____

****

Após vermos algumas das ABCs existentes, vamos praticar tipagem ganso
implementando uma ABC do zero, e a colocando em uso. O objetivo aqui não é
encorajar todo mundo a criar ABCs a torto e a direito, mas mostrar como ler o
código-fonte das ABCs encontradas na biblioteca padrão e em outros
pacotes.((("", startref="GTstlib13")))((("", startref="ABCstndlib13")))

<<<
[[defining_using_abc_sec]]
==== Definindo e usando uma ABC

Escrevi esta((("goose typing", "defining and using ABCs", id="GTdef13")))((("ABCs (abstract base classes)",
"defining and using ABCs", id="ABCdef13")))
advertência no capítulo "Interfaces" da primeira edição deste livro:

[quote]
____

ABCs, como os descritores e as metaclasses, são ferramentas para criar
frameworks. Assim, só uma pequena minoria dos desenvolvedores Python tem
a oportunidade de criar ABCs sem impor limitações pouco razoáveis e
trabalho desnecessário a seus colegas programadores.
____

Agora ABCs têm mais casos de uso potenciais, em dicas de tipo para permitir
tipagem estática. Como discutido na https://fpy.li/8r[«Seção 8.5.7»] (vol.1), usar ABCs em vez de
tipos concretos em dicas de tipos de argumentos de função dá mais flexibilidade a
quem chama a função.

Para justificar a criação de uma ABC, precisamos pensar em um contexto para
usá-la como um ponto de extensão em um framework. Então aqui está nosso
contexto: imagine que você precisa exibir publicidade em um site ou em uma app
de celular, em ordem aleatória, mas sem repetir um anúncio antes que o
inventário completo de anúncios tenha sido exibido. Agora vamos presumir que
estamos desenvolvendo um gerenciador de publicidade. Um dos
requisitos é permitir o uso de classes de escolha aleatória não repetida
fornecidas pelo usuário.footnote:[Talvez o cliente precise auditar o
randomizador ou a agência queira fornecer um randomizador "viciado". Nunca se
sabe...] Para deixar claro aos usuários do framework de anúncios o que se espera
de um componente de "escolha aleatória não repetida", vamos definir uma ABC.

Na bibliografia sobre estruturas de dados, _stack_ (pilha) e _queue_ (fila) descrevem
interfaces abstratas em termos dos arranjos físicos dos objetos. Vamos seguir o
mesmo caminho e usar uma metáfora do mundo real para batizar nossa ABC: gaiolas
de bingo e sorteadores de loteria são máquinas projetadas para escolher
aleatoriamente itens de um conjunto finito, sem repetir, até o conjunto ser
esgotado. Vamos chamar a ABC de `Tombola`, seguindo o nome italiano do bingo, e
do recipiente giratório que mistura os números.

<<<
A ABC `Tombola` tem quatro métodos.
Os dois métodos abstratos são:

`.load(…)`:: Coloca itens na coleção.
`.pick()`:: Remove e devolve um item aleatório da coleção.

Os métodos concretos são:

`.loaded()`:: Devolve `True` se existir pelo menos um item na coleção.
`.inspect()`:: Devolve uma `tuple` construída a partir dos itens atualmente na coleção,
sem modificar o conteúdo (a ordem interna não é preservada).

A <<tombola_uml>> mostra a ABC `Tombola` e três implementações concretas.
Vale notar que _registered_ (registrada) e _virtual subclass_ (subclasse virtual)
não são termos da UML padrão, mas representam uma relação de classe específica de Python,
como veremos na <<virtual_subclass_sec>>.

[role="width-80"]
[[tombola_uml]]
.Diagrama UML para uma ABC e três subclasses. O nome da ABC `Tombola` e de seus métodos abstratos estão escritos em _itálico_, segundo as convenções da UML. A seta tracejada indica que `TomboList` implementa a interface `Tombola`, e também está registrada como _subclasse virtual_ daquela ABC.
image::../images/flpy_1305.png[align="center",pdfwidth=9cm]

<<<
O <<ex_tombola_abc>> mostra a definição da ABC `Tombola`.

[[ex_tombola_abc]]
.tombola.py: ABC com dois métodos abstratos e dois métodos concretos.
====
[source, python]
----
include::../code/13-protocol-abc/tombola.py[tags=TOMBOLA_ABC]
----
====
<1> Para definir uma ABC, crie uma subclasse de `abc.ABC`.

<2> Um método abstrato é marcado com o decorador `@abstractmethod`, e muitas
vezes seu corpo é vazio, exceto por uma docstring.footnote:[Antes das ABCs
existirem, métodos abstratos levantariam um `NotImplementedError` para sinalizar
que as subclasses eram responsáveis por suas implementações. No Smalltalk-80, o
corpo dos métodos abstratos invocaria `subclassResponsibility`, um método
herdado de `object` que gerava um erro com a mensagem "Minha subclasse deveria
ter sobrescrito uma de minhas mensagens."]

<3> A docstring instrui os implementadores a levantarem `LookupError` se não
existirem itens para escolher.

<4> Uma ABC pode incluir métodos concretos.

<5> Métodos concretos em uma ABC devem depender apenas da interface definida
pela ABC (isto é, outros métodos concretos ou abstratos ou propriedades da ABC).

<6> Não sabemos como as subclasses concretas vão armazenar os itens, mas podemos
escrever o resultado de `inspect` esvaziando a `Tombola` com chamadas sucessivas
a `.pick()`...

<7> ...e então usando `.load(…)` para colocar tudo de volta.


[role="pagebreak-before less_space"]
[TIP]
====
Um método abstrato pode ter uma implementação.
Mas mesmo que tenha, as subclasses ainda são obrigadas a sobrescrevê-lo,
mas poderão invocar o método abstrato com `super()`,
acrescentando funcionalidade em vez de implementar do zero.
Veja os detalhes do uso de `@abstractmethod` na
https://fpy.li/6r[documentação do módulo `abc`].
====

O código do método `.inspect()` é ridículo mas funciona.
Ele serve para mostrar que podemos usar `.pick()` e `.load(…)`
para inspecionar o que está dentro de `Tombola`, puxando
e devolvendo os itens—sem saber como eles são realmente armazenados. O
objetivo deste exemplo é ressaltar que não há problema em oferecer métodos
concretos em ABCs, desde que eles dependam apenas de outros métodos na
interface. Conhecendo suas estruturas de dados internas, as subclasses concretas
de `Tombola` podem sobrescrever `.inspect()` com uma implementação mais
eficiente, mas não são obrigadas a fazer isso.

O método `.loaded()` no <<ex_tombola_abc>> tem uma linha, mas é custoso:
ele chama `.inspect()` para criar a `tuple` apenas para aplicar `bool()` nela.
Funciona, mas subclasses concretas podem fazer bem melhor, como veremos.

Observe que nossa implementação tortuosa de `.inspect()` exige a captura de um
`LookupError` lançado por `self.pick()`. O fato de `self.pick()` poder disparar
um `LookupError` também é parte de sua interface, mas não há como tornar isso
explícito em Python, exceto na documentação (veja a docstring para o método
abstrato `pick` no <<ex_tombola_abc>>).

<<<
Escolhi a exceção `LookupError` por sua posição na hierarquia de exceções em
relação a `IndexError` e `KeyError`, as exceções mais comuns de ocorrerem nas
estruturas de dados usadas para implementar uma `Tombola` concreta. Dessa forma,
as implementações podem lançar `LookupError`, `IndexError`, `KeyError`, ou uma
subclasse customizada de `LookupError` para atender à interface. Veja o
<<exc_tree_part>>.


[[exc_tree_part]]
.Parte da hierarquia de classes de exceção.
====
----
BaseException
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ImportError
      ├── LookupError       <1>
      │    ├── IndexError  <2>
      │    └── KeyError    <3>
      ├── MemoryError
      ... etc.
----
====
<1> `LookupError` é a exceção que tratamos em `Tombola.inspect`.
<2> `IndexError` é a subclasse de `LookupError` gerada quando tentamos
acessar um item em uma sequência com um índice além da última posição.
<3> `KeyError` ocorre quando usamos uma chave inexistente para acessar um item em um
mapeamento (`dict` etc.).

Agora temos nossa própria ABC `Tombola`. Para observar a checagem da interface
feita por uma ABC, vamos tentar enganar `Tombola` com uma implementação
defeituosa no <<fake_tombola_ex>>.

[[fake_tombola_ex]]
.Uma `Tombola` falsa não passa despercebida
====
[source, python]
----
>>> from tombola import Tombola
>>> class Fake(Tombola):  # <1>
...     def pick(self):
...         return 13
...
>>> Fake  # <2>
<class '__main__.Fake'>
>>> f = Fake()  # <3>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Fake with abstract method load
----
====
<1> Declara `Fake` como subclasse de `Tombola`.
<2> A classe é criada, nenhum erro até agora.
<3> Um `TypeError` é sinalizado quando tentamos instanciar `Fake`.
A mensagem é bastante clara: `Fake` é considerada abstrata porque deixou de implementar `load`, um dos métodos abstratos declarados na ABC `Tombola`.

Então definimos nossa primeira ABC, e a usamos para validar uma classe.
Logo vamos criar uma subclasse de `Tombola`, mas primeiro temos que falar sobre algumas regras para a programação de ABCs.((("", startref="ABCdef13")))((("", startref="GTdef13")))

[[abc_syntax_section]]
==== Detalhes da sintaxe das ABCs

A((("goose typing", "ABC syntax details")))((("ABCs (abstract base classes)", "ABC syntax details"))) forma padrão de declarar uma ABC é criar uma subclasse de `abc.ABC` ou de alguma outra ABC.

Além da classe base ABC e do decorador `@abstractmethod`, o módulo `abc` define
os decoradores `@abstractclassmethod`, `@abstractstaticmethod`, e `@abstractproperty`.
Entretanto, os três últimos foram descontinuados no Python 3.3,
quando se tornou possível empilhar decoradores sobre `@abstractmethod`, tornando os outros redundantes.

<<<
Por exemplo, a maneira preferível de declarar um método de classe abstrato é:

[source, python]
----
class MyABC(abc.ABC):
    @classmethod
    @abc.abstractmethod
    def an_abstract_classmethod(cls, ...):
        pass
----

[WARNING]
====
A ordem dos decoradores de função empilhados importa, e no caso de `@abstractmethod`, a documentação é explícita:

[quote]
____
Quando `@abstractmethod` é aplicado em combinação com outros descritores de
método, ele deve ser aplicado como o decorador mais interno...footnote:[O
verbete https://fpy.li/6s[`@abc.abstractmethod`] na
https://docs.python.org/pt-br/dev/library/abc.html[documentação do módulo
`abc`].]
____

Em outras palavras, nenhum outro decorador pode aparecer entre `@abstractmethod` e a instrução `def`.
====

Agora que abordamos essas questões de sintaxe das ABCs, vamos colocar `Tombola`
em uso, implementando duas subclasses concretas.

==== Criando uma subclasse de `Tombola`

Dada((("goose typing", "subclassing ABCs", id="GTsubclass13")))((("ABCs (abstract base classes)",
"subclassing ABCs", id="ABCsubclass13")))((("inheritance and subclassing", "subclassing ABCs",
id="IASsubclass13"))) a ABC `Tombola`, vamos desenvolver duas subclasses
concretas que satisfazem a interface. Essas classes estão ilustradas na
<<tombola_uml>>, junto com a subclasse virtual que será discutida na seção
seguinte.

A classe `BingoCage` no <<ex_tombola_bingo>> é uma variação do
https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1) usando um randomizador melhor. `BingoCage` implementa os
métodos abstratos obrigatórios `load` e `pick`.

[[ex_tombola_bingo]]
.bingo.py: `BingoCage` é uma subclasse concreta de `Tombola`
====
[source, python]
----
include::../code/13-protocol-abc/bingo.py[tags=TOMBOLA_BINGO]
----
====
<1> Essa classe `BingoCage` estende `Tombola` explicitamente.

<2> `random.SystemRandom()`
implementa a API `random` sobre a função `os.urandom()`, que fornece bytes
aleatórios "adequados para uso em criptografia", segundo https://fpy.li/6t[a
documentação do módulo `os`].

<3> Delega o carregamento inicial para o método `.load()`

<4> Em vez da função `random.shuffle()` normal, usamos o método `.shuffle()` de
nossa instância de `SystemRandom`.

<5> `pick` é implementado como no https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1).

<6> `+__call__+` também é do https://fpy.li/8y[«Exemplo 8 do Capítulo 7»] (vol.1). Ele não é necessário para
satisfazer a interface de `Tombola`, mas é comum que subclasses tenham mais
métodos.

`BingoCage` herda o custoso método `loaded` e o tolo `inspect` de `Tombola`.
Ambos poderiam ser sobrescritos com métodos de uma linha mais rápidos, como no
<<ex_lotto>>. A questão é: podemos decidir apenas herdar os
métodos concretos de uma ABC. Os métodos herdados de `Tombola`
não são tão rápidos quanto poderiam ser na `BingoCage` concreta,
mas fornecem os resultados esperados para qualquer subclasse de
`Tombola` que implemente `pick` e `load` corretamente.

O <<ex_lotto>> mostra uma implementação muito diferente, mas também válida,
da interface de `Tombola`. Em vez de misturar as "bolas" e tirar a última,
`LottoBlower` tira um item de uma posição aleatória..

[[ex_lotto]]
.lotto.py: `LottoBlower` é uma subclasse concreta que sobrecarrega os métodos `inspect` e `loaded` de `Tombola`
====
[source, python]
----
include::../code/13-protocol-abc/lotto.py[tags=LOTTERY_BLOWER]
----
====

<1> O construtor aceita qualquer iterável: o argumento é usado para construir
uma lista.
<2> A função `random.randrange(…)` levanta um `ValueError` se a faixa de valores
estiver vazia, então capturamos esse erro e trocamos por `LookupError`, para ser
compatível com `Tombola`.
<3> Caso contrário, o item selecionado aleatoriamente é retirado de
`self._balls`.
<4> Sobrescreve `loaded` para evitar a chamada a `inspect` (como `Tombola.loaded`
faz no <<ex_tombola_abc>>). Podemos fazer isso mais rápido acessando
`self._balls` diretamente—não precisamos criar uma nova tupla.
<5> Sobrescreve `inspect` com uma linha de código.

O <<ex_lotto>> ilustra um idioma que vale a pena mencionar:
em `+__init__+`, `self._balls` armazena `list(iterable)`, e não apenas uma
referência para `iterable` (isto é, nós não apenas atribuímos
`self._balls = iterable`, apelidando o argumento).
Como mencionado na <<defensive_duck_prog_sec>>, isso torna a `LottoBlower`
flexível, pois o argumento `iterable` pode ser de qualquer tipo iterável.
Ao mesmo tempo, garantimos que os itens serão armazenados em uma `list`,
de onde podemos retirar itens com `.pop()`.
E mesmo quando recebemos uma lista no argumento `iterable`,
`list(iterable)` produz uma cópia, o que é uma boa prática,
considerando que vamos remover itens da lista,
e o cliente pode não estar esperando
que a lista passada seja modificada.footnote:[A https://fpy.li/8z[«Seção 6.5.2»] (vol.1)
trata do problema de apelidamento que acabamos de evitar aqui.]

Chegamos agora à característica dinâmica da tipagem ganso:
declarar subclasses virtuais com o método `register`.
((("", startref="GTsubclass13")))((("", startref="ABCsubclass13")))((("", startref="IASsubclass13")))

[[virtual_subclass_sec]]
==== Uma subclasse virtual de uma ABC

Uma((("goose typing", "virtual subclasses of ABCs", id="GTvsub13")))((("ABCs
(abstract base classes)", "virtual subclasses of ABCs",
id="ABCvirt13")))((("virtual subclasses", id="virtsub13")))((("inheritance and subclassing",
"virtual subclasses of ABCs", id="IASvirtualabc13")))
característica essencial da tipagem ganso—e uma razão pela qual ela merece um
nome de ave aquática—é a habilidade de registrar uma classe como uma _subclasse
virtual_ de uma ABC, mesmo se a classe não herde da ABC. Ao fazer isso,
prometemos que a classe implementa fielmente a interface definida na ABC—e
Python vai acreditar em nós sem checar. Se mentirmos, vamos enfrentar
exceções de tempo de execução.

Isso é feito invocando um método de classe `register` da ABC.
A subclasse registrada será reconhecida por `issubclass`,
mas herdará qualquer método ou atributo da ABC.

[WARNING]
====
Subclasses virtuais não herdam da ABC na qual se registram,
e sua conformidade com a interface da ABC nunca é checada,
nem quando são instanciadas.
E mais, neste momento checadores de tipos estáticos não conseguem tratar subclasses virtuais.
Mais detalhes em https://fpy.li/13-22[Mypy issue 2922—ABCMeta.register support].
====

O método `register` é normalmente invocado como uma função comum
(veja a <<register_usage>>), mas também pode ser usado como decorador.
No ((("UML class diagrams", "TomboList"))) <<ex_tombolist>>,
usamos a sintaxe de decorador e implementamos `TomboList`,
uma subclasse virtual de `Tombola`, ilustrada na <<tombolist_uml>>.

[role="width-50"]
[[tombolist_uml]]
.Diagrama de classe UML para `TomboList`, subclasse real de `list` e subclasse virtual de `Tombola`.
image::../images/flpy_1307.png[align="center",pdfwidth=5.5cm]

[[ex_tombolist]]
.tombolist.py: a classe `TomboList` é uma subclasse virtual de `Tombola`
====
[source, python]
----
include::../code/13-protocol-abc/tombolist.py[]
----
====
<1> `TomboList` é registrada como subclasse virtual de `Tombola`.

<2> `TomboList` estende `list`.

<3> `TomboList` herda seu comportamento booleano de `list`, devolvendo
`True` se a lista não estiver vazia.

<4> Nosso `pick` invoca `self.pop`, herdado de `list`, passando um índice
aleatório para um item.

<5> `TomboList.load` é o mesmo que `list.extend`.

<6> `loaded` delega para `bool`.footnote:[O truque usado com `load()` não
funciona com `loaded()`, pois o tipo `list` não implementa `+__bool__+`, o
método que eu teria de vincular a `loaded`. O `bool()` nativo não precisa de
`+__bool__+` para funcionar, porque pode também usar `+__len__+`. Veja
https://fpy.li/2g[4.1. Teste do Valor Verdade] na documentação de Python.]

<7> É sempre possível invocar `register` dessa forma, e é útil fazer assim
quando você precisa registrar uma classe cujo código você não mantém,
mas que implementa a interface.

Note que, por causa do registro, as funções `issubclass` e `isinstance` agem
como se `TomboList` fosse uma subclasse de `Tombola`:

[source, python]
----
>>> from tombola import Tombola
>>> from tombolist import TomboList
>>> issubclass(TomboList, Tombola)
True
>>> t = TomboList(range(100))
>>> isinstance(t, Tombola)
True
----

Entretanto, a herança é guiada por um atributo de classe especial chamado
`+__mro__+`&#x2014;sigla de _Method Resolution Order_
(Ordem de Resolução de Métodos). Esse atributo lista a
classe e suas superclasses na ordem que Python segue para procurar
métodos.footnote:[Há toda uma explicação
sobre o atributo de classe `+__mro__+` na <<mult_inherit_mro_sec>>. Por agora, essas
informações básicas são o suficiente.] Se você inspecionar o `+__mro__+` de
`TomboList`, verá que ele lista apenas as superclasses "reais"—`list` e
`object`:

[source, python]
----
>>> TomboList.__mro__
(<class 'tombolist.TomboList'>, <class 'list'>, <class 'object'>)
----

`Tombola` não está em `+TomboList.__mro__+`, então `TomboList` não herda nenhum método de `Tombola`.

Isso conclui nosso estudo de caso da ABC `Tombola`.
Na próxima seção, vamos falar sobre como a função `register` das ABCs é usada na vida real.((("", startref="GTvsub13")))((("", startref="ABCvirt13")))((("", startref="virtsub13")))((("", startref="IASvirtualabc13")))

[[register_usage]]
==== O uso de `register` na prática

No <<ex_tombolist>>, usamos((("goose typing", "usage of register")))((("ABCs (abstract base classes)",
"usage of register"))) `Tombola.register` como um
decorador de classe. Antes de Python 3.3, `register` não podia ser usado dessa
forma—ele tinha que ser invocado como uma função normal após a definição da
classe, como sugerido pelo comentário no final do <<ex_tombolist>>. Mas `register`
continua sendo usado como uma função para registrar classes definidas em
outro lugar. Por exemplo, no https://fpy.li/13-24[código-fonte] do módulo
`collections.abc`, os tipos nativos `tuple`, `str`, `range`, e `memoryview` são
registrados como subclasses virtuais de `Sequence` assim:

[source, python]
----
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)
----

Vários outros tipo nativos estão registrados com as ABCs em _&#x5f;collections_abc.py_.
Esses registros ocorrem apenas quando aquele módulo é importado,
o que não causa problema, pois você terá mesmo que importar o módulo para obter as ABCs.
Por exemplo, você precisa importar `MutableMapping` de `collections.abc` para checar algo como `isinstance(my_dict, MutableMapping)`.

Criar uma subclasse de uma ABC ou se registrar com uma ABC são duas maneiras explícitas de fazer nossas classes passarem checagens com `issubclass` e `isinstance` (que também se apoia em `issubclass`).
Mas algumas ABCs também suportam tipagem estrutural.
A próxima seção explica isso.

[[subclasshook_sec]]
==== Tipagem estrutural com ABCs

As ABCs((("goose typing", "structural typing with ABCs", id="GTstruct13")))((("ABCs (abstract base classes)",
"structural typing with", id="ABCstruct13")))((("structural typing", id="strtype13")))
são usadas principalmente com tipagem nominal.

Quando uma classe `Sub` herda explicitamente de `UmaABC`, ou está registrada com
`UmaABC`, o nome de `UmaABC` fica ligado ao da classe `Sub`—é assim que, durante
a execução, `issubclass(UmaABC, Sub)` devolve `True`.

Em contraste, a tipagem estrutural diz respeito a olhar para a estrutura da
interface pública de um objeto para determinar seu tipo: um objeto é
_consistente-com_ um tipo se implementa os métodos definidos no tipo.footnote:[O
conceito de consistência de tipo é explicado na https://fpy.li/83[«Seção 8.5.1.1»] (vol.1).] A
tipagem pato estática e a tipagem pato dinâmica são duas abordagens à tipagem
estrutural.

Acontece que algumas ABCs também suportam tipagem estrutural.
Em seu ensaio _<<waterfowl_essay>>_, Alex mostra que uma classe pode ser
reconhecida como subclasse de uma ABC mesmo sem registro. Aqui está novamente o
exemplo dele, com um teste adicional usando `issubclass`:

[source, python]
----
>>> class Struggle:
...     def __len__(self): return 23
...
>>> from collections import abc
>>> isinstance(Struggle(), abc.Sized)
True
>>> issubclass(Struggle, abc.Sized)
True
----

A classe `Struggle` é considerada uma subclasse de `abc.Sized` pela função
`issubclass` (e, consequentemente, também por `isinstance`) porque `abc.Sized`
implementa um método de classe especial chamado `+__subclasshook__+`.

O `+__subclasshook__+` de `Sized` verifica se o argumento classe tem um atributo
chamado `+__len__+`. Se tiver, então a classe é considerada uma subclasse
virtual de `Sized`. Veja o <<sized_source_code>>.

<<<
[[sized_source_code]]
.Definição de `Sized` em https://fpy.li/13-25[Lib/_collections_abc.py]
====
[source, python]
----
class Sized(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if any("__len__" in B.__dict__ for B in C.__mro__):  # <1>
                return True  # <2>
        return NotImplemented  # <3>
----
====
<1> Se há um atributo chamado `+__len__+` no `+__dict__+` de qualquer classe
listada em `+C.__mro__+` (isto é, `C` e suas superclasses)...
<2> ...devolve `True`, sinalizando que `C` é uma subclasse virtual de `Sized`.
<3> Caso contrário devolve `NotImplemented`, para permitir que a checagem de subclasse continue.

[NOTE]
====
Se você tiver interesse nos detalhes da checagem de subclasse,
estude o código-fonte do método `+ABCMeta.__subclasscheck__+` no Python 3.6:
https://fpy.li/13-26[_Lib/abc.py_].
Saiba que é complicado: lá há muitos ifs e duas chamadas recursivas.
No Python 3.7, Ivan Levkivskyi e Inada Naoki reescreveram em C
a maior parte da lógica do módulo `abc`, para melhorar o desempenho.
Veja https://fpy.li/13-27[Python issue #31333].
A implementação atual de `+ABCMeta.__subclasscheck__+` simplesmente chama `_abc_subclasscheck`.
O código-fonte em C relevante está em https://fpy.li/13-28[_cpython/Modules/&#x5f;abc.c#L605_].
====

É assim que `+__subclasshook__+` permite às ABCs suportarem a tipagem
estrutural. Você pode formalizar uma interface com uma ABC, pode fazer checagens
`isinstance` com aquela ABC, e ainda ter uma classe sem qualquer relação de
herança aprovada por uma checagem de `issubclass` porque ela implementa um certo
método (ou porque ela faz o necessário para convencer o
`+__subclasshook__+` da ABC aprová-la como subclasse virtual).

É uma boa ideia implementar `+__subclasshook__+` em nossas próprias ABCs?
Provavelmente não. Todas as implementações de `+__subclasshook__+` que vi no
código-fonte de Python estão em ABCs como `Sized`, que declara apenas um método
especial, e elas simplesmente verificam a presença do nome daquele método
especial. Dado seu status "especial", é quase certeza que qualquer método
chamado `+__len__+` faz o que se espera. Mas mesmo no reino dos métodos
especiais e ABCs fundamentais, pode ser arriscado fazer tais suposições. Por
exemplo, mapeamentos implementam `+__len__+`, `+__getitem__+`, e `+__iter__+`,
mas corretamente não são considerados subtipos de `Sequence`, pois não podemos
recuperar itens usando índices a partir de zero ou obter fatias. Por isso a classe
https://fpy.li/13-29[`abc.Sequence`] não implementa `+__subclasshook__+`.

Para ABCs que você ou eu podemos escrever, um `+__subclasshook__+` seria ainda
menos confiável. Não estou preparado para acreditar que qualquer classe chamada
`Spam` que implemente ou herde `load`, `pick`, `inspect`, e `loaded` vai
necessariamente se comportar como uma `Tombola`. É melhor deixar o programador
afirmar isso, fazendo de `Spam` uma subclasse de `Tombola`, ou registrando a
classe com `Tombola.register(Spam)`. Claro, o seu `+__subclasshook__+` poderia
também verificar assinaturas de métodos e outras características, mas não creio
que valha o esforço.((("", startref="GTstruct13")))((("",
startref="ABCstruct13")))((("", startref="strtype13")))


[[static_protocols_sec]]
=== Protocolos estáticos

[NOTE]
====

Vimos algo sobre protocolos estáticos((("protocols", "static protocols",
id="Pstatic13"))) na https://fpy.li/8m[«Seção 8.5.10»] (vol.1). Pensei em deixar toda a discussão
sobre protocolos para este capítulo, mas decidi que a apresentação inicial de
dicas de tipo em funções precisava incluir protocolos, pois a tipagem pato é uma
parte essencial de Python, e a checagem de tipos estática sem protocolos não
consegue lidar muito bem com muitas APIs pythônicas.

====

Vamos encerrar este capítulo ilustrando os protocolos estáticos com dois
exemplos simples, e uma discussão sobre as ABCs numéricas e protocolos.
Começaremos mostrando como um protocolo estático possibilita anotar e checar
tipos na função `double()`, que vimos antes na https://fpy.li/8s[«Seção 8.4»] (vol.1).

[[typed_double_sec]]
==== A função `double` tipada

Quando((("static protocols", "typed double function",
id="SPtypeddouble13")))((("typed double function", id="typdblf13")))((("double()
function", id="double13")))((("functions", "double() function"))) apresento
Python para programadores mais habituados à tipagem estática, um de meus
exemplos é esta função `double` capaz de lidar com uma variedade de tipos:

[source, python]
----
>>> def double(x):
...     return x * 2
...
>>> double(1.5)
3.0
>>> double('A')
'AA'
>>> double([10, 20, 30])
[10, 20, 30, 10, 20, 30]
>>> from fractions import Fraction
>>> double(Fraction(2, 5))
Fraction(4, 5)
----

Antes da introdução dos protocolos estáticos, não havia uma forma prática de
acrescentar dicas de tipo a `double` sem limitar seus usos
possíveis.footnote:[Concordo que `double()` não é muito útil, exceto como um exemplo.
Mas a biblioteca padrão de Python tem muitas funções que não poderiam ser
anotadas de modo apropriado antes dos protocolos estáticos serem adicionados, no
Python 3.8. Ajudei a corrigir alguns bugs no _typeshed_ acrescentando dicas de
tipo com protocolos. Por exemplo, no _pull request_ onde consertei
https://fpy.li/shed4051[_Should Mypy warn about potential invalid arguments to `+max+`?_]
(Deveria o Mypy avisar sobre argumentos potencialmente inválidos passados a `max`?)
defini um protocolo `_SupportsLessThan`, que usei para melhorar
as anotações de `max`, `min`, `sorted`, e `list.sort`.]


Graças à tipagem pato, `double` funciona mesmo com tipos inventados depois, tal
como a classe `Vector` aprimorada que veremos na <<overloading_mul_sec>>:

[source, python]
----
>>> from vector_v7 import Vector
>>> double(Vector([11.0, 12.0, 13.0]))
Vector([22.0, 24.0, 26.0])
----

A implementação inicial de dicas de tipo no Python era um sistema de tipos
nominal: o nome de um tipo em uma anotação tinha que corresponder ao nome do
tipo do argumento real—ou com o nome de uma de suas superclasses. Como é
impossível nomear todos os tipos que implementam um protocolo (suportando as
operações requeridas), a tipagem pato não podia ser descrita por dicas de tipo
antes do Python 3.8.

Agora, com `typing.Protocol`, podemos informar ao Mypy que `double` recebe um
argumento `x` que suporta `x * 2`.
O <<repeatable_protocol_ex>> mostra como.

[[repeatable_protocol_ex]]
._double_protocol.py_: a definição de `double` usando um `Protocol`.
====
[source, python]
----
include::../code/13-protocol-abc/double/double_protocol.py[]
----
====
<1> Vamos usar esse `T` na assinatura de `+__mul__+`.
<2> `+__mul__+` é a essência do protocolo `Repeatable`.
O parâmetro `self` normalmente não é anotado—presume-se que seu tipo seja a classe.
Aqui usamos `T` para assegurar que o tipo do resultado é o mesmo tipo de `self`.
Além disso observe que decidi limitar `repeat_count` ao tipo `int` neste protocolo.
<3> A variável de tipo `RT` é vinculada pelo protocolo `Repeatable`:
o checador de tipos vai exigir que o tipo efetivo implemente `Repeatable`.
<4> Agora o checador de tipos pode checar que o parâmetro `x` é um objeto que
pode ser multiplicado por um inteiro, e que o valor retornado tem o mesmo tipo
que `x`.

Este exemplo mostra por que o subtítulo da https://fpy.li/pep544[PEP 544] é
_static duck typing_ (tipagem pato estática). O tipo nominal do argumento
concreto `x` passado a `double`, é irrelevante, desde que grasne—ou seja,
desde que implemente `+__mul__+`.((("", startref="SPtypeddouble13")))((("",
startref="typdblf13")))((("", startref="double13")))


[[runtime_checkable_proto_sec]]
==== Protocolos estáticos checados durante a execução

No ((("static protocols", "runtime checkable", id="SPruntime13"))) Mapa de
Tipagem (<<type_systems_described>>), `typing.Protocol` aparece na área de
checagem estática—a metade inferior do diagrama. Entretanto, ao definir uma
subclasse de `typing.Protocol`, você pode usar o decorador `@runtime_checkable`
para fazer aquele protocolo aceitar checagens com `isinstance/issubclass`
durante a execução. Isso funciona porque `typing.Protocol` é uma ABC, assim
suporta o `+__subclasshook__+` que vimos na <<subclasshook_sec>>.

No Python 3.9, o módulo `typing` inclui sete protocolos prontos para uso que são
verificáveis durante a execução. Aqui estão dois deles, citados diretamente da
https://fpy.li/gv[documentação de `typing`]:

`class typing.SupportsComplex`::
    Um ABC com um método abstrato __complex__.

`class typing.SupportsFloat`::
    Um ABC com um método abstrato __float__.

Estes((("numeric types", "checking for convertibility"))) protocolos foram
projetados para checar a "convertibilidade" de tipos numéricos: se um objeto `n`
implementa `+__complex__+`, então deveria ser possível obter um `complex`
invocando `complex(n)`, pois o método especial `+__complex__+` existe para
suportar a função embutida `complex()`.

<<supportscomplex_ex>> mostra o
https://fpy.li/13-31[código-fonte]
do protocolo `typing.SupportsComplex`.

[[supportscomplex_ex]]
.código-fonte do protocolo `typing.SupportsComplex`
====
[source, python]
----
@runtime_checkable
class SupportsComplex(Protocol):
    """An ABC with one abstract method __complex__."""
    __slots__ = ()

    @abstractmethod
    def __complex__(self) -> complex:
        pass
----
====

A chave é o método abstrato `+__complex__+`.footnote:[O atributo `+__slots__+` é
irrelevante para nossa discussão aqui—é uma otimização sobre a qual falamos na
<<slots_sec>>.] Durante a checagem de tipo estática, um objeto será considerado
_consistente-com_ o protocolo `SupportsComplex` se implementar um método
`+__complex__+` que recebe apenas `self` e retorna um `complex`.

Graças ao decorador de classe `@runtime_checkable`, aplicado a
`SupportsComplex`, aquele protocolo também pode ser utilizado em checagens com
`isinstance` no <<repeatable_protocol_demo_ex>>.

[[repeatable_protocol_demo_ex]]
.Usando `SupportsComplex` durante a execução
====
[source, python]
----
>>> from typing import SupportsComplex
>>> import numpy as np
>>> c64 = np.complex64(3+4j)  # <1>
>>> isinstance(c64, complex)   # <2>
False
>>> isinstance(c64, SupportsComplex)  # <3>
True
>>> c = complex(c64)  # <4>
>>> c
(3+4j)
>>> isinstance(c, SupportsComplex) # <5>
False
>>> complex(c)
(3+4j)
----
====
<1> `complex64` é um dos cinco tipos de números complexos fornecidos pelo NumPy.
<2> Nenhum dos tipos complexos da NumPy é subclasse do `complex` embutido.
<3> Mas os tipos complexos de NumPy implementam `+__complex__+`, então cumprem o protocolo `SupportsComplex`.
<4> Portanto, você pode criar objetos `complex` a partir deles.
<5> O tipo `complex` embutido não implementa `+__complex__+`,
mas `complex(c)` funciona sem problemas se `c` for uma instância de
`complex`.

Como consequência deste último ponto, se você quiser testar se um objeto `c` é
um `complex` ou `SupportsComplex`, você deve passar uma tupla de tipos como
segundo argumento para `isinstance`, assim:

[source, python]
----
isinstance(c, (complex, SupportsComplex))
----

Uma outra alternativa seria usar a ABC `Complex`, definida no módulo `numbers`.
O tipo embutido `complex` e os tipos `complex64` e `complex128` da NumPy são
todos registrados como subclasses virtuais de `numbers.Complex`, então
o código a seguir funciona:

<<<

[source, python]
----
>>> import numbers
>>> isinstance(c, numbers.Complex)
True
>>> isinstance(c64, numbers.Complex)
True
----

Na primeira edição de _Python Fluente_ eu recomendava o uso das ABCs de
`numbers`, mas agora esse não é mais um bom conselho, pois aquelas ABCs não são
reconhecidas pelos checadores de tipos estáticos, como veremos na
<<numbers_abc_proto_sec>>.

Nesta seção eu queria demonstrar que um protocolo verificável durante a execução
funciona com `isinstance`, mas na verdade esse exemplo não é um caso de uso
particularmente bom de `isinstance`, como a barra lateral
<<duck_typing_friend_box>> explica.

[TIP]
====

Se você estiver usando o Mypy, há uma vantagem nas checagens explícitas com
`isinstance`: quando você escreve uma instrução `if` onde a condição é
`isinstance(n, MyType)`, então o Mypy infere que dentro do bloco `if`, o tipo do
objeto `n` é _consistente-com_ `MyType`.

====

[[duck_typing_friend_box]]
.Confie no pato
****

Durante((("duck typing"))) a execução, muitas vezes a tipagem pato é a melhor
abordagem para checagem de tipos: em vez de chamar `isinstance` ou `hasattr`,
apenas tente realizar as operações que você precisa com o objeto, e trate as
exceções conforme necessário. Segue um exemplo concreto.

Continuando a discussão anterior,
dado um objeto `n` que preciso usar como número complexo,
essa seria uma abordagem:

[source, python]
----
if isinstance(n, (complex, SupportsComplex)):
    # código que precisa converter `n` para `complex`
else:
    raise TypeError('n must be convertible to complex')
----

<<<
A abordagem da tipagem ganso seria usar a ABC `numbers.Complex`:

[source, python]
----
if isinstance(n, numbers.Complex):
    # código que assume que `n` é instância de `Complex`
else:
    raise TypeError('n must be an instance of Complex')
----

Mas eu prefiro aproveitar a tipagem pato e pedir perdão
em vez de permissão (Princípio de Hopper):

[source, python]
----
try:
    c = complex(n)
except TypeError as exc:
    raise TypeError('n must be convertible to complex') from exc
----

Mas se o único tratamento que você vai dar para o `TypeError`
é levantar `TypeError`, eu escreveria só isso:

[source, python]
----
c = complex(n)
----

Neste último caso, se `n` não é de um tipo aceitável,
o Python levantará uma exceção com uma mensagem bem clara.
Por exemplo, se `n` é uma `tuple`, esse é o resultado:

[source]
----
TypeError: complex() first argument must be a string or a number, not 'tuple'
----

Em português: "O primeiro argumento de `complex()` deve ser uma string ou um número, não 'tuple'".

A abordagem da tipagem pato é simples e correta neste caso.
****

Agora que vimos como usar protocolos estáticos durante a execução com tipos
pré-existentes como `complex` e `numpy.complex64`, precisamos discutir as
limitações de protocolos verificáveis durante a execução.((("",
startref="SPruntime13")))

[[protocol_type_hints_ignored]]
==== Limitações das checagens de protocolo durante a execução

Vimos((("static protocols", "limitations of runtime protocol checks"))) que
dicas de tipo são geralmente ignoradas durante a execução, e isso também afeta o
uso de checagens com `isinstance` ou `issubclass` com protocolos estáticos.

Por exemplo, qualquer classe com um método `+__float__+`
é considerada—durante a execução—uma subclasse virtual de `SupportsFloat`,
mesmo se seu método `+__float__+` não devolver um `float`.

Veja essa sessão no console:

[source, python]
----
>>> import sys
>>> sys.version
'3.9.5 (v3.9.5:0a7dcbdb13, May 3 2021, 13:17:02) \n[Clang 6.0 (clang-600.0.57)]'
>>> c = 3+4j
>>> c.__float__
<method-wrapper '__float__' of complex object at 0x10a16c590>
>>> c.__float__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't convert complex to float
----

Em Python 3.9, o tipo `complex` tem um método `+__float__+`, mas ele existe
apenas para gerar `TypeError` com uma mensagem de erro explícita. Se aquele
método `+__float__+` tivesse anotações, o tipo de retorno seria `NoReturn`— que
vimos na https://fpy.li/8f[«Seção 8.5.12»] (vol.1).

Mas incluir dicas de tipo em `+complex.__float__+` no _typeshed_ não resolveria
esse problema, porque o interpretador Python em geral ignora dicas de tipo—e
também não acessa os arquivos de anotações de tipo do _typeshed_.

Continuando da sessão anterior de Python 3.9:

[source, python]
----
>>> from typing import SupportsFloat
>>> c = 3+4j
>>> isinstance(c, SupportsFloat)
True
>>> issubclass(complex, SupportsFloat)
True
----

Então temos resultados enganosos: as checagens durante a execução usando
`SupportsFloat` sugerem que você pode converter um `complex` para `float`, mas
na verdade isso gera um erro de tipo.


[WARNING]
====

O problema específico com o tipo `complex` foi resolvido no Python 3.10, com
a remoção do método `+complex.__float__+`.

Mas o problema geral persiste: checagens com `isinstance`/`issubclass` só olham
para a presença ou ausência de métodos, sem checar sequer suas assinaturas,
muito menos suas anotações de tipo. E isso não vai mudar tão cedo, porque este
tipo de checagem de tipos durante a execução traria um custo de processamento
inaceitável.footnote:[Agradeço a Ivan Levkivskyi, co-autor da
https://fpy.li/pep544[«PEP 544 (sobre protocolos)»], por apontar que checagem de
tipo não é apenas uma questão de checar se o tipo de `x` é `T`: é sobre
determinar que o tipo de `x` é _consistente-com_ `T`, o que pode ser custoso.
Não é de se espantar que o Mypy leve alguns segundos para fazer uma checagem
de tipos, mesmo em scripts Python curtos.]

====

Agora vamos implementar um protocolo estático em uma classe concreta.

[[support_typing_proto]]
==== Suportando um protocolo estático

Lembra((("static protocols", "supporting", id="SPsupport13"))) da classe
`Vector2d`, que desenvolvemos no <<ch_pythonic_obj>>? Dado que um número
`complex` e uma instância de `Vector2d` contém um par de números de
ponto flutuante, faz sentido suportar a conversão de `Vector2d` para `complex`.

O <<ex_vector2d_complex_v4>> mostra a implementação do método `+__complex__+`,
para melhorar a última versão de `Vector2d`, vista no <<ex_vector2d_v3_full>> do <<ch_pythonic_obj>>.
Para deixar o serviço completo, podemos suportar a operação inversa, com um
método de classe `fromcomplex`, que constrói um `Vector2d` a partir de um
`complex`.

[[ex_vector2d_complex_v4]]
._vector2d_v4.py_: métodos para conversão de e para `complex`
====
[source, python]
----
include::../code/13-protocol-abc/typing/vector2d_v4.py[tags=VECTOR2D_V4_COMPLEX]
----
====

<1> Presume que `n` tem atributos `.real` e `.imag`. Veremos uma
implementação melhor no <<ex_vector2d_complex_v5>>.

Dado o código acima, e o método `+__abs__+` que o `Vector2d` já tinha em
<<ex_vector2d_v3_full>> do <<ch_pythonic_obj>>, temos o seguinte:

[source, python]
----
>>> from typing import SupportsComplex, SupportsAbs
>>> from vector2d_v4 import Vector2d
>>> v = Vector2d(3, 4)
>>> isinstance(v, SupportsComplex)
True
>>> isinstance(v, SupportsAbs)
True
>>> complex(v)
(3+4j)
>>> abs(v)
5.0
>>> Vector2d.fromcomplex(3+4j)
Vector2d(3.0, 4.0)
----

Para checagem de tipos durante a execução, o <<ex_vector2d_complex_v4>> serve
bem, mas para uma cobertura estática e relatório de erros melhores com o Mypy,
os métodos `+__abs__+`, `+__complex__+`, e `fromcomplex` deveriam receber dicas
de tipo, como mostrado no <<ex_vector2d_complex_v5>>.

[[ex_vector2d_complex_v5]]
._vector2d_v5.py_: acrescentando anotações aos métodos mencionados
====
[source, python]
----
include::../code/13-protocol-abc/typing/vector2d_v5.py[tags=VECTOR2D_V5_COMPLEX]
----
====

<1> A anotação de resultado `float` é necessária, senão o Mypy infere `Any`, e não
checa o corpo do método.

<2> Mesmo sem a anotação, o Mypy inferiu que isto devolve um
`complex`. A anotação evita um aviso, dependendo da configuração do Mypy.

<3> Aqui `SupportsComplex` garante que `n` é conversível.

<4> Esta conversão explícita é necessária, pois um tipo _consistente-com_ 
`SupportsComplex` não necessariamente tem os atributos `.real` e `.img`,
que usamos na linha seguinte. A própria classe `Vector2d` não tem estes
atributos, mas implementa `+__complex__+`.

O tipo de retorno de `fromcomplex` pode ser `Vector2d` se a linha `from
{dunder}future{dunder} import annotations` aparecer no início do módulo. Aquela
importação faz as dicas de tipo serem armazenadas como strings, sem serem
processadas durante a importação, quando as definições de função são tratadas.
Sem o `+__future__+` import of `annotations`, `Vector2d` é uma referência
inválida neste momento (a classe não está inteiramente definida ainda) e deveria
ser escrita como uma string: `'Vector2d'`, como se fosse uma referência
adiantada. Essa importação de `+__future__+` foi introduzida na
https://fpy.li/pep563[PEP 563—Postponed Evaluation of Annotations], implementada
no Python 3.7. Aquele comportamento estava marcado para se tornar default no
3.10, mas a mudança foi adiada para uma versão futura.footnote:[Leia a
https://fpy.li/13-32[decisão] do Python Steering Council na lista _python-dev_.]
Quando isso acontecer, a importação será redundante mas inofensiva.

Agora vamos criar—e depois estender—um novo protocolo estático.((("",
startref="SPsupport13")))

[[designing_static_proto_sec]]
==== Projetando um protocolo estático

Quando((("static protocols", "designing", id="SPdesign13"))) estudamos tipagem
ganso, vimos a ABC `Tombola` na <<defining_using_abc_sec>>. Aqui vamos ver como
definir uma interface similar usando um protocolo estático.

A ABC `Tombola` especifica dois métodos: `pick` e `load`. Poderíamos também
definir um protocolo estático com esses dois métodos, mas aprendi com a
comunidade Go que protocolos de apenas um método tornam a tipagem pato estática
mais útil e flexível. A biblioteca padrão do Go tem inúmeras interfaces, como
`Reader`, uma interface para E/S que requer apenas um método `read`. 
Depois, se você concluir que um protocolo mais complexo é necessário,
pode combinar dois ou mais protocolos para definir um novo.

<<<
Usar um componente que escolhe itens aleatoriamente pode ou não exigir o
recarregamento do componente, mas ele certamente precisa de um método para 
sortear um item, então escolhi o método `pick` para o
protocolo mínimo `RandomPicker`. O código do protocolo está no
<<ex_randompick_protocol>>, e seu uso é demonstrado por testes no
<<ex_randompick_protocol_demo>>.

[[ex_randompick_protocol]]
._randompick.py_: definição de `RandomPicker`
====
[source, python]
----
include::../code/13-protocol-abc/typing/randompick.py[]
----
====

[NOTE]
====

O método `pick` retorna `Any`. Na <<implementing_generic_static_proto_sec>>
veremos como tornar `RandomPicker` um tipo genérico, com um parâmetro que
permite aos usuários do protocolo especificarem o tipo de retorno do método
`pick`.

====

[[ex_randompick_protocol_demo]]
._randompick_test.py_: `RandomPicker` em uso
====
[source, python]
----
include::../code/13-protocol-abc/typing/randompick_test.py[]
----
====

<1> Não é necessário importar um protocolo estático para definir uma classe que
o implementa; aqui eu importei `RandomPicker` apenas para usá-lo em
`test_isinstance` mais tarde.

<2> `SimplePicker` implementa `RandomPicker`, mas não é uma subclasse dele.
Isso é a tipagem pato estática em ação.

<3> `Any` é o tipo de retorno default, então essa anotação não é estritamente
necessária, mas deixa mais claro que estamos implementando o protocolo
`RandomPicker`, como definido em <<ex_randompick_protocol>>.

<4> Não esqueça de acrescentar dicas `-&#62; None` aos seus testes, se quiser
que o Mypy olhe para eles.

<5> Acrescentei uma dica de tipo para a variável `popper`, para mostrar que o
Mypy entende que o `SimplePicker` é _consistente-com_.

<6> Teste provando que uma instância de `SimplePicker` também é uma instância
de `RandomPicker`. Isso funciona por causa do decorador `@runtime_checkable`
aplicado a `RandomPicker`, e porque o `SimplePicker` tem um
método `pick`, como exigido.

<7> Este teste invoca o método `pick` de `SimplePicker`, verifica que ele
retorna um dos itens dados a `SimplePicker`, e então realiza testes estáticos e
de execução sobre o item obtido.

<8> Esta linha gera uma observação no relatório do Mypy.

Como vimos no https://fpy.li/92[«Exemplo 22 do Capítulo 8»] (vol.1), `reveal_type` é uma função "mágica"
reconhecida pelo Mypy. Ela não é importada, e só conseguimos invocá-la
de dentro de blocos `if` cuja condição é `typing.TYPE_CHECKING`, uma constante 
que é sempre considerada `True` pelos checadores de tipos estáticos,
mas é `False` durante a execução do programa.

Os dois testes no <<ex_randompick_protocol_demo>> passam.
O Mypy também não encontra erro naquele código,
e mostra o resultado de `reveal_type` sobre o `item`
retornado por `pick`:

[source, shell]
----
$ mypy randompick_test.py
randompick_test.py:24: note: Revealed type is 'Any'
----

Tendo criado nosso primeiro protocolo, vamos estudar algumas recomendações sobre
essa prática.((("", startref="SPdesign13")))

[[best_protocol_design_sec]]
==== Melhores práticas no desenvolvimento de protocolos

Após((("static protocols", "best practices for protocol design"))) 10 anos de
experiência com tipagem pato estática em Go, está claro que protocolos estreitos
são mais úteis—muitas vezes tais protocolos têm um único método, raramente mais
que um par de métodos. Martin Fowler descreve uma boa ideia para se ter em mente
ao desenvolver protocolos: a https://fpy.li/13-33[_Role Interface_],
(interface papel—no sentido de incorporar uma personagem).
A ideia é que um protocolo deve ser definido em termos de um papel
que um objeto pode desempenhar, e não em termos de uma classe específica.

Além disso, é comum ver um protocolo definido próximo a uma função que o usa
para anotar um argumento, em, vez de forçar os clientes da função a importar
uma definição de interface de alguma biblioteca central.
Isso facilita a criação de novos tipos compatíveis com aquela função,
favorecendo a extensibilidade e facilitando testes com _mocks_
(simulacros).

As duas práticas, protocolos estreitos e protocolos em código cliente, evitam
um acoplamento muito forte, em acordo com o https://fpy.li/6v[Princípio da
Segregação de Interface], que podemos resumir como "Clientes não devem ser
forçados a depender de interfaces que não usam."

A página https://fpy.li/13-35[_Contributing to typeshed_] (Colaborando com o typeshed)
recomenda a seguinte convenção de nomenclatura para protocolos estáticos:

* Use nomes simples para protocolos que representam um conceito claro (e.g.,
`Iterator`, `Container`).

* Use `SupportsX` para protocolos que oferecem métodos que podem ser chamados
(e.g., `SupportsInt`, `SupportsRead`, `SupportsReadSeek`).footnote:[Qualquer
método pode ser chamado, então essa recomendação não diz muito. Talvez "forneça
um ou dois métodos"? De qualquer forma, é uma recomendação, não uma regra
absoluta.]

* Use `HasX` para protocolos que têm atributos de dados que podem ser lidos ou
escritos, ou métodos _getter/setter_ (e.g., `HasItems`, `HasFileno`).

A biblioteca padrão do Go tem uma convenção de nomenclatura que eu gosto: para
protocolos de método único, se o nome do método é um verbo, acrescente o sufixo
adequado (em inglês, "-er" ou "-or", em geral) para torná-lo um substantivo. Por
exemplo, em vez de `SupportsRead`, temos `Reader`. Outros exemplos incluem
`Formatter`, `Animator`, e `Scanner`. Para se inspirar, veja
https://fpy.li/13-36["Go (Golang) Standard Library Interfaces (Selected)"]
de Asuka Kenji.

Uma boa razão para se criar protocolos minimalistas é que eles servem de base
para protocolos mais complexos, quando necessário. Veremos a seguir que não é
difícil criar um protocolo derivado com um método adicional.

==== Estendendo um protocolo

Como((("static protocols", "extending"))) mencionei na seção anterior, os
desenvolvedores Go defendem que, na dúvida, melhor escolher o minimalismo
ao definir interfaces—o nome usado para protocolos estáticos naquela linguagem.
Muitas das interfaces Go mais usadas têm um único método.

Quando a prática revela que um protocolo com mais métodos seria útil, em vez de
adicionar métodos ao protocolo original, é melhor derivar dali um novo
protocolo. Estender um protocolo estático em Python tem algumas ressalvas, como
mostra o <<ex_randompickload_protocol>>.

[[ex_randompickload_protocol]]
._randompickload.py_: estendendo `RandomPicker`
====
[source, python]
----
include::../code/13-protocol-abc/typing/randompickload.py[]
----
====

<1> Se você quer que o protocolo derivado possa ser checado durante a execução,
precisa aplicar o decorador `@runtime_checkable` novamente—pois os
comportamentos definidos em decoradores de classes não são
herdados.footnote:[Para detalhes e justificativa, veja a seção sobre
https://fpy.li/13-37[`@runtime_checkable`] na PEP 544—Protocols: Structural
subtyping (static duck typing).]

<2> Todo protocolo deve nomear explicitamente `typing.Protocol` como uma de suas
classes base, além do protocolo que estamos estendendo. Isto é diferente da
forma como herança funciona de modo geral.footnote:[Novamente, leia 
https://fpy.li/13-38[_Merging and extending protocols_] na PEP 544 para os
detalhes e justificativa.]

<3> De volta à programação orientada a objetos "normal": só precisamos declarar
o método novo no protocolo derivado. A declaração do método `pick` é herdada de
`RandomPicker`.

Isto conclui o último exemplo sobre definir e usar um protocolo estático neste
capítulo. Para encerrar, vamos olhar as ABCs numéricas e sua possível
substituição por protocolos numéricos.


[[numbers_abc_proto_sec]]
==== As ABCs de `numbers` e os novos protocolos numéricos

Como((("static protocols", "numbers ABCS and numeric protocols",
id="SPnumbers13")))((("numbers ABCs", id="number13")))((("numeric protocols",
id="numpro13")))((("protocols", "numeric", id="Pnum13"))) vimos na
https://fpy.li/8g[«Seção 8.5.7.1»] (vol.1), as ABCs no pacote `numbers` da biblioteca padrão
funcionam bem para checagem de tipos durante a execução.

Se você precisa checar um inteiro, pode usar `isinstance(x, numbers.Integral)`
para aceitar `int`, `bool` (que é subclasse de `int`) ou outros tipos inteiros
oferecidos por bibliotecas externas que registram seus tipos como subclasses
virtuais das ABCs de `numbers`. Por exemplo, a NumPy tem
https://fpy.li/13-39[21 tipos inteiros]—bem como diversos tipos de ponto flutuante
registrados como `numbers.Real`, e números complexos com várias amplitudes de
bits, registrados como `numbers.Complex`.

[TIP]
====

Vale notar que `decimal.Decimal` não é registrado como uma subclasse virtual de
`numbers.Real`. A razão é que, se você precisa da precisão de `Decimal`, então
vai querer evitar mistura acidental de números decimais com números de ponto
flutuante (que são menos precisos).

====

Infelizmente, a torre numérica não foi projetada para checagem de tipo estática.
A ABC raiz—`numbers.Number`—não tem métodos, então se você declarar `x: Number`,
o Mypy não vai deixar você fazer operações aritméticas ou chamar qualquer método
com `X`.

Quando as ABCs de `numbers` não servem, quais as opções?
Um bom lugar para procurar soluções de tipagem é no projeto _typeshed_. Como
parte da biblioteca padrão de Python, o módulo `statistics` tem um arquivo stub
correspondente no _typeshed_ com dicas de tipo, o
https://fpy.li/13-40[_statistics.pyi_].

Lá você encontrará as seguintes definições, que são usadas para anotar diversas funções:

[source, python]
----
_Number = Union[float, Decimal, Fraction]
_NumberT = TypeVar('_NumberT', float, Decimal, Fraction)
----

Essa abordagem está correta, mas é limitada.
Ela não suporta((("numeric types", "support for"))) tipos numéricos
fora da biblioteca padrão, que as ABCs de `numbers` suportam durante a
execução—quando tipos numéricos são registrados como subclasses virtuais.

A tendência atual é recomendar os protocolos numéricos fornecidos pelo módulo `typing`,
como `SupportsFloat`, que discutimos na <<runtime_checkable_proto_sec>>.

Infelizmente, durante a execução os protocolos numéricos podem deixar você na
mão. Como mencionado na <<protocol_type_hints_ignored>>, o tipo `complex` no
Python 3.9 implementa `+__float__+`, mas o método existe apenas para lançar uma
`TypeError` com uma mensagem explícita: "can't convert complex to float" (não é
possível converter complex para float). Por alguma razão, ele também implementa
`+__int__+`. A presença destes métodos faz `isinstance` produzir resultados
enganosos no Python 3.9. No Python 3.10, os métodos de `complex` que geravam
`TypeError` incondicionalmente foram removidos.footnote:[ver
https://fpy.li/13-41[Issue #41974—Remove `+complex.__float__+`,
`+complex.__floordiv__+`, etc].]

Por outro lado, os tipos complexos da NumPy implementam métodos `+__float__+` e
`+__int__+` que funcionam, emitindo apenas um aviso quando cada um deles é usado
pela primeira vez:

[source, python]
----
>>> import numpy as np
>>> cd = np.cdouble(3+4j)
>>> cd
(3+4j)
>>> float(cd)
<stdin>:1: ComplexWarning: Casting complex values to real
discards the imaginary part
3.0
----

O problema oposto também acontece:
os tipos embutidos `complex`, `float`, e `int`, bem como `numpy.float16` e
`numpy.uint8`, não têm um método `+__complex__+`, então `isinstance(x,
SupportsComplex)` retorna `False` para eles.footnote:[Eu não testei todas as
outras variantes de _float_ e _integer_ que a NumPy oferece.] Os tipos complexos
da NumPy, tal como `np.complex64`, implementam `+__complex__+` para conversão em
um `complex` embutido.

Entretanto, na prática, o construtor embutido `complex()` trabalha com
instâncias de todos esses tipos sem erros ou avisos.

[source, python]
----
>>> import numpy as np
>>> from typing import SupportsComplex
>>> sample = [1+0j, np.complex64(1+0j), 1.0, np.float16(1.0), 1, np.uint8(1)]
>>> [isinstance(x, SupportsComplex) for x in sample]
[False, True, False, False, False, False]
>>> [complex(x) for x in sample]
[(1+0j), (1+0j), (1+0j), (1+0j), (1+0j), (1+0j)]
----

Isso mostra que checagens de `SupportsComplex` com `isinstance` sugerem que
todas aquelas conversões para `complex` falhariam, mas elas funcionam. Na
lista de discussão _typing-sig_, Guido van Rossum indicou que o `complex` embutido
aceita um único argumento, e por isso as conversões funcionam.

Por outro lado, o Mypy aceita argumentos de todos esses seis tipos em uma
chamada à função `to_complex()`, definida assim:

[source, python]
----
def to_complex(n: SupportsComplex) -> complex:
    return complex(n)
----

No momento em que escrevo isso, a NumPy não tem dicas de tipo, então seus tipos
numéricos são todos `Any`.footnote:[Os tipos numéricos da NumPy são todos
registrados com as ABCs apropriadas de `numbers`, que o Mypy ignora.] Por outro
lado, o Mypy de alguma maneira "sabe" que o `int` e o `float` embutidos podem
ser convertidos para `complex`, apesar de, no _typeshed_, apenas a classe
embutida `complex` ter o método `+__complex__+`.footnote:[Isso é uma mentira bem
intencionada da parte do typeshed: a partir de Python 3.9, o tipo embutido
`complex` na verdade não tem mais um método `+__complex__+`.]

Concluindo, apesar((("numeric types", "checking for convertibility"))) da
expectativa de que a checagem de tipos numéricos não seria difícil, a situação
atual é a seguinte: as dicas de tipo da PEP 484
https://fpy.li/cardxvi[desprezam] a torre numérica e recomendam implicitamente
que os checadores de tipos tratem como casos especiais as relações de tipo entre
os `complex`, `float`, e `int` embutidos. O Mypy faz isso, e também,
pragmaticamente, aceita que `int` e `float` são _consistente-com_
`SupportsComplex`, apesar deles não implementarem `+__complex__+`.

[TIP]
====
Só encontrei resultados inesperados usando checagens com `isinstance` em
conjunto com os protocolos numéricos `Supports*` quando fiz experiências de
conversão de ou para `complex`. Se você não usa números complexos, pode confiar
naqueles protocolos em vez das ABCs de `numbers`.
====

As principais lições dessa seção são:

* As ABCs de `numbers` são boas para checagem de tipos durante a execução, mas
não servem para tipagem estática.

* Os protocolos numéricos estáticos `SupportsComplex`, `SupportsFloat`, etc.
funcionam bem para tipagem estática, mas são pouco confiáveis para checagem de
tipos durante a execução se números complexos estiverem envolvidos.

Estamos agora prontos para a revisão dos temas deste capítulo.((("",
startref="Pstatic13")))((("", startref="Pnum13")))((("",
startref="numpro13")))((("", startref="number13")))((("",
startref="SPnumbers13")))


=== Resumo do capítulo

O((("interfaces", "overview of")))((("protocols", "overview of")))((("ABCs (abstract base classes)",
"overview of"))) Mapa de Tipagem (<<type_systems_described>>) 
é a chave para entender este capítulo. Após uma breve introdução às quatro
abordagens da tipagem, comparamos protocolos dinâmicos e estáticos, que
suportam tipagem pato e tipagem pato estática, respectivamente. Os dois tipos de
protocolo compartilham uma característica essencial: nunca é exigido de uma
classe que ela declare explicitamente o suporte a qualquer protocolo.
Uma classe suporta um protocolo apenas implementando os métodos necessários.

A próxima parada foi a <<prog_ducks_sec>>, onde exploramos os esforços que o
interpretador Python faz para que os protocolos dinâmicos de sequência e
iterável funcionem, incluindo a implementação parcial de ambos. Então vimos como
fazer uma classe implementar um protocolo durante a execução, através da adição
de métodos via _monkey patching_. A seção sobre tipagem pato terminou com
sugestões de programação defensiva, incluindo a detecção de tipos estruturais
sem checagens explícitas com `isinstance` ou `hasattr`, usando `try/except` e
falhando logo.

Após Alex Martelli introduzir a tipagem ganso em _<<waterfowl_essay>>_, vimos
como criar subclasses de ABCs existentes, examinamos algumas ABCs importantes da
biblioteca padrão, e criamos uma ABC do zero, que então implementamos por
herança e por registro. Finalizamos aquela seção vendo como o método especial
`+__subclasshook__+` permite que ABCs suportem a tipagem estrutural, pelo
reconhecimento de classes não-relacionadas, mas que fornecem os métodos 
exigidos pela interface declarada na ABC.

Retomamos o estudo da tipagem pato estática na <<static_protocols_sec>>, que
iniciamos na https://fpy.li/8m[«Seção 8.5.10»] (vol.1). Vimos como
o decorador `@runtime_checkable` também aproveita o `+__subclasshook__+` para
suportar tipagem estrutural durante a execução—mesmo que o melhor uso dos
protocolos estáticos seja com checadores de tipos estáticos, que podem levar em
consideração as dicas de tipo, tornando a tipagem estrutural mais confiável.
Então falamos sobre o projeto e a codificação de um protocolo estático e como
estendê-lo. O capítulo terminou com a triste história do abandono da torre
numérica e das limitações da alternativa proposta: os protocolos numéricos
estáticos, tal como `SupportsFloat` e outros adicionados ao módulo `typing` no
Python 3.8.

A mensagem principal deste capítulo é que temos quatro maneiras complementares
de programar com interfaces no Python moderno, cada uma com diferentes vantagens
e deficiências. Você encontrará casos de uso adequados para cada esquema de
tipagem em qualquer base de código moderna de tamanho significativo. Rejeitar
qualquer destas abordagens tornará seu trabalho como programador Python mais
difícil e limitado.

Dito isso, Python ganhou sua enorme popularidade enquanto suportava apenas
tipagem pato. Outras linguagens populares que aproveitam o poder e a
simplicidade da tipagem pato são JavaScript, PHP e Ruby, e outras,
menos populares mas muito influentes, como 
Lisp, Smalltalk, Erlang, Elixir e Clojure.

[[interfaces_further_reading]]
=== Para saber mais

Para((("interfaces", "further reading on")))((("protocols",
"further reading on")))((("ABCs (abstract base classes)", "further reading on")))
uma rápida revisão dos prós e contras da tipagem, bem como da importância de
`typing.Protocol` para a saúde de bases de código checadas estaticamente,
recomendo fortemente o post de Glyph Lefkowitz
https://fpy.li/13-42[_I Want A New Duck: `typing.Protocol` and the future of duck typing_]
(Quero um novo pato: `typing.Protocol` e o futuro da tipagem pato).
Também aprendi bastante em seu post
https://fpy.li/13-43[_Interfaces and Protocols_],
comparando `typing.Protocol` com `zope.interface`—um mecanismo mais antigo
para definir interfaces em sistemas que suportam plug-in fracamente acoplados, usado no
https://fpy.li/13-44[_Plone CMS_], no framework Web
na https://fpy.li/13-45[_Pyramid_], e no framework de programação assíncrona
https://fpy.li/13-46[_Twisted_],
um projeto fundado por Glyph.footnote:[Agradeço ao revisor técnico Jürgen Gmach
por ter recomendado o post _Interfaces and Protocols_.]

Bons livros sobre Python têm—quase que por definição—uma ótima cobertura de
tipagem pato. Dois de meus livros favoritos de Python tiveram atualizações
lançadas após a primeira edição de _Python Fluente_: _The Quick Python Book_,
3rd ed., (Manning), de Naomi Ceder; e _Python in a Nutshell_, 3rd ed., de
Alex Martelli, Anna Ravenscroft, e Steve Holden (O'Reilly).

Para uma discussão sobre os prós e contras da tipagem dinâmica, veja a
entrevista de Guido van Rossum com Bill Venners em
https://fpy.li/13-47[_Contracts in Python: A Conversation with Guido van Rossum, Part IV_]
(Contratos em Python: uma conversa com Guido van Rossum).
O post https://fpy.li/13-48[_Dynamic Typing_], de Martin Fowler,
traz uma avaliação perspicaz e equilibrada deste debate.
Ele também escreveu
https://fpy.li/13-33[_Role Interface_] (interface papel), que
mencionei na <<best_protocol_design_sec>>. Apesar de não ser sobre tipagem pato,
aquele post é altamente relevante para o projeto de protocolos em Python, pois
ele contrasta as interfaces papel estreitas com as interfaces públicas bem mais
abrangentes de classes em geral.

A documentação do Mypy é, muitas vezes, a melhor fonte de informação sobre
qualquer tema relacionado a tipagem estática em Python,
incluindo à tipagem pato estática, tratada em
https://fpy.li/13-50[_Protocols and structural subtyping_].

As demais referências são sobre tipagem ganso.

O _Python Cookbook_, 3rd ed. de Beazley & Jones (O'Reilly) tem uma seção sobre
como definir uma ABC (Recipe 8.12). O livro foi escrito antes de Python 3.4,
então eles não usam a atual sintaxe preferida para declarar ABCs, criar uma
subclasse de `abc.ABC` (em vez disso, eles usam a palavra-chave `metaclass`, da
qual só vamos precisar mesmo no https://fpy.li/24[«Capítulo 24»] (vol.3)). Tirando esse pequeno
detalhe, a receita cobre os principais recursos das ABCs muito bem.

_The Python Standard Library by Example_ de Doug Hellmann (Addison-Wesley), tem
um capítulo sobre o módulo `abc`. Ele também está disponível na Web, em
https://fpy.li/13-51[_PyMOTW—Python Module of the Week_]. Hellmann usa a
declaração de ABC no estilo antigo: `++PluginBase(metaclass=abc.ABCMeta)++` em vez de
`PluginBase(abc.ABC)`, suportada desde o Python 3.4.

Quando usamos ABCs, herança múltipla não é apenas comum, mas praticamente
inevitável, pois cada uma das ABCs fundamentais de coleções (`Sequence`,
`Mapping`, `Set`) estende `Collection`, que por sua vez estende múltiplas ABCs
(veja <<collections_uml>>). Assim, o <<ch_inheritance>> é um complemento
importante ao presente capítulo.

A https://fpy.li/13-52[_PEP 3119–Introducing Abstract Base Classes_]
apresenta a justificativa para as ABCs.
A https://fpy.li/13-53[_PEP 3141–A Type Hierarchy for Numbers_]
apresenta as ABCs do https://fpy.li/13-54[módulo `numbers`],
mas a discussão no
https://fpy.li/13-55[_Mypy issue #3186_] intitulado
"int is not a Number?" (int não é um número?)
inclui alguns argumentos sobre por que a torre numérica não serve
para checagem estática de tipo.
Alex Waygood escreveu uma
https://fpy.li/13-56[«resposta abrangente»] no StackOverflow, discutindo formas de anotar tipos numéricos.

Vou continuar monitorando o 
https://fpy.li/13-55[_Mypy issue #3186_]
para os próximos capítulos dessa saga,
na esperança de um final feliz que torne a tipagem estática
e a tipagem ganso compatíveis, como deveriam ser.


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

**A Jornada MVP da tipagem estática no Python**

Trabalhei((("interfaces", "Soapbox discussion", id="Isoap13")))((("protocols",
"Soapbox discussion", id="Psoap13")))((("ABCs (abstract base classes)", "Soapbox
discussion", id="ABCsoap13")))((("Soapbox sidebars", "static
typing")))((("static protocols", "Soapbox discussion"))) na Thoughtworks, 
empresa pioneira em metodologias ágeis de engenharia de software.
A Thoughtworks muitas vezes ajuda os clientes a criar e implantar um MVP:
_Minimum Viable Product_ (Produto Mínimo Viável),
"uma versão simples de um produto, oferecida para os usuários com o
objetivo de validar hipóteses centrais do negócio,"
conforme a definição de Paulo Caroli em
https://fpy.li/13-58[_Lean Inception_],
um artigo no
https://fpy.li/13-59[«blog coletivo»] editado por Martin Fowler.

Guido van Rossum e os outros mantenedores que projetaram e implementaram a
tipagem estática têm seguido a estratégia do MVP desde 2006. Primeiro, a
https://fpy.li/pep3107[_PEP 3107—Function Annotations_] foi implementada no Python
3.0 com uma semântica bastante limitada: apenas uma sintaxe para anexar
anotações a parâmetros e resultados de funções, armazenadas no objeto função.
Isso foi feito para explicitamente permitir experimentação e
receber feedback—os principais benefícios de um MVP.

Oito anos depois, a https://fpy.li/pep484[_PEP 484—Type Hints_] foi proposta e
aprovada. Sua implementação, no Python 3.5, não exigiu mudanças na linguagem ou
na biblioteca padrão—exceto a adição do módulo `typing`, do qual nenhuma outra
parte da biblioteca padrão dependia.

A PEP 484 suportava apenas tipos nominais com genéricos—similar ao Java—mas com
a checagem estática sendo executada por ferramentas externas.
Recursos importantes não existiam, como anotações de variáveis, tipos embutidos
genéricos, e protocolos.

Apesar destas limitações, este MVP de tipagem foi bem sucedido o suficiente para
atrair investimento e adoção por parte de empresas com enormes bases de código
em Python, como a Dropbox, o Google e o Facebook, bem como apoio de IDEs
profissionais como o https://fpy.li/13-60[PyCharm], o
https://fpy.li/13-61[Wing], e o https://fpy.li/13-62[VS Code].

<<<
A https://fpy.li/pep526[_PEP 526—Syntax for Variable Annotations_] foi o primeiro
passo evolutivo que exigiu mudanças no interpretador, no Python 3.6. Mais
mudanças no interpretador foram feitas na versão 3.7 para suportar a
https://fpy.li/pep563[_PEP 563—Postponed Evaluation of Annotations_] e a
https://fpy.li/pep560[_PEP 560—Core support for typing module and generic types_],
que permitiram que coleções embutidas e da biblioteca padrão aceitem dicas de
tipo genéricas "de fábrica" no Python 3.9, graças à
https://fpy.li/pep585[_PEP 585—Type Hinting Generics In Standard Collections_].

Durante todos esses anos, alguns usuários de Python—incluindo eu—ficamos
desapontados com os tipos estáticos. Após aprender Go, a falta de
tipagem pato estática em Python era incompreensível, 
pois a tipagem pato sempre foi uma característica marcante desta linguagem.

Mas essa é a natureza dos MVPs: eles podem não satisfazer todos os usuários em
potencial, mas exigem menos esforço de implementação, e guiam o desenvolvimento
posterior com o feedback do uso em situações reais.

Se há uma coisa que todos aprendemos com Python 3, é que progresso incremental é
mais seguro que lançamentos estrondosos. Estou contente que não tivemos que
esperar pelo Python 4—se é que existirá—para tornar Python mais atrativo para
grandes empresas, onde os benefícios da tipagem estática superam a complexidade
adicional.

**Abordagens à tipagem em linguagens populares**

A <<type_systems_languages>> é((("Soapbox sidebars", "typing map")))((("typing
map"))) uma variação do Mapa de Tipagem (<<type_systems_described>>) com 
algumas linguagens conhecidas que suportam cada um dos modos de tipagem.

TypeScript e Python ≥ 3.8 são as únicas linguagens em minha pequena amostra que
suportam todas as quatro abordagens.

Go é claramente uma linguagem de tipos estáticos na tradição do Pascal, mas ela
foi a pioneira da tipagem pato estática—pelo menos entre as linguagens mais
usadas hoje. Também coloquei Go no quadrante da tipagem ganso por causa
de sua sintaxe especial para checagem de tipo (_type assertion_),
que permite tratar tipos nominais dinamicamente durante a execução.

<<<
{nbsp}

[[type_systems_languages]]
.Quatro abordagens para checagem de tipos e algumas linguagens que as usam.
image::../images/mapa-da-tipagem-linguagens.png[align="center",pdfwidth=12cm]

No ano 2000, só existiam linguagens populares nos quadrantes diametralmente opostos 
da tipagem pato e da tipagem estática.
Não conheço nenhuma linguagem que suportava tipagem pato estática ou
tipagem ganso 20 anos atrás, mas pode ser que existam.
O fato de cada um dos quatro quadrantes ter pelo
menos três linguagens populares sugere que muita gente vê benefícios em cada uma
das quatro abordagens à tipagem.

**Monkey patching**

Monkey patching((("Soapbox sidebars",
"monkey-patching")))((("monkey-patching"))) tem uma reputação ruim. Se usado com
exagero, pode gerar sistemas difíceis de entender e manter. O remendo (_patch_)
dinâmico está
normalmente fortemente acoplado ao seu alvo, tornando-se quebradiço quando o código
evolui. Outro problema é
que duas bibliotecas que aplicam remendos deste tipo durante a execução podem
pisar nos pés uma da outra, com a segunda biblioteca a rodar destruindo os
remendos da primeira.

Mas o monkey patching pode também ser útil, por exemplo, para fazer uma classe
implementar um protocolo durante a execução. O design pattern _Adapter_ resolve
o mesmo problema de modo mais verboso implementando toda uma nova classe.

É fácil usar monkey patching em código Python, mas há limitações. Ao contrário
de Ruby e JavaScript, Python não permite mudar o comportamento dos tipos
embutidos durante a execução. Na verdade, considero isto uma vantagem, pois dá a
certeza de que um objeto `str` terá sempre os mesmos métodos. Esta limitação reduz
a chance de bibliotecas aplicarem correções conflitantes quando importadas em
seu projeto.

**Metáforas e idiomatismos em interfaces**

Uma((("Soapbox sidebars", "interfaces"))) metáfora promove o entendimento
tornando restrições e acessos visíveis. Esse é o valor das palavras _stack_
(pilha) e _queue_ (fila) para descrever estruturas de dados fundamentais:
elas tornam claras as operações permitidas, isto é, como os itens podem ser
adicionados ou removidos. Por outro lado, Alan Cooper et al. escrevem em _About
Face, the Essentials of Interaction Design_, 4th ed. (Wiley):

[quote]
____

Fidelidade estrita a metáforas liga interfaces de forma desnecessariamente firme
aos mecanismos do mundo físico.

____

Os autores estão falando de interface de usuário, mas a advertência se aplica também a
APIs. Eles admitem que quando "cai no nosso colo" uma metáfora "verdadeiramente
apropriada", podemos usá-la (escreveram "cai no nosso colo" porque é tão
difícil encontrar metáforas adequadas que ninguém deveria perder tempo tentando
encontrá-las). Acredito que a imagem da máquina de bingo que usei
nesse capítulo é apropriada.

_About Face_ é, disparado, o melhor livro sobre design de UI que já li—e eu li
uns tantos. Abandonar as metáforas como paradigmas de design, adotando em seu
lugar "interfaces idiomáticas", foi a lição mais valiosa que aprendi com o
trabalho de Cooper.

Em _About Face_, Cooper não lida com APIs, mas quanto mais penso em suas ideias,
mais vejo como se aplicam ao Python. Cada protocolo fundamental da linguagem é
o que Cooper chama de _idiom_ (idiomatismo).footnote:[Neste contexto,
a tradução correta de _idiom_ não é "idioma", mas sim "idiomatismo" que é uma
"construção ou locução peculiar a uma língua".
Cooper defende que uma GUI
é uma linguagem com locuções peculiares, como menus e caixas de diálogo.]

Uma vez que aprendemos o que é uma "sequência",
podemos aplicar este conhecimento em diferentes contextos. Este é o tema
principal de _Python Fluente_: ressaltar os idiomatismos fundamentais da linguagem,
para que o seu código seja conciso, eficaz e legível para um pythonista
fluente.((("", startref="ABCsoap13")))((("", startref="Psoap13")))((("",
startref="Isoap13")))

****
