[[ch_op_overload]]
== Sobrecarga de operadores
:example-number: 0
:figure-number: 0

[quote, James Gosling, Criador de Java]
____
Existem algumas coisas que me deixam meio dividido, como a sobrecarga de operadores. Deixei a sobrecarga de operadores de fora em uma decisão bastante pessoal, pois tinha visto gente demais abusar [desse recurso] no {cpp}.footnote:[Fonte: https://fpy.li/16-1["The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling" (_A Família de Linguagens C: Entrevista com Dennis Ritchie, Bjarne Stroustrup, e James Gosling_)] (EN).]
____

Em((("operator overloading", "infix operators")))((("infix operators"))) Python, podemos calcular juros compostos usando uma fórmula escrita assim:

[source, python]
----
interest = principal * ((1 + rate) ** periods - 1)
----

Operadores que aparecem entre operandos, como em `1 + rate`, são _operadores infixos_.
No Python, operadores infixos podem lidar com qualquer tipo arbitrário.
Assim, se você está trabalhando com dinheiro real, pode se assegurar que `principal`, `rate`, e `periods` sejam números exatos—instâncias da classe `decimal.Decimal` de Python—e a fórmula vai funcionar como está escrita, produzindo um resultado exato.

Mas em Java, se você mudar de `float` para `BigDecimal`, para obter resultados exatos, não é mais possível usar operadores infixos, porque naquela linguagem eles só funcionam com tipos primitivos.
Abaixo vemos a mesma fórmula escrita em Java para funcionar com números `BigDecimal`:

[source, java]
----
BigDecimal interest = principal.multiply(BigDecimal.ONE.add(rate)
                        .pow(periods).subtract(BigDecimal.ONE));
----

Está claro que operadores infixos tornam as fórmulas mais legíveis.
A sobrecarga de operadores é necessária para suportar a notação infixa de operadores com tipos definidos pelo usuário ou estendidos, tal como os arrays da NumPy.
Oferecer a sobrecarga de operadores em uma linguagem de alto nível e fácil de usar foi provavelmente uma das principais razões do imenso sucesso de Python na ciência de dados, incluindo as aplicações financeiras e científicas.

Na <<data_model_emulating_sec>> (<<ch_data_model>>) vimos algumas implementações triviais de operadores em uma classe básica `Vector`. Os métodos `+__add__+` e `+__mul__+` no <<ex_vector2d>> foram escritos para demonstrar como os métodos especiais suportam a sobrecarga de operadores, mas deixamos passar problemas sutis naquelas implementações. Além disso, no <<ex_vector2d_v0>> notamos que o método `+Vector2d.__eq__+` considera `True` a seguinte expressão: `Vector(3, 4) == [3, 4]`&#x2014;algo que pode ou não fazer sentido. Nesse capítulo vamos cuidar desses problemas, e((("operator overloading", "topics covered"))) falaremos também de:

* Como um método de operador infixo deveria indicar que não consegue tratar um operando
* O uso de _duck typing_ ou _goose typing_ para lidar com operandos de vários tipos
* O comportamento especial dos operadores de comparação cheia (e.g., `==`, `>`, `<=`, etc.)
* O tratamento default de operadores de atribuição aumentada tal como `+=`, e como sobrecarregá-los


=== Novidades neste capítulo

O _goose typing_((("operator overloading", "significant changes to"))) é uma parte fundamental de Python, mas as ABCs `numbers` não são suportadas na tipagem estática. Então modifiquei o <<ex_vector_v7>> para usar _duck typing_, em vez de uma verificação explícita usando `isinstance` contra `numbers.Real`.footnote:[O restante das ABCs na biblioteca padrão de Python ainda são valiosas para o _goose typing_ e a tipagem estática. O problema com as ABCs `numbers` é explicado na <<numbers_abc_proto_sec>>.]

Na primeira edição do _Python Fluente_, tratei do operador de multiplicação de matrizes `@` como uma mudança futura, quando Python 3.5 ainda estava em sua versão alfa. Agora o `@` está integrado ao fluxo do capítulo na <<matmul_operator_sec>>.
Aproveitei o _goose typing_ para tornar a implementação de `+__matmul__+` aqui mais segura que a da primeira edição, sem comprometer sua flexibilidade.

A <<further_reading_op_sec>> agora inclui algumas novas referências—incluindo um post de blog de Guido van Rossum.
Também adicionei menções a duas bibliotecas que demonstram um uso efetivo da sobrecarga de operadores fora do domínio da matemática: `pathlib` e `Scapy`.


[[op_overloading_101_sec]]
=== Introdução à sobrecarga de operadores

A sobrecarga de operadores((("operator overloading", "basics of"))) permite que objetos definidos pelo usuário interoperem com operadores infixos como `+` e `|`, ou com operadores unários como `-` e `~`. No Python, de uma perspectiva mais geral, a invocação de funções (`()`), o acesso a atributos (`.`)  e o acesso a itens e o fatiamento (`[]`) também são operadores, mas este capítulo trata dos operadores unários e infixos.

A sobrecarga de operadores tem má-fama em certos círculos.
É um recurso da linguagem que pode ser (e tem sido) abusado,
resultando em programadores confusos, bugs, e gargalos de desempenho inesperados.
Mas se bem utilizada, possibilita APIs agradáveis de usar e código legível.
Python alcança um bom equilíbrio entre flexibilidade,
usabilidade e segurança,
pela imposição de algumas limitações:

* Não é permitido modificar o significado dos operadores para os tipos embutidos.
* Não é permitido criar novos operadores, apenas sobrecarregar os existentes.
* Alguns poucos operadores não podem ser sobrecarregados: `is`, `and`, `or` e `not` (mas os operadores `==`, `&`, `|`, e `~` podem).

No <<ch_seq_methods>>, na classe `Vector`, já apresentamos um operador infixo: `==`, suportado pelo método `+__eq__+`. Nesse capítulo, vamos melhorar a implementação de `+__eq__+` para lidar melhor com operandos de outros tipos além de `Vector`. Entretanto, os operadores de comparação cheia (`==`, `!=`, `>`, `<`, `>=`, `<=`) são casos especiais de sobrecarga de operadores, então começaremos sobrecarregando quatro operadores aritméticos em `Vector`: os operadores unários `-` e `+`, seguido pelos infixos `+` e `*`.

Vamos começar pelo tópico mais fácil: operadores unários.


=== Operadores unários

A seção https://docs.python.org/pt-br/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations["6.5. Unary arithmetic and bitwise operations" (_Aritmética unária e operações binárias_)] (EN), de _A Referência da Linguagem Python_, elenca((("operator overloading", "unary operators", id="OOunary16")))((("unary operators", id="unary16"))) três operações unárias, listadas abaixo juntamente com seus métodos especiais associados:

`-`, implementado por `+__neg__+`:: Negativo((("&#x005F;&#x005F;neg&#x005F;&#x005F;"))) aritmético unário. Se `x` é `-2` então `-x == 2`.
`{plus}`, implementado por `+__pos__+`:: Positivo((("&#x005F;&#x005F;pos&#x005F;&#x005F;"))) aritmético unário. De forma geral, `x == +x`, mas há alguns poucos casos onde isso não é verdadeiro. Veja a <<when_plus_x_sec>>, se estiver curioso.
`~`, implementado por `+__invert__+`:: Negação((("&#x005F;&#x005F;invert&#x005F;&#x005F;"))) binária, ou inversão binária de um inteiro, definida como `~x == -(x+1)`. Se `x` é `2` então `~x == -3`.footnote:[Veja https://pt.wikipedia.org/wiki/L%C3%B3gica_bin%C3%A1ria#NOT[Lógica Binária - NOT] para uma explicação da negação binária.]

O pass:[<a href="https://docs.python.org/pt-br/3/reference/datamodel.html#object.__neg__">capítulo "Modelo de Dados"</a>] de _A Referência da Linguagem Python_ também inclui a função embutida `abs()` como um operador unário. O método especial associado é `+__abs__+`, como já vimos.

É fácil suportar operadores unários. Basta implementar o método especial apropriado, que receberá apenas um argumento: `self`. Use a lógica que fizer sentido na sua classe, mas se atenha à regra geral dos operadores: sempre devolva um novo objeto. Em outras palavras, não modifique o receptor (`self`), crie e devolva uma nova instância do tipo adequado.

No caso de `-` e `+`, o resultado será provavelmente uma instância da mesma classe de `self`. Para o `+` unário, se o receptor for imutável você deveria devolver `self`; caso contrário, devolva uma cópia de `self`.
Para `abs()`, o resultado deve ser um número escalar.

Já no caso de `~`, é difícil determinar o que seria um resultado razoável se você não estiver lidando com bits de um número inteiro.
No pacote de análise de dados https://fpy.li/pandas[_pandas_], o til nega condições booleanas de filtragem; veja exemplos na documentação do _pandas_, em https://fpy.li/16-4["Boolean indexing" (_Indexação booleana)] (EN).

Como prometido acima, vamos implementar vários novos operadores na classe `Vector`, do  <<ch_seq_methods>>. O <<ex_vector_v6_unary>> mostra o método  `+__abs__+`, que já estava no  <<ex_vector_v5>>, e os novos métodos `+__neg__+` e `+__pos__+` para operadores unários.

[[ex_vector_v6_unary]]
.vector_v6.py: unary operators - and + added to <<ex_vector_v5>>
====
[source, python]
----
include::../code/16-op-overloading/vector_v6.py[tags=VECTOR_V6_UNARY]
----
====
<1> Para computar `-v`, cria um novo `Vector` com a negação de cada componente de `self`.
<2> Para computar `+v`, cria um novo `Vector` com cada componente de `self`.

Lembre-se que instâncias de `Vector` são iteráveis, e o `+Vector.__init__+` recebe um argumento iterável, e daí as implementações de `+__neg__+` e
`+__pos__+` são curtas e rápidas.

Não vamos implementar `+__invert__+`. Se um usuário tentar escrever `~v` para uma instância de `Vector`, Python vai gerar um  `TypeError` com uma mensagem clara: “bad operand type for unary ~: `'Vector'`” (_operando inválido para o ~ unário: `'Vector'`).

O quadro a seguir trata de uma curiosidade que algum dia poderá ajudar você a ganhar uma aposta sobre o `+` unário .

[[when_plus_x_sec]]
[role="pagebreak-before less_space"]
.Quando x e +x não são iguais
****

Todo mumdo espera que `x == +x`, e isso é verdade no Python quase todo o tempo, mas encontrei dois casos na biblioteca padrão onde `x != +x`.

O((("decimal.Decimal class"))) primeiro caso envolve a classe `decimal.Decimal`. Você pode obter
`x != +x` se `x` é uma instância de `Decimal`, criada em um dado contexto aritmético e `+x` for então avaliada em um contexto com definições diferentes. Por exemplo, `x` é calculado em um contexto com uma determinada precisão, mas a precisão do contexto é modificada e daí `+x` é avaliado. Veja a uma demonstração no <<ex_unary_plus_decimal>>.

[[ex_unary_plus_decimal]]
.Uma mudança na precisão do contexto aritmético pode fazer `x` se tornar diferente de `+x`
====
[source, python]
----
include::../code/16-op-overloading/unary_plus_decimal.py[tags=UNARY_PLUS_DECIMAL]
----
====
<1> Obtém uma referência ao contexto aritmético global atual.
<2> Define a precisão do contexto aritmético em `40`.
<3> Computa `1/3` usando a precisão atual.
<4> Inspeciona o resultado; há 40 dígitos após o ponto decimal.
<5> `one_third == +one_third` é `True`.
<6> Diminui a precisão para `28`—a precisão default para aritmética com `Decimal`.
<7> Agora `one_third == +one_third` é `False`.
<8> Inspeciona `+one_third`; aqui há 28 dígitos após o `'.'` .

O fato é que cada ocorrência da expressão `+one_third` produz uma nova instância de `Decimal` a partir do valor de `one_third`, mas usando a precisão do contexto aritmético atual.

Podemos encontrar o segundo caso onde `x != +x` na https://docs.python.org/pt-br/3/library/collections.html#collections.Counter[documentação] de `collections.Counter`. A classe `Counter` implementa vários operadores aritméticos, incluindo o `+` infixo, para somar a contagem de duas instâncias de `Counter`. Entretanto, por razões práticas, a adição em `Counter` descarta do resultado qualquer item com contagem negativa ou zero. E o prefixo `+++` é um atalho para somar um `Counter` vazio, e portanto produz um novo `Counter`, preservando apenas as contagens maiores que zero. Veja o <<ex_unary_plus_counter>>.

[[ex_unary_plus_counter]]
.O + unário produz um novo `Counter`sem as contagens negativas ou zero
====
[source, python]
----
>>> ct = Counter('abracadabra')
>>> ct
Counter({'a': 5, 'r': 2, 'b': 2, 'd': 1, 'c': 1})
>>> ct['r'] = -3
>>> ct['d'] = 0
>>> ct
Counter({'a': 5, 'b': 2, 'c': 1, 'd': 0, 'r': -3})
>>> +ct
Counter({'a': 5, 'b': 2, 'c': 1})
----
====

Como se vê, `+ct` devolve um contador onde todas as contagens são maiores que zero.

Agora voltamos à nossa programação normal.((("", startref="OOunary16")))((("", startref="unary16")))

****

[[overloading_plus_sec]]
=== Sobrecarregando + para adição de Vector

A((("operator overloading", "overloading &#x002B; for vector addition", id="OOplus16")))((("mathematical vector operations")))((("&#x002B; operator", id="Plusover16")))((("vectors", "overloading &#x002B; for vector addition", id="Voverload16"))) classe `Vector` é um tipo sequência,
e a seção https://docs.python.org/pt-br/3/reference/datamodel.html#emulating-container-types["3.3.7. Emulando de tipos contêineres"]
do capítulo "Modelo de Dados", na documentação oficial do Python, diz que sequências devem suportar o operador
`\+` para concatenação e o `\*` para repetição.
Entretanto, aqui vamos implementar `+` e `*` como operações matemáticas de vetores, algo um pouco mais complicado porém mais útil para um tipo `Vector`.

[TIP]
====
Usuários que desejem concatenar ou repetir instâncias de `Vector` podem convertê-las para tuplas ou listas, aplicar o operador e convertê-las de volta—graças ao fato de `Vector` ser iterável e poder ser criado a partir de um iterável:

[source, python]
----
>>> v_concatenated = Vector(list(v1) + list(v2))
>>> v_repeated = Vector(tuple(v1) * 5)
----
====

Somar dois vetores euclidianos resulta em um novo vetor no qual os componentes são as somas pareadas dos componentes dos operandos. Ilustrando:

[source, python]
----
>>> v1 = Vector([3, 4, 5])
>>> v2 = Vector([6, 7, 8])
>>> v1 + v2
Vector([9.0, 11.0, 13.0])
>>> v1 + v2 == Vector([3 + 6, 4 + 7, 5 + 8])
True
----

E o que acontece se tentarmos somar duas instâncias de `Vector` de tamanhos diferentes? Poderíamos gerar um erro, mas considerando as aplicações práticas (tal como recuperação de informação), é melhor preencher o `Vector` menor com zeros. Esse é o resultado que queremos:

[source, python]
----
>>> v1 = Vector([3, 4, 5, 6])
>>> v3 = Vector([1, 2])
>>> v1 + v3
Vector([4.0, 6.0, 5.0, 6.0])
----

Dados esses requerimentos básicos, podemos implementar `+__add__+` como no <<ex_vector_add_t1>>.

[[ex_vector_add_t1]]
.Método `+Vector.__add__+`, versão #1
====
[source, python]
----
    # inside the Vector class

    def __add__(self, other):
        pairs = itertools.zip_longest(self, other, fillvalue=0.0)  # <1>
        return Vector(a + b for a, b in pairs)  # <2>
----
====
<1> `pairs` é um gerador que produz tuplas `(a, b)`, onde `a` vem de `self` e `b` de `other`. Se `self` e `other` tiverem tamanhos diferentes, `fillvalue` fornece os valores ausentes para o iterável mais curto.
<2> Um novo `Vector` é criado a partir de uma expressão geradora, produzindo uma soma para cada
`(a, b)` de `pairs`.

Observe como `+__add__+` devolve uma nova instância de `Vector`, sem modificar `self` ou `other`.

[WARNING]
====
Métodos especiais implementando operadores unários ou infixos não devem nunca modificar o valor dos operandos. Se espera que expressões com tais operandos produzam resultados criando novos objetos. Apenas operadores de atribuição aumentada podem modidifcar o primeiro operando (`self`), como discutido na <<augmented_assign_ops>>.
====

O <<ex_vector_add_t1>> permite somar um `Vector` a um `Vector2d`, e `Vector` a uma tupla ou qualquer iterável que produza números, como prova o <<ex_vector_add_demo_mixed_ok>>.

[[ex_vector_add_demo_mixed_ok]]
.Nossa versão #1 de `+Vector.__add__+` também aceita objetos diferentes de ++Vector++
====
[source, python]
----
>>> v1 = Vector([3, 4, 5])
>>> v1 + (10, 20, 30)
Vector([13.0, 24.0, 35.0])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> v1 + v2d
Vector([4.0, 6.0, 5.0])
----
====

Os dois usos de `+++` no <<ex_vector_add_demo_mixed_ok>> funcionam porque `+__add__+` usa
`zip_longest(…)`, capaz de consumir qualquer iterável, e a expressão geradora que cria um novo `Vector` simplemente efetua a operação `a + b` com os pares produzidos por `zip_longest(…)`, então um iterável que produza quaisquer itens numéricos servirá.

Entretanto, se trocarmos a ordem dos operandos (no <<ex_vector_add_demo_mixed_fail>>), a soma de tipos diferentes falha.

[[ex_vector_add_demo_mixed_fail]]
.A versão #1 de `+Vector.__add__+` falha com se o operador da esquerda não for um `Vector
====
[source, python]
----
>>> v1 = Vector([3, 4, 5])
>>> (10, 20, 30) + v1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "Vector") to tuple
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> v2d + v1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'Vector2d' and 'Vector'
----
====

Para suportar operações envolvendo objetos de tipos diferentes, Python implementa um mecanismo especial de despacho para os métodos especiais de operadores infixos. Dada a expressão `a + b`, o interpretador vai executar as seguintes etapas (veja também a <<operator_flowchart>>):

. Se `a` implementa `+__add__+`, invoca `+a.__add__(b)+` e devolve o resultado, a menos que seja `NotImplemented`.
. Se `a` não implementa `+__add__+`, ou a chamada devolve `NotImplemented`, verifica se `b` implementa `+__radd__+`, e então invoca `+b.__radd__(a)+` e devolve o resultado, a menos que seja `NotImplemented`.
. Se `b` não implementa `+__radd__+`, ou a chamada devolve `NotImplemented`, gera um `TypeError` com a mensagem 'unsupported operand types' (_tipos de operandos não suportados_).

[TIP]
====
O método `+__radd__+` é chamado de variante "reversa" ou "refletida" de `+__add__+`.
Adotei o termo geral "métodos especiais reversos".footnote:[A documentação de Python usa os dois termos. O https://fpy.li/dtmodel[capítulo "Modelo de Dados"] usa "refletido", mas em https://fpy.li/16-7["9.1.2.2. Implementando operações aritméticas"], a documentação do módulo menciona métodos de "adiante" (_forward_) e "reverso" (_reverse_), uma terminologia que considero melhor, pois "adiante" e "reverso" são claramente sentidos opostos, mas o oposto de "refletido" não é tão evidente.]
====

[[operator_flowchart]]
.Fluxograma para computar `a + b` com `+__add__+` e `+__radd__+`.
image::../images/flpy_1601.png[Fluxograma de operador]

Assim, para fazer as somas de tipos diferentes no <<ex_vector_add_demo_mixed_fail>> funcionarem, precisamos implementar o método `+Vector.__radd__+`, que Python vai invocar como alternativa, se o operando à esquerda não implementar `+__add__+`, ou se implementar mas devolver `NotImplemented`, indicando que não sabe como tratar o operando à direita.

[WARNING]
====
Não confunda `NotImplemented` com `NotImplementedError`. O primeiro é um valor _singleton_ especial, que um método especial de operador infixo deve devolver para informar o interpretador que não consegue tratar um dado operando. `NotImplementedError`, por outro lado, é um exceção que métodos _stub_ em classes abstratas podem gerar, para avisar que subclasses devem implementar tais métodos.
====

A implementação viável mais simples de `+__radd__+` aparece no <<ex_vector_add_t2>>.

[[ex_vector_add_t2]]
.Os  métodos `+__add__+` e `+__radd__+` de `Vector`
====
[source, python]
----
    # inside the Vector class

    def __add__(self, other):  # <1>
        pairs = itertools.zip_longest(self, other, fillvalue=0.0)
        return Vector(a + b for a, b in pairs)

    def __radd__(self, other):  # <2>
        return self + other
----
====
<1> Nenhuma mudança no `+__add__+` do <<ex_vector_add_t1>>; ele é listado aqui porque é usado por `+__radd__+`.
<2> `+__radd__+` apenas delega para `+__add__+`.

Muitas vezes, `+__radd__+` pode ser simples assim: apenas a invocação do operador apropriado, delegando para `+__add__+` neste caso. Isso se aplica para qualquer operador comutativo; `+` é comutativo quando lida com números ou com nossos vetores, mas não é comutativo ao concatenar sequências no Python.

Se `+__radd__+` apenas invoca `+__add__+`, aqui está outra forma de obter o mesmo efeito:

[source, python]
----
    def __add__(self, other):
        pairs = itertools.zip_longest(self, other, fillvalue=0.0)
        return Vector(a + b for a, b in pairs)

    __radd__ = __add__
----

Os métodos no <<ex_vector_add_t2>> funcionam com objetos `Vector` ou com qualquer iterável com itens numéricos, tal como um `Vector2d`, uma `tuple` de inteiros ou um `array` de números de ponto flutuante. Mas se alimentado com um objeto não-iterável, `+__add__+` gera uma exceção com uma mensagem não muito útil, como no <<ex_vector_error_iter>>.

[[ex_vector_error_iter]]
.O método `+Vector.__add__+` precisa de operandos iteráveis
====
[source, python]
----
>>> v1 + 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "vector_v6.py", line 328, in __add__
    pairs = itertools.zip_longest(self, other, fillvalue=0.0)
TypeError: zip_longest argument #2 must support iteration
----
====

E pior ainda, recebemos uma mensagem enganosa se um operando for iterável mas seus itens não puderem ser somados aos itens `float` no `Vector`. Veja o <<ex_vector_error_iter_not_add>>.

[[ex_vector_error_iter_not_add]]
.O método `+Vector.__add__+` precisa de um iterável com itens numéricos
====
[source, python]
----
>>> v1 + 'ABC'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "vector_v6.py", line 329, in __add__
    return Vector(a + b for a, b in pairs)
  File "vector_v6.py", line 243, in __init__
    self._components = array(self.typecode, components)
  File "vector_v6.py", line 329, in <genexpr>
    return Vector(a + b for a, b in pairs)
TypeError: unsupported operand type(s) for +: 'float' and 'str'
----
====

Tentei somar um `Vector` a uma `str`, mas a mensagem reclama de `float` e `str`.

Na verdade, os problemas no <<ex_vector_error_iter>> e no <<ex_vector_error_iter_not_add>> são mais profundos que meras mensagens de erro obscuras: se um método especial de operando não é capaz de devolver um resultado válido por incompatibilidade de tipos, ele deverua devolver `NotImplemented` e não gerar um `TypeError`. Ao devolver `NotImplemented`, a porta fica aberta para a implementação do operando do outro tipo executar a operação, quando Python tentar invocar o método reverso.

No espírito do _duck typing_, vamos nos abster de testar o tipo do operando `other` ou o tipo de seus elementos. Vamos capturar as exceções e devolver `NotImplemented`. Se o interpretador ainda não tiver invertido os operandos, tentará isso agora. Se a invocação do método reverso devolver `NotImplemented`, então Python irá gerar um `TypeError` com uma mensagem de erro padrão "unsupported operand type(s) for +: 'Vector' and 'str'” (_tipos de operandos não suportados para +: `Vector` e `str`_)

A implementação final dos métodos especiais de adição de `Vector` está no <<ex_vector_v6>>.

[[ex_vector_v6]]
.vector_v6.py: métodos do operador `+` adicionados a vector_v5.py (no <<ex_vector_v5>>)
====
[source, python]
----
include::../code/16-op-overloading/vector_v6.py[tags=VECTOR_V6_ADD]
----
====

Observe que agora `+__add__+` captura um `TypeError` e devolve `NotImplemented`.

[WARNING]
====
Se um método de operador infixo gera uma exceção, ele interrompe o algoritmo de despacho do operador. No caso específico de `TypeError`, geralmente é melhor capturar essa exceção e devolver `NotImplemented`. Isso permite que o interpretador tente chamar o método reverso do operador, que pode tratar corretamente a operação com operadores invertidos, se eles forem de tipos diferentes.
====

Agora que já sobrecarregamos o operador `+` com segurança, implementando `+__add__+` e `+__radd__+`, vamos enfrentar outro operador infixo: `*`.((("", startref="OOplus16")))((("", startref="Plusover16")))((("", startref="Voverload16")))

[[overloading_mul]]
=== Sobrecarregando * para multiplicação escalar

O((("operator overloading", "overloading &#x002A; for scalar multiplication", id="OOscalar16")))((("* (star) operator", id="starover16")))((("star (*) operator", id="staroverb16")))((("multiplication, scalar", id="Mscalar16"))) que significa `Vector([1, 2, 3]) * x`? Se `x` é um número, isso seria um produto escalar, e o resultado seria um novo `Vector` com cada componente multiplicado por `x`—também conhecida como multiplicação elemento a elemento (_elementwise multiplication_):

[source, python]
----
>>> v1 = Vector([1, 2, 3])
>>> v1 * 10
Vector([10.0, 20.0, 30.0])
>>> 11 * v1
Vector([11.0, 22.0, 33.0])
----

[NOTE]
====
Outro tipo de produto envolvendo operandos de `Vector` seria o _dot product_ (produto vetorial) de dois vetores—ou multiplicação de matrizes, se tomarmos um vetor como uma matriz de 1 &#x00D7; N e o outro como uma matriz de N &#x00D7; 1.
Vamos implementar esse operador em nossa classe `Vector` na <<matmul_operator_sec>>.
====

De volta a nosso produto escalar, começamos novamente com os métodos `+__mul__+` e `+__rmul__+` mais simples possíveis que possam funcionar:

[source, python]
----
    # inside the Vector class

    def __mul__(self, scalar):
        return Vector(n * scalar for n in self)

    def __rmul__(self, scalar):
        return self * scalar
----

Esses métodos funcionam, exceto quando recebem operandos incompatíveis. O argumento `scalar` precisa ser um número que, quando multiplicado por um `float`, produz outro `float` (porque nossa classe `Vector` usa, internamente, um `array` de números de ponto flutuante).
Então um número `complex` não serve, mas o escalar pode ser um `int`, um `bool` (porque `bool` é subclasse  de `int`) ou mesmo uma instância de `fractions.Fraction`.
No <<ex_vector_v7>>, o método `+__mul__+` não faz qualquer checagem de tipos explícita com `scalar`. Em vez disso, o converte em um `float`, e devolve `NotImplemented` se a conversão falha.
Esse é um exemplo claro de _duck typing_.

[[ex_vector_v7]]
.vector_v7.py: métodos do operador `*` adicionados
====
[source, python]
----
class Vector:
    typecode = 'd'

    def __init__(self, components):
        self._components = array(self.typecode, components)

    # many methods omitted in book listing, see vector_v7.py
    # in https://github.com/fluentpython/example-code-2e

    def __mul__(self, scalar):
        try:
            factor = float(scalar)
        except TypeError:  # <1>
            return NotImplemented  # <2>
        return Vector(n * factor for n in self)

    def __rmul__(self, scalar):
        return self * scalar  # <3>
----
====
<1> Se `scalar` não pode ser convertido para `float`...
<2> ...não temos como lidar com ele, então devolvemos `NotImplemented`, para permitir ao Python tentar `+__rmul__+` no operando `scalar`.
<3> Neste exemplo, `+__rmul__+` funciona bem apenas executando `self * scalar`, que delega a operação para o método `+__mul__+`.

Com o <<ex_vector_v7>>, é possível multiplicar um `Vector` por valores escalares de tipos numéricos comuns e não tão comuns:

[source, python]
----
>>> v1 = Vector([1.0, 2.0, 3.0])
>>> 14 * v1
Vector([14.0, 28.0, 42.0])
>>> v1 * True
Vector([1.0, 2.0, 3.0])
>>> from fractions import Fraction
>>> v1 * Fraction(1, 3)
Vector([0.3333333333333333, 0.6666666666666666, 1.0])
----

Agora que podemos multiplicar `Vector` por valores escalares, vamos ver como implementar o produto de um `Vector` por outro `Vector`.

[NOTE]
====
Na primeira edição de _Python Fluente_, usei _goose typing_ no <<ex_vector_v7>>: verificava o argumento `scalar` de `+__mul__+` com `isinstance(scalar, numbers.Real)`.
Agora eu evito usar as ABCs de `numbers`, por não serem suportadas pelas anotações de tipo introduzidas na PEP 484.
Usar durante a execução tipos que não podem ser também verificados de forma estática me parece uma má ideia.

Outra alternativa seria verificar com o protocolo `typing.SupportsFloat`, que vimos na <<runtime_checkable_proto_sec>>. Escolhi usar _duck typing_ naquele exemplo por achar que pythonistas fluentes devem se sentir confortáveis com esse padrão de programação.

Mas `+__matmul__+`, no <<ex_vector_v7_matmul>>, que é novo e foi escrito para essa segunda edição, é um bom exemplo de _goose typing_.((("", startref="starover16")))((("", startref="staroverb16")))((("", startref="OOscalar16")))((("", startref="Mscalar16")))
====

[[matmul_operator_sec]]
=== Usando @ como operador infixo

O símbolo `@`((("operator overloading", "using @ as infix operator", id="OOatsign16")))((("@ sign", id="atinfix16")))((("infix operators", id="infixop16"))) é bastante conhecido como o prefixo de decoradores de função,
mas desde 2015 ele também pode ser usado como um operador infixo.
Por anos, o produto escalar no NumPy foi escrito como `numpy.dot(a, b)`.
A notação de invocação de função faz com que fórmulas mais longas sejam difíceis de traduzir da notação matemática para Python,footnote:[Veja o <<operator_soapbox>> para uma discussão desse problema.]
então a comunidade de computação numérica fez campanha pela
https://fpy.li/pep465[PEP 465—A dedicated infix operator for matrix multiplication (_Um operador infixo dedicado para multiplicação de matrizes_)] (EN), que foi implementada no Python 3.5.
Hoje é possível escrever `a @ b` para computar o produto escalar de dois arrays da NumPy.

O operador `@` é suportado pelos métodos especiais `+__matmul__+`, `+__rmatmul__+`
e `+__imatmul__+`, cujos nomes derivam de "matrix multiplication" (_multiplicação de matrizes_).
Até Python 3.10, esses métodos não são usados em lugar algum na biblioteca padrão,
mas eles são reconhecidos pelo interpretador desde o Python 3.5,
então os desenvolvedores da NumPy--e o resto de nós--podemos implementar o operador `@` em nossas classes.
O analisador sintático de Python também foi modificado para aceitar o novo operador
(no Python 3.4, `a @ b` era um erro de sintaxe).

Os testes simples abaixo mostram como `@` deve funcionar com instâncias de `Vector`:

[source, python]
----
>>> va = Vector([1, 2, 3])
>>> vz = Vector([5, 6, 7])
>>> va @ vz == 38.0  # 1*5 + 2*6 + 3*7
True
>>> [10, 20, 30] @ vz
380.0
>>> va @ 3
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for @: 'Vector' and 'int'
----

O resultado de `va @ vz` no exemplo acima é o mesmo que obtemos no NumPy
fazendo o produto escalar de arrays com os mesmos valores:

[source, python]
----
>>> import numpy as np
>>> np.array([1, 2, 3]) @ np.array([5, 6, 7])
38
----


O <<ex_vector_v7_matmul>> mostra o código dos métodos especiais relevantes na classe `Vector`.


[[ex_vector_v7_matmul]]
.vector_v7.py: operator `@` methods
====
[source, python]
----
class Vector:
    # many methods omitted in book listing

    def __matmul__(self, other):
        if (isinstance(other, abc.Sized) and  # <1>
            isinstance(other, abc.Iterable)):
            if len(self) == len(other):  # <2>
                return sum(a * b for a, b in zip(self, other))  # <3>
            else:
                raise ValueError('@ requires vectors of equal length.')
        else:
            return NotImplemented

    def __rmatmul__(self, other):
        return self @ other
----
====
<1> Ambos os operandos precisam implementar `+__len__+` e `+__iter__+`...
<2> ...e ter o mesmo tamanho, para permitir...
<3> ...uma linda aplicação de `sum`, `zip` e uma expressão geradora.

[[zip_strict_tip]]
.O novo recurso de zip() no Python 3.10
[TIP]
====
Desde o Python 3.10, a função embutida `zip` aceita um argumento opcional apenas nomeado, `strict`. Quando `strict=True`, a função gera um `ValueError` se os iteráveis têm tamanhos diferentes. O default é `False`.
Esse novo comportamento estrito se alinha à filosofia de https://fpy.li/16-8[_falhar rápido_] de Python.
No <<ex_vector_v7_matmul>>, substituí o `if` interno por um `try/except ValueError` e acrescentei `strict=True` à invocação de `zip`.
====

O <<ex_vector_v7_matmul>> é um bom exemplo prático de _goose typing_.
Testar o operando `other` contra `Vector` negaria aos usuários a flexibilidade de usar listas ou arrays como operandos de `@`.
Desde que um dos operandos seja um `Vector`, nossa implementação de `@` suporta outros operandos que sejam instâncias de `abc.Sized` e `abc.Iterable`.
Ambas as ABCs implementam o `+__subclasshook__+`, portanto qualquer objeto que forneça `+__len__+` e `+__iter__+` satisfaz nosso teste—não há necessidade de criar subclasses concretas dessas ABCs ou sequer registrar-se com elas, como explicado na <<subclasshook_sec>>.
Em especial, nossa classe `Vector` não é subclasse nem de `abc.Sized` nem de `abc.Iterable`, mas passa os testes de `isinstance` contra aquelas ABCs, pois implementa os métodos necessários.

Vamos revisar os operadores aritméticos suportados pelo Python antes de mergulhar na categoria especial dos <<rich_comp_op_sec>>.((("", startref="atinfix16")))((("", startref="OOatsign16")))

=== Resumindo os operadores aritméticos

Ao implementar `+`, `*`, e `@`, vimos((("operator overloading", "infix operator method names"))) os padrões de programação mais comuns para operadores infixos.
As técnicas descritas são aplicáveis a todos os operadores listados na <<infix_operator_names_tbl>> (os operadores internos serão tratados em <<augmented_assign_ops>>).

[[infix_operator_names_tbl]]
.Nomes dos métodos de operadores infixos (os operadores internos são usados para atribuição aumentada; operadores de comparação estão na <<reversed_rich_comp_op_tbl>>)
[options="header"]
|=================================================================================================
| Operador  | Direto   | Reverso   | Interno  | Descrição
| `+`       | `+__add__+`   | `+__radd__+`  | `+__iadd__+`  | Adição ou concatenação
| `-`       | `+__sub__+`   | `+__rsub__+`  | `+__isub__+`  | Subtração
| `*`       | `+__mul__+`   | `+__rmul__+`  | `+__imul__+`  | Multiplicação ou repetição
| `/`       | `+__truediv__+`   | `+__rtruediv__+`  | `+__itruediv__+`  | Divisão exata (_True division_)
| `//`      | `+__floordiv__+`  | `+__rfloordiv__+`     | `+__ifloordiv__+`     | Divisão inteira (_Floor division_)
| `%`       | `+__mod__+`       | `+__rmod__+`  | `+__imod__+`  | Módulo
| `divmod()`| `+__divmod__+`    | `+__rdivmod__+`   | `+__idivmod__+`   | Devolve uma tupla com o quociente da divisão inteira e o módulo
| `**`, `pow()`   | `+__pow__+`   | `+__rpow__+`  | `+__ipow__+`  | Exponenciaçãofootnote:[`pow` pode receber um terceiro argumento opcional, `modulo`: `pow(a, b, modulo)`, também suportado pelos métodos especiais quando invocados diretamente (por exemplo, `+a.__pow__(b, modulo)+`).]
| `@`       | `+__matmul__+`    | `+__rmatmul__+`   | `+__imatmul__+`   | Multiplicação de matrizes
| `&`       | `+__and__+`   | `+__rand__+`  | `+__iand__+`  | E binário (bit a bit)
| \|        | `+__or__+`    | `+__ror__+`   | `+__ior__+`   | OU binário (bit a bit)
| `^`       | `+__xor__+`   | `+__rxor__+`  | `+__ixor__+`  | XOR binário (bit a bit)
| `<<`      | `+__lshift__+`    | `+__rlshift__+`   | `+__ilshift__+`   | Deslocamento de bits para a esquerda
| `>>`      | `+__rshift__+`    | `+__rrshift__+`   | `+__irshift__+`   | Deslocamento de bits para a direita
|=================================================================================================


Operadores de comparação cheia usam um conjunto diferente de regras.((("", startref="infixop16")))


[[rich_comp_op_sec]]
=== Operadores de comparação cheia

O((("operator overloading", "rich comparison operators", id="OOrich16")))((("rich comparison operators", id="richcomp16")))((("comparison operators", id="comop16")))((("&#x3D;&#x3D; (equality) operator")))((("!&#x3D; (not equal to) operator")))((("&lt; (less than) operator")))((("&lt;&#x3D; (less than or equal to) operator")))((("&gt; (greater than) operator")))((("&gt;&#x3D; (greater than or equal to) operator")))((("equality (&#x3D;&#x3D;) operator")))((("greater than (&gt;) operator")))((("greater than or equal to (&gt;&#x3D;) operator")))((("less than (&lt;) operator")))((("less than or equal to (&lt;&#x3D;) operator")))((("not equal to (!&#x3D;) operator"))) tratamento dos operadores de comparação cheia `==`, `!=`, `>`, `<`, `>=` e `<=` pelo interpretador Python é similar ao que já vimos, com duas importantes diferenças:

* O mesmo conjunto de métodos é usado para invocações diretas ou reversas do operador. As regras estão resumidas na <<reversed_rich_comp_op_tbl>>. Por exemplo, no caso de `==`, tanto a chamada direta quanto a reversa invocam `+__eq__+`, apenas permutando os argumentos; e uma chamada direta a
`+__gt__+` é seguida de uma chamada reversa a `+__lt__+`, com os argumentos permutados.
* Nos casos de `==` e `!=`, se o métodos reverso estiver ausente, ou devolver `NotImplemented`, Python vai comparar os IDs dos objetos em vez de gerar um `TypeError`.

[[reversed_rich_comp_op_tbl]]
.Operadores de comparação cheia: métodos reversos invocados quando a chamada inicial ao método devolve `NotImplemented`
[options="header"]
|=================================================================================================
| Grupo    | Operador infixo | Método de invocação direta | Método de invocação reversa | Alternativa
| Igualdade | `a == b`       | `+a.__eq__(b)+`       | `+b.__eq__(a)+`       | Devolve `id(a) == id(b)`
|          | `a != b`       | `+a.__ne__(b)+`       | `+b.__ne__(a)+`       | Devolve `not (a == b)`
| Ordenação | `a > b`        | `+a.__gt__(b)+`       | `+b.__lt__(a)+`       | Gera um `TypeError`
|          | `a < b`        | `+a.__lt__(b)+`       | `+b.__gt__(a)+`       | Gera um `TypeError`
|          | `a >= b`       | `+a.__ge__(b)+`       | `+b.__le__(a)+`       | Gera um `TypeError`
|          | `a <= b`       | `+a.__le__(b)+`       | `+b.__ge__(a)+`       | Gera um `TypeError`
|=================================================================================================

Dadas essas regras, vamos revisar e aperfeiçoar o comportamento do método `+Vector.__eq__+`, que foi escrito assim no __vector_v5.py__ (<<ex_vector_v5>>):

[source, python]
----
class Vector:
    # many lines omitted

    def __eq__(self, other):
        return (len(self) == len(other) and
                all(a == b for a, b in zip(self, other)))
----

Esse método produz os resultados do <<eq_initial_demo>>.

[[eq_initial_demo]]
.Comparando um `Vector` a um `Vector`, a um `Vector2d`, e a uma `tuple`
====
[source, python]
----
>>> va = Vector([1.0, 2.0, 3.0])
>>> vb = Vector(range(1, 4))
>>> va == vb  # <1>
True
>>> vc = Vector([1, 2])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> vc == v2d  # <2>
True
>>> t3 = (1, 2, 3)
>>> va == t3  # <3>
True
----
====
<1> Duas instâncias de `Vector` com componentes numéricos iguais são iguais.
<2> Um `Vector` e um `Vector2d` também são iguais se seus componentes são iguais.
<3> Um `Vector` também é considerado igual a uma `tuple` ou qualquer iterável com itens numéricos de valor igual.

O resultado no <<eq_initial_demo>> é provavelmente indesejável.
Queremos mesmo que um `Vector` seja considerado igual a uma `tuple` contendo os mesmos números?
Não tenho uma regra fixa sobre isso; depende do contexto da aplicação.
O "Zen of Python" diz:

[quote]
____
Em face da ambiguidade, rejeite a tentação de adivinhar.
____

Liberalidade excessiva na avaliação de operandos pode levar a resultados surpreendentes, e programadores odeiam surpresas.

Buscando inspiração no próprio Python, vemos que `[1,2] == (1, 2)` é `False`. Então, vamos ser conservadores e executar alguma checagem de tiposs. Se o segundo operando for uma instância de `Vector` (ou uma instância de uma subclasse de `Vector`), então usaremos a mesma lógica do
`+__eq__+` atual. Caso contrário, devolvemos `NotImplemented` e deixamos Python cuidar do caso. Veja o <<ex_vector_v8_eq>>.

[[ex_vector_v8_eq]]
.vector_v8.py: `+__eq__+` aperfeiçoado na classe `Vector`
====
[source, python]
----
include::../code/16-op-overloading/vector_v8.py[tags=VECTOR_V8_EQ]
----
====
<1> Se o operando `other` é uma instância de `Vector` (ou de uma subclasse de `Vector`), executa a comparação como antes.
<2> Caso contrário, devolve `NotImplemented`.

Rodando os testes do <<eq_initial_demo>> com o novo `+Vector.__eq__+` do <<ex_vector_v8_eq>>, obtemos os resultados que aparecem no <<eq_demo_new_eq>>.

[[eq_demo_new_eq]]
.Mesmas comparações do <<eq_initial_demo>>: o último resultado mudou
====
[source, python]
----
>>> va = Vector([1.0, 2.0, 3.0])
>>> vb = Vector(range(1, 4))
>>> va == vb  # <1>
True
>>> vc = Vector([1, 2])
>>> from vector2d_v3 import Vector2d
>>> v2d = Vector2d(1, 2)
>>> vc == v2d  # <2>
True
>>> t3 = (1, 2, 3)
>>> va == t3  # <3>
False
----
====
<1> Mesmo resultado de antes, como esperaado.
<2> Mesmo resultado de antes, mas por que? Explicação a seguir.
<3> Resultado diferente; era o que queríamos. Mas por que isso funciona? Continue lendo...

Dos três resultados no <<eq_demo_new_eq>>, o primeiro não é novidade, mas os dois últimos foram causados por `+__eq__+` devolver `NotImplemented` no <<ex_vector_v8_eq>>. Eis o que acontece no exemplo com um `Vector` e um `Vector2d`, `vc == v2d`, passo a passo:

. Para avaliar `vc == v2d`, Python invoca `Vector.__eq__(vc, v2d)`.
. `+Vector.__eq__(vc, v2d)+` verifica que `v2d` não é um `Vector` e devolve `NotImplemented`.
. Python recebe o resultado `NotImplemented`, então tenta
`+Vector2d.__eq__(v2d, vc)+`.
. `+Vector2d.__eq__(v2d, vc)+` transforma os dois operandos em tuplas e os compara: o resulltado é `True` (o código de `+Vector2d.__eq__+` está no <<ex_vector2d_v3_full>>).

Já para a comparação `va == t3`, entre `Vector` e `tuple` no <<eq_demo_new_eq>>, os passos são:

. Para avaliar `va == t3`, Python invoca `+Vector.__eq__(va, t3)+`.
. `+Vector.__eq__(va, t3)+` verifica que `t3` não é um `Vector` e devolve `NotImplemented`.
. Python recebe o resultado `NotImplemented`, e então tenta `+tuple.__eq__(t3, va)+`.
. `+tuple.__eq__(t3, va)+` não tem a menor ideia do que seja um `Vector`, então devolve `NotImplemented`.
. No caso especial de `==`, se a chamada reversa devolve `NotImplemented`, Python compara os IDs dos objetos, como último recurso.

Não precisamos implementar `+__ne__+` para `!=`, pois o comportamento alternativo do `+__ne__+` herdado de `object` nos serve:
quando `+__eq__+` é definido e não devolve `NotImplemented`, `+__ne__+` devolve o mesmo resultado negado.

Em outras palavras, dados os mesmos objetos que usamos no <<eq_demo_new_eq>>, os resultados para `!=` são consistentes:

[source, python]
----
>>> va != vb
False
>>> vc != v2d
False
>>> va != (1, 2, 3)
True
----

O `+__ne__+` herdado de `object` funciona como o código abaixo—exceto pelo original estar escrito em C:footnote:[A lógica para `+object.__eq__+` e
`+object.__ne__+` está na função `object_richcompare` em https://fpy.li/16-9[_Objects/typeobject.c_], no código-fonte do CPython.]

[source, python]
----
    def __ne__(self, other):
        eq_result = self == other
        if eq_result is NotImplemented:
            return NotImplemented
        else:
            return not eq_result
----

Vimos o básico da sobrecarga de operadores infixos.Vamos agora voltar nossa atenção para uma classe diferente de operador: os operadores de atribuição aumentada.((("", startref="comop16")))((("", startref="richcomp16")))((("", startref="OOrich16")))


[[augmented_assign_ops]]
=== Operadores de atribuição aumentada

Nossa((("operator overloading", "augmented assignment operators", id="OOaugmented16")))((("augmented assignment operators", id="augmented16")))((("&#x002B;&#x003D; (addition assignment) operator", id="addassigna16")))((("&#x002A;&#x3D; (star equals) operator", id="stareqa16")))((("addition assignment (&#x002B;&#x003D;) operator", id="adassb16")))((("star equals (&#x002A;&#x3D;) operator", id="stareqb16"))) classe `Vector` já suporta os operadores de atribuição aumentada `+=` e `*=`. Isso se dá porque a atribuição aumentada trabalha com recipientes imutáveis criando novas instâncias e re-vinculando a variável à esquerda do operador.

O <<eq_demo_augm_assign_immutable>> os mostra em ação.

[[eq_demo_augm_assign_immutable]]
.Usando `+=` e `*=` com instâncias de `Vector`
====
[source, python]
----
>>> v1 = Vector([1, 2, 3])
>>> v1_alias = v1  # <1>
>>> id(v1)  # <2>
4302860128
>>> v1 += Vector([4, 5, 6])  # <3>
>>> v1  # <4>
Vector([5.0, 7.0, 9.0])
>>> id(v1)  # <5>
4302859904
>>> v1_alias  # <6>
Vector([1.0, 2.0, 3.0])
>>> v1 *= 11  # <7>
>>> v1  # <8>
Vector([55.0, 77.0, 99.0])
>>> id(v1)
4302858336
----
====
<1> Cria um alias, para podermos inspecionar o objeto `Vector([1, 2, 3])` mais tarde.
<2> Verifica o ID do `Vector` inicial, vinculado a `v1`.
<3> Executa a adição aumentada.
<4> O resultado esperado...
<5> ...mas foi criado um novo `Vector`.
<6> Inspeciona `v1_alias` para confirmar que o `Vector` original não foi alterado.
<7> Executa a multiplicação aumentada.
<8> Novamente, o resultado é o esperado, mas um novo `Vector` foi criado.

Se uma classe não implementa os operadores internos listados na <<infix_operator_names_tbl>>, os operadores de atribuição aumentada funcionam como açúcar sintático: `a += b` é avaliado exatamente como `a = a + b`. Esse é o comportamento esperado para tipos imutáveis, e se você fornecer
`+__add__+`, então `+=` funcionará sem qualquer código adicional.

Entretanto, se você implementar um operador interno tal como
`+__iadd__+`, aquele método será chamado para computar o resultado de `a += b`.
Como indica seu nome, espera-se que esses operadores modifiquem internamente o operando da esquerdafootnote:[NT: O "i" nos nomes desses operadores se refere a "_in-place_"], e não criem um novo objeto como resultado.

[WARNING]
====
Os métodos especiais de atualização interna não devem nunca ser implementados para tipos imutáveis como nossa classe `Vector`.
Pode ser óbvio, mas vale a pena enfatizar.
====

Para mostrar o código de um operador de atualização interno, vamos estender a classe `BingoCage` do <<ex_tombola_bingo>> para implementar
`+__add__+` e `+__iadd__+`.

Vamos chamar a subclasse de `AddableBingoCage`. O <<demo_addable_bingo_add>> mostra o comportamento esperado para o operador `+`.

[[demo_addable_bingo_add]]
.O operador `+` cria uma nova instância de `AddableBingoCage`
====
[source, python]
----
include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_ADD_DEMO]
----
====
<1> Cria uma instância de `globe` com cinco itens (cada uma das `vowels`).
<2> Extrai um dos itens, e verifica que é uma das `vowels`.
<3> Confirma que `globe` tem agora quatro itens.
<4> Cria uma segunda instância, com três itens.
<5> Cria uma terceira instância pela soma das duas anteriores. Essa instância tem sete itens.
<6> Tentar adicionar uma `AddableBingoCage` a uma `list` falha com um `TypeError`. A mensagem de erro é produzida pelo interpretador de Python quando nosso método `+__add__+` devolve `NotImplemented`.

Como uma `AddableBingoCage` é mutável, o <<demo_addable_bingo_iadd>> mostra como ela funcionará quando implementarmos `+__iadd__+`.

[[demo_addable_bingo_iadd]]
.Uma `AddableBingoCage` existente pode ser carregada com `+=` (continuando do <<demo_addable_bingo_add>>)
====
[source, python]
----
include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO_IADD_DEMO]
----
====
<1> Cria um alias para podermos verificar a identidade do objeto mais tarde.
<2> `globe` tem quatro itens aqui.
<3> Uma instância de  `AddableBingoCage` pode receber itens de outra instância da mesma classe.
<4> O operador à diretia de `+=` também pode ser qualquer iterável.
<5> Durante todo esse exemplo, `globe` sempre se refere ao mesmo objeto que `globe_orig`.
<6> Tentar adicionar um não-iterável a uma `AddableBingoCage` falha com uma mensagem de erro apropriada.

Observe que o operador `++=+` é mais liberal que `+++` quanto ao segundo operando. Com `+++`, queremos que ambos os operandos sejam do mesmo tipo (nesse caso, `AddableBingoCage`), pois se aceitássemos tipos diferentes, isso poderia causar confusão quanto ao tipo do resultado. Com o `++=+`, a situação é mais clara: o objeto à esquerda do operador é atualizado internamente, então não há dúvida quanto ao tipo do resultado.

[TIP]
====
Eu validei os comportamentos diversos de `+` e `+=` observando como funciona o tipo embutido `list`. Ao escrever `my_list + x`, você só pode concatenar uma `list` a outra `list`, mas se você escrever `my_list += x`, você pode estender a lista da esquerda com itens de qualquer iterável `x` à direita do operador.
É assim que o método `list.extend()` funciona: ele aceita qualquer argumento iterável.
====

Agora que esclarecemos o comportamento desejado para `AddableBingoCage`, podemos examinar sua implementação no <<ex_addable_bingo>>.
Lembre-se que `BingoCage`, do <<ex_tombola_bingo>>, é uma subclasse concreta da ABC `Tombola` do <<ex_tombola_abc>>.

[[ex_addable_bingo]]
.bingoaddable.py: `AddableBingoCage` estende `BingoCage` para suportar `+` e `+=`
====
[source, python]
----
include::../code/16-op-overloading/bingoaddable.py[tags=ADDABLE_BINGO]
----
====
<1> `AddableBingoCage` estende `BingoCage`.
<2> Nosso `+__add__+` só vai funcionar se o segundo operando for uma instância de `Tombola`.
<3> Em `+__iadd__+`, obtém os itens de `other`, se ele for uma instância de `Tombola`.
<4> Caso contrário, tenta obter um iterador sobre `other`.footnote:[A função embutida `iter` será tratada no próximo capítulo. Eu poderia ter usado `tuple(other)` aqui, e isso funcionaria, ao custo de criar uma nova `tuple` quando tudo que o método `.load(…)` precisa é iterar sobre seu argumento.]
<5> Se aquilo falhar, gera uma exceção explicando o que o usuário deve fazer. Sempre que possível, mensagens de erro devem guiar o usuário explicitamente para a solução.
<6> Se chegamos até aqui, podemos carregar o `other_iterable` para `self`.
<7> Muito importante: os métodos especiais de atribuição aumentada de objetos mutáveis devem devolver `self`. É o que os usuários esperam.

Podemos resumir toda a ideia dos operadores de atualização internos comparando as instruções `return` que produzem os resultados em `+__add__+` e em `+__iadd__+` no <<ex_addable_bingo>>:

`+__add__+`:: O resultado é produzido chamando o construtor `AddableBingoCage` para criar uma nova instância.

`+__iadd__+`:: O resultado é produzido devolvendo `self`, após ele ter sido modificado.

Para concluir esse exemplo, uma última observação sobre o <<ex_addable_bingo>>: propositalmente, nenhum método `+__radd__+` foi incluído em `AddableBingoCage`, porque não há necessidade. O método direto `+__add__+` só vai lidar com operandos à direita do mesmo tipo, então se Python tentar computar `a + b`, onde `a` é uma `AddableBingoCage` e `b` não, devolvemos `NotImplemented`&#x2014;talvez a classe de `b` possa fazer isso funcionar. Mas se a expressão for `b + a` e `b` não for uma `AddableBingoCage`, e devolver `NotImplemented`, então é melhor deixar Python desistir e gerar um `TypeError`, pois não temos como tratar `b`.

[TIP]
====
De modo geral, se um método de operador infixo direto (por exemplo `+__mul__+`) for projetado para funcionar apenas com operandos do mesmo tipo de `self`, é inútil implementar o método reverso correspondente (por exemplo, `+__rmul__+`) pois, por definição, esse método só será invocado quando estivermos lidando com um operando de um tipo diferente.
====

Isso conclui nossa exploração de sobrecarga de operadores no Python.((("", startref="OOaugmented16")))((("", startref="augmented16")))((("", startref="addassigna16")))((("", startref="stareqa16")))((("", startref="adassb16")))((("", startref="stareqb16")))


=== Resumo do capítulo

Começamos((("operator overloading", "overview of"))) o capítulo revisando algumas restrições impostas pelo Python à sobrecarga de operadores: é proibido redefinir operadores nos próprios tipos embutidos, a sobrecarga está limitada aos operadores existentes, e alguns operadores não podem ser sobrecarregados (`is`, `and`, `or`, `not`).

Colocamos a mão na massa com os operadores unários, implementando `+__neg__+` e `+__pos__+`. A seguir vieram os operadores infixos, começando por `+`, suportado pelo método `+__add__+`. Vimos  que operadores unários e infixos devem produzir resultados criando novos objetos, sem nunca modificar seus operandos. Para suportar operações com outros tipos, devolvemos o valor especial `NotImplemented`—não uma exceção—permitindo ao interpretador tentar novamente permutando os operandos e chamando o método especial reverso para aquele operador (por exemplo, `+__radd__+`). O algoritmo usado pelo Python para tratar operadores infixos está resumido no fluxograma da <<operator_flowchart>>.

Misturar operandos de mais de um tipo exige detectar os operandos que não podemos tratar. Neste capitulo fizemos isso de duas maneiras: ao modo do _duck typing_, apenas fomos em frente e tentamos a operação, capturando uma exceção de `TypeError` se ela acontecesse; mais tarde, em `+__mul__+` e `+__matmul__+`, usamos um teste `isinstance` explícito. Há prós e contras nas duas abordagens: _duck typing_ é mais flexível, mas a verificação explícita de tipo é mais previsível.

De modo geral, bibliotecas deveriam tirar proveito do _duck typing_--abrindo a porta para objetos independente de seus tipos, desde que eles suportem as operações necessárias.
Entretanto, o algoritmo de despacho de operadores de Python pode produzir mensagens de erro enganosas ou resultados inesperados quando combinado com o _duck typing_.
Por essa razão, a disciplina da checagem de tipos com invocações de `isinstance` contra ABCs é muitas vezes útil quando escrevemos métodos especiais para sobrecarga de operadores.
Essa é a técnica batizada de _goose typing_ por Alex Martelli—como vimos na <<goose_typing_sec>>.
A _goose typing_ é um bom compromisso entre a flexibilidade e a segurança, porque os tipos definidos pelo usuário, existentes ou futuros, podem ser declarados como subclasses reais ou virtuais de uma ABC.
Além disso, se uma ABC implementa o `+__subclasshook__+`, objetos podem então passar por verificações com `isinstance` contra aquela ABC apenas fornecendo os métodos exigidos--sem necessidade de ser uma subclasse ou de se registrar com a ABC.

O próximo tópico tratado foram os operadores de comparação cheia. Implementamos `==` com
`+__eq__+` e descobrimos que Python oferece uma implementação conveniente de `!=` no `+__ne__+` herdado da classe base `object`. A forma como Python avalia esses operadores, bem como `>`, `<`, `>=`, e `<=`, é um pouco diferente, com uma lógica especial para a escolha do método reverso, e um tratamento alternativo para `==` e `!=` que nunca gera erros, pois Python compara os IDs dos objetos como último recurso.

Na última seção, nos concentramos nos operadores de atribuição aumentada.
Vimos que Python os trata, por default, como uma combinação do operador simples seguido de uma atribuição, isto é:
`a += b` é avaliado exatamente como `a = a + b`.
Isso sempre cria um novo objeto, então funciona para tipos mutáveis ou imutáveis.
Para objetos mutáveis, podemos implementar métodos especiais de atualização interna,
tal como `+__iadd__+` para `+=`, e alterar o valor do operando à esquerda do operador.
Para demonstrar isso na prática, deixamos para trás a classe imutável `Vector` e
trabalhamos na implementação de uma subclasse de `BingoCage`,
suportando `+=` para adicionar itens ao reservatório de itens para sorteio,
de modo similar à forma como o tipo embutido `list` suporta
`+=` como um atalho para o método `list.extend()`.
Enquanto fazíamos isso, discutimos como `+` tende a ser mais estrito que `+=` em relação aos tipos aceitos.
Para tipos de sequências, `+` normalmente exige que ambos os operandos sejam do mesmo tipo,
enquanto `+=` muitas vezes aceita qualquer iterável como o operando à direita do operador.

[[further_reading_op_sec]]
=== Para saber mais

Guido van Rossum((("operator overloading", "further reading on"))) escreveu uma boa apologia da sobrecarga de operadores em https://fpy.li/16-10["Why operators are useful" (_Porque operadores são úteis_)] (EN).
Trey Hunner postou https://fpy.li/16-11["Tuple ordering and deep comparisons in Python" (_Ordenação de tuplas e comparações profundas em Python_)] (EN), argumentando que os operadores de comparação cheia de Python são mais flexíveis e poderosos do que os programadores vindos de outras linguagens costumam pensar.

A sobrecarga de operadores é uma área da programação em Python onde testes com `isinstance` são comuns.
A melhor prática relacionada a tais testes é a _goose typing_, tratada na <<goose_typing_sec>>.
Se você pulou essa parte, se assegure de voltar lá e ler aquela seção.

A principal referência para os métodos especiais de operadores é o https://docs.python.org/pt-br/3/reference/datamodel.html[capítulo "Modelos de Dados"] na documentação de Python. Outra leitura relevante é https://docs.python.org/pt-br/3/library/numbers.html#implementing-the-arithmetic-operations["Implementando as operações aritméticas"] no módulo `numbers` da _Biblioteca Padrão de Python_.

Um exemplo brilhante de sobrecarga de operadores apareceu no pacote https://fpy.li/16-13[`pathlib`], adicionado no Python 3.4.
Sua classe `Path` sobrecarrega o operador `/` para construir caminhos do sistema de arquivos a partir de strings, como mostra o exemplo abaixo, da documentação:

[source, python]
----
>>> p = Path('/etc')
>>> q = p / 'init.d' / 'reboot'
>>> q
PosixPath('/etc/init.d/reboot')
----

Outro exemplo não aritmético de sobrecarga de operadores está na biblioteca https://fpy.li/16-14[Scapy], usada para "enviar, farejar, dissecar e forjar pacotes de rede".
Na Scapy, o operador `/` operator cria pacotes empilhando campos de diferentes camadas da rede. Veja https://fpy.li/16-15["Stacking layers" (_Empilhando camadas)] (EN) para mais detalhes.

Se você está prestes a implementar operadores de comparação, estude `functools.total_ordering`.
Esse é um decorador de classes que gera automaticamente os métodos para todos os operadores de comparação cheia em qualquer classe que defina ao menos alguns deles.
Veja a https://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering[documentação do módulo functools] (EN).

Se você tiver curiosidade sobre o despacho de métodos de operadores em linguagens com tipagem dinâmica, duas leituras fundamentais são https://fpy.li/16-17["A Simple Technique for Handling Multiple Polymorphism" (_Uma Técnica Simples para Tratar Polimorfismo Múltiplo_)] (EN), de Dan Ingalls (membro da equipe original de Smalltalk), e https://fpy.li/16-18["Arithmetic and Double Dispatching in Smalltalk-80" (_Aritmética e Despacho Duplo  em Smalltalk-80_)] (EN), de Kurt J. Hebel e Ralph Johnson (Johnson ficou famoso como um dos autores do livro _Padrões de Projetos_ original). Os dois artigos fornecem discussões profundas sobre o poder do polimorfismo em linguagens com tipagem dinâmica, como Smalltalk, Python e Ruby. Python não tem despacho duplo para tratar operadores, como descrito naqueles artigos. O algoritmo de Python, usando operadores diretos e reversos, é mais fácil de suportar por classes definidas pelo usuário que o despacho duplo, mas exige tratamento especial pelo interpretador. Por outro lado, o despacho duplo clássico é uma técnica geral, que pode ser usada no Python ou em qualquer linguagem orientada a objetos, para além do contexto específico de operadores infixos. E, de fato, Ingalls, Hebel e Johnson usam exemplos muito diferentes para descrever essa técnica.

O artigo https://fpy.li/16-1["The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling"(_A Família de Linguagens C: Entrevista com Dennis Ritchie, Bjarne Stroustrup, e James Gosling_)] (EN), da onde tirei a epígrafe desse capítulo, apareceu na _Java Report_, 5(7), julho de 2000, e na _{cpp} Report_, 12(7), julho/agosto de 2000, juntamente com outros trechos que usei no "Ponto de Vista" deste capítulo (abaixo).
Se você se interessa pelo projeto de linguagens de programação, faça um favor a si mesmo e leia aquela entrevista.

[[operator_soapbox]]
.Ponto de Vista
****

[role="soapbox-title"]
Sobrecarga de operadores: prós e contras

James Gosling, citado((("operator overloading", "Soapbox discussion")))((("Soapbox sidebars", "operator overloading"))) no início desse capítulo, tomou a decisão consciente de excluir a sobrecarga de operadores quando projetou o Java. Naquela mesma entrevista (https://fpy.li/16-20["The C Family of Languages: Interview with Dennis Ritchie, Bjarne Stroustrup, and James Gosling" (_A Família de Linguagens C: Entrevista com Dennis Ritchie, Bjarne Stroustrup, e James Gosling_)] (EN)) ele diz:

[quote]
____
Provavelmente uns 20 a 30 porcento da população acha que sobrecarga de operadores é uma criação demoníaca; alguém fez algo com sobrecarga de operadores que realmente os tirou do sério, porque eles usaram algo como + para inserção em listas, e isso torna a vida muito, muito confusa. Muito daquele problema vem do fato de existirem apenas uma meia dúzia de operadores que podem ser sobrecarregados de forma razoável, mas existem milhares ou milhões de operadores que as pessoas gostariam de definir—então é preciso escolher, e muitas vezes as escolhas entram em conflito com a sua intuição.
____

Guido van Rossum escolheu o caminho do meio no suporte à sobrecarga de operadores: ele não deixou a porta aberta para que os usuários criassem novos operadores arbitrários como `<=>` ou `:-)`, evitando uma Torre de Babel de operadores customizados, e permitindo ao analisador sintático de Python permanecer simples. Python também não permite a sobrecarga dos operadores de tipos embutidos, outra limitação que promove a legibilidade e o desempenho previsível.

Gosling continua:

[quote]
____
E então há uma comunidade de aproximadamente 10 porcento que havia de fato usado a sobrecarga de operadores de forma apropriada, e que realmente gostavam disso, e para quem isso era realmente importante; essas são quase exclusivamente pessoas que fazem trabalho numérico, onde a notação é muito importante para avivar a intuição [das pessoas], porque elas chegam ali com uma intuição sobre o que + significa, e a capacidade de dizer "a + b", onde a e b são números complexos ou matrizes ou alguma outra coisa, realmente faz sentido.
____

Claro, há benefícios em não permitir a sobrecarga de operadores em uma linguagem. Já ouvi o argumento que C é melhor que C&#43;&#43; para programação de sistemas, porque a sobrecarga de operadores em C&#43;&#43; pode fazer com que operações dispendiosas pareçam triviais.
Duas linguagens modernas bem sucedidas, que compilam para executáveis binários, fizeram escolhas opostas: Go não tem sobrecarga de operadores, https://fpy.li/16-21[Rust tem].

Mas operadores sobrecarregados, quando usados de forma sensata, tornam o código mais fácil de ler e escrever. É um ótimo recurso em uma linguagem de alto nível moderna.

[role="soapbox-title"]
Um vislumbre da avaliação preguiçosa

Se você olhar de perto o _traceback_ no <<ex_vector_error_iter_not_add>>, vai encontrar evidências da avaliação https://fpy.li/16-22[_preguiçosa_] de expressões geradoras. O <<ex_vector_error_iter_not_add_repeat>> é o mesmo _traceback_, agora com explicações.

[[ex_vector_error_iter_not_add_repeat]]
.Mesmo que o <<ex_vector_error_iter_not_add>>
====
[source, python]
----
>>> v1 + 'ABC'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "vector_v6.py", line 329, in __add__
    return Vector(a + b for a, b in pairs)  # <1>
  File "vector_v6.py", line 243, in __init__
    self._components = array(self.typecode, components)  # <2>
  File "vector_v6.py", line 329, in <genexpr>
    return Vector(a + b for a, b in pairs)  # <3>
TypeError: unsupported operand type(s) for +: 'float' and 'str'
----
====
<1> A chamada a `Vector` recebe uma expressão geradora como seu argumento  `components`. Nenhum problema nesse estágio.
<2> A genexp `components` é passada para o construtor de `array`. Dentro do construtor de `array`, Python tenta iterar sobre a genexp, causando a avaliação do primeiro item `a + b`. É quando ocorre o `TypeError`.
<3> A exceção se propaga para a chamada ao construtor de `Vector`, onde é relatada.

Isso mostra como a expressão geradora é avaliada no último instante possível, e não onde é definida no código-fonte.

Se, por outro lado, o construtor de `Vector` fosse invocado como
`Vector([a + b for a, b in pairs])`, então a exceção ocorreria bem ali, porque a compreensão de lista tentou criar uma `list` para ser passada como argumento para a chamada a `Vector()`. O corpo de `+Vector.__init__+` nunca seria alcançado.

O <<ch_generators>> vai tratar das expressões geradoras em detalhes, mas não eu queria deixar essa demonstração acidental de sua natureza preguiçosa passar desapercebida.

****
