1 of 25

Sobrecarga de operadores e igualdade

Programação

Orientada a Objetos

(com Python)

Prof. Rodrigo Rocha <rodrigorgs@ufba.br>

Instituto de Computação

Universidade Federal da Bahia

2 of 25

Sobrecarga de operadores

3 of 25

Operadores

Python define vários operadores, como +, -, /, //, %, *, ==, !=, <, >, <=, >=, =, +=, -=, *=, /=, //=, %=, dentre outros

3

4 of 25

Operadores são açúcar sintático

Operadores são apenas uma forma amigável de escrever chamadas a métodos

4

Usando operadores

Usando nome do método

a + b

a.__add__(b)

a - b

a.__sub__(b)

a == b

a.__eq__(b)

str(x)

x.__str__()

5 of 25

Sobrecarga de operadores

Ao criar uma classe, você pode redefinir esses métodos, efetivamente definindo o código a ser executado quando os operadores são executados com objetos da sua classe

Essa redefinição é chamada de sobrecarga de operadores.

5

6 of 25

Exemplo: sobrecarga�da adição (+)

6

class Fruta:

def __init__(self, nome):

self.nome = nome

def __add__(self, outra_fruta):

prefixo = self.nome[0:4]

sufixo = outra_fruta.nome[3:]

return Fruta(prefixo + sufixo)

abacate = Fruta('abacate')

banana = Fruta('banana')

print(banana + abacate)

7 of 25

Exemplo:

string

7

class Pessoa:

def __init__(self, nome, sobrenome):

self.nome = nome

self.sobrenome = sobrenome

def __repr__(self):

return self.nome + ' ' + self.sobrenome

pessoa = Pessoa('Fulano', 'de tal')

print(pessoa)

Equivalente a

print(str(pessoa))

Retorna a representação do objeto como string

Ver também: __str__

8 of 25

Igualdade

9 of 25

Motivação

  • Em várias situações precisamos comparar objetos para saber se eles são iguais
  • Exemplo: determinar se um elemento está em uma lista

9

lista = ['a', 'b', 'c']

if 'c' in lista:

print('Pertence')

Compara 'e' com cada elemento da lista:�'c' == 'a' ⇒ False�'c' == 'b' ⇒ False

'c' == 'c' ⇒ True

10 of 25

Exemplo

  • Qual a saída do programa?
  • Visualize a execução do programa no PythonTutor.com

10

class Aluno:

def __init__(self, matricula, nome):

self.matricula = matricula

self.nome = nome

lista = [

Aluno('111', 'Fulana'),

Aluno('222', 'Sicrano')

]

a = Aluno('111', 'Fulana')

if a in lista:

print('Presente')

11 of 25

Operador == (__eq__)

  • Por padrão, a == b retorna True somente quando id(a) == id(b) — isto é, a e b são o mesmo objeto
  • Se quiser, você pode redefinir essa lógica; para isso, implemente o método __eq__
  • Ou seja, você define o critério para determinar se dois objetos da sua classe são considerados iguais

11

12 of 25

Exemplo com

__eq__

12

class Aluno:

def __init__(self, matricula, nome):

self.matricula = matricula

self.nome = nome

def __eq__(self, outro):

if isinstance(outro, Aluno):

return self.matricula == outro.matricula

else:

return False

isinstance(obj, classe) retorna True somente se obj é uma instância da classe classe

13 of 25

Exemplo

Considerando a classe definida anteriormente, qual a saída do programa ao lado?

13

lista = [

Aluno('111', 'Fulana'),

Aluno('222', 'Sicrano')

]

a = Aluno('111', 'XYZ')

if a in lista:

print('Presente')

14 of 25

Código hash (__hash__)

  • Para usar objetos de sua classe como chave de um dicionário ou inseri-los em um conjunto (set), dentre outras situações, a classe deve implementar o método __hash__, que retorna o código hash do objeto (se não, dá erro TypeError: unhashable type)
  • O código hash é um número, gerado de forma que, se dois objetos são considerados iguais, eles devem ter o mesmo código hash (mas o inverso nem sempre é verdadeiro)
  • O uso de um código hash permite usar uma estrutura de dados chamada tabela hash, que realiza buscas de forma muito rápida

14

15 of 25

Exemplo

  • No caso de Aluno, podemos usar o hash da matrícula
  • Se a igualdade levasse em conta matrícula e nome, poderíamos retornar o hash da tupla (self.matricula, self.nome)

15

class Aluno:

def __init__(self, matricula, nome):

self.matricula = matricula

self.nome = nome

def __hash__(self):

return hash(self.matricula)

def __eq__(self, outro):

if isinstance(outro, Aluno):

return self.matricula == outro.matricula

else:

return False

hash(x) equivale a x.__hash__()

16 of 25

Código hash e imutabilidade

  • Apenas objetos imutáveis devem ter código hash
  • Afinal, como garantir que um conjunto não tem objetos iguais se é possível modificar objetos após inseri-los no conjunto?

16

17 of 25

Ordenação

18 of 25

Método sort

  • A classe list define e implementa o método sort, que ordena a lista
  • Para isso, o método compara elementos da lista dois a dois
  • Ao criarmos uma classe, devemos redefinir os operadores de comparação* para possibilitar a ordenação de listas com instâncias da nossa classe

18

Operador

Método

==

__eq__

<

__lt__

<=

__le__

>

__gt__

>=

__ge__

* Na prática o método sort só usa == e <, mas devemos implementar os demais comparadores para tornar nosso código resiliente a mudanças na implementação do sort

19 of 25

Exemplo

  • Ao usar o decorator total_ordering, basta definir == e um dos outros comparadores (<, <=, > ou >=) e os demais são gerados automaticamente
  • Se não forem definidos os operadores de comparação, dá erro TypeError: '<' not supported between instances of 'Aluno' and 'Aluno'

19

from functools import total_ordering

@total_ordering

class Aluno:

def __init__(self, matricula, nome):

self.matricula = matricula

self.nome = nome

def __lt__(self, outro):

if isinstance(outro, Aluno):

return self.matricula < outro.matricula

return NotImplemented

def __eq__(self, outro):

if isinstance(outro, Aluno):

return self.matricula == outro.matricula

return False

20 of 25

Data classes

21 of 25

Data classes

Data classes foram introduzidas no Python 3.7 e permitem escrever classes de forma sucinta, gerando automaticamente métodos como __init__, __eq__, __repr__, dentre outros (a depender das opções fornecidas)

21

22 of 25

Exemplo

  • A lista de atributos é declarada dentro da classe, indicando o tipo do atributo e, opcionalmente, valor padrão
  • frozen=True: torna os objetos imutáveis e gera __hash__ com base em todos os atributos
  • order=True: gera métodos de comparação considerando todos os atributos, em ordem

Note que a implementação automaticamente gerada leva em consideração todos os atributos e nem sempre é isso que desejamos (nesse caso específico, gostaríamos de usar matrícula para verificar igualdade e ordem)

22

from dataclasses import dataclass

@dataclass(frozen=True, order=True)

class Aluno:

matricula: str

nome: str = "Aluno"

23 of 25

Tópicos avançados

  • Cuidados com valores padrão mutáveis. No exemplo, se adicionarmos turmas: list = [], todos os objetos compartilharão a mesma lista; use field(default_factory=list) (from dataclasses import field)
  • A função field pode receber argumentos adicionais que alteram a implementação automática dos métodos, como compare=False, hash=False, repr=False
  • Ver https://youtu.be/vBH6GRJ1REM?t=327

23

24 of 25

Conclusão

25 of 25

Contexto

A depender do contexto onde queremos usar os objetos das classes que definimos, podemos precisar sobrecarregar alguns operadores:

  • __repr__ ou __str__: imprimir objetos
  • __eq__: comparar dois objetos
  • __hash__: usar em set ou chave de dict
  • __lt__ e __eq__: ordenar lista de objetos

25