Sobrecarga de Operadores em Python
O Python permite definir operadores internos para suas classes, como adição, subtração, entre outros. Para isso, o módulo operator contém uma série de funções:
| Operação | Sintaxe | Função | 
|---|---|---|
| Adição | a + b | __add__(a, b) | 
| União | seq1 + seq2 | __concat__(seq1, seq2) | 
| Verificação de presença | obj in seq | __contains__(seq, obj) | 
| Divisão real | a / b | __truediv__(a, b) | 
| Divisão inteira | a // b | __floordiv__(a, b) | 
| Operação bit a bit AND | a & b | __and__(a, b) | 
| Operação bit a bit XOR | a ^ b | __xor__(a, b) | 
Para ver a lista completa de operadores e funções, você pode consultar a documentação oficial.
Para definir um operador específico para uma classe, ela deve implementar a função correspondente. Por exemplo, para definir o operador de adição, implementamos a função __add__() dentro da classe. Veja o exemplo abaixo:
class Counter:
    def __init__(self, value):
        self.value = value
    # redefinindo o operador de adição
    def __add__(self, other):
        return Counter(self.value + other.value)
counter1 = Counter(5)
counter2 = Counter(15)
counter3 = counter1 + counter2
print(counter3.value)       # Saída: 20Neste exemplo, a classe Counter possui o atributo value, representando um número específico. Definimos o operador de adição para o tipo Counter usando a função __add__. Isso permite somar dois objetos Counter, retornando um novo objeto Counter com a soma dos valores dos atributos value dos objetos envolvidos.
Essa implementação não é a única forma de usar o operador de adição. No exemplo anterior, o segundo parâmetro era outro objeto Counter. No entanto, é possível que o segundo parâmetro seja de outro tipo. Por exemplo, caso desejemos somar um objeto Counter a um número:
class Counter:
    def __init__(self, value):
        self.value = value
    def __add__(self, other):
        return Counter(self.value + other)
counter1 = Counter(5)
counter3 = counter1 + 6
print(counter3.value)       # Saída: 11Agora, o segundo parâmetro representa um número comum. Ainda assim, a função __add__ retorna um novo objeto Counter com o valor atualizado.
O tipo retornado pelos operadores também não precisa ser estritamente um objeto da classe. Podemos, por exemplo, retornar apenas um número:
class Counter:
    def __init__(self, value):
        self.value = value
    def __add__(self, other):
        return self.value + other
counter1 = Counter(5)
result = counter1 + 7
print(result)       # Saída: 12Veracidade de um Objeto
A definição da função __bool__ permite estabelecer a veracidade de um objeto ou convertê-lo implicitamente para True ou False. Veja o exemplo:
class Counter:
    def __init__(self, value):
        self.value = value
    def __bool__(self):
        return self.value > 0
def test(counter):
    if counter:
        print("Counter = True")
    else:
        print("Counter = False")
counter1 = Counter(3)
test(counter1)              # Saída: Counter = True
counter2 = Counter(-3)
test(counter2)              # Saída: Counter = FalseAqui, consideramos que, se value for menor que 1, o objeto Counter será False. Para valores positivos, ele será True. Dessa forma, podemos usar Counter em estruturas condicionais e de repetição, como no exemplo abaixo, onde usamos um objeto Counter como condição em um loop while:
class Counter:
    def __init__(self, value):
        self.value = value
    def __bool__(self):
        return self.value > 0
counter1 = Counter(3)
while counter1:
    print("Counter1:", counter1.value)
    counter1.value -= 1Aqui, o loop while será executado enquanto counter1 tiver valor verdadeiro (enquanto value for maior que zero).
Operadores que Retornam Valor Booleano
Algumas operações retornam um valor lógico True ou False, como as operações de comparação:
class Counter:
    def __init__(self, value):
        self.value = value
    def __gt__(self, other):
        return self.value > other.value
    def __lt__(self, other):
        return self.value < other.value
counter1 = Counter(1)
counter2 = Counter(2)
if counter1 > counter2:
    print("counter1 é maior que counter2")
elif counter1 < counter2:
    print("counter1 é menor que counter2")
else:
    print("counter1 e counter2 são iguais")No exemplo acima, o operador > é implementado pela função __gt__() e < pela função __lt__(). Esses operadores permitem comparar o atributo value entre dois objetos Counter.
Operadores de Indexação
Certos operadores permitem acessar um objeto por meio de índice usando colchetes ([]), como em obj[index]. Esses operadores são geralmente usados para coleções, mas podem ser aplicados a qualquer objeto. Veja um exemplo de indexação personalizada para acessar atributos específicos:
class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    def __getitem__(self, prop):
        if prop == "name": return self.__name
        elif prop == "age": return self.__age
        return None
tom = Person("Tom", 39)
print("Name:", tom["name"])     # Name: Tom
print("Age:", tom["age"])       # Age: 39
print("Id:", tom["id"])         # Id: NoneAqui, a classe Person possui dois atributos privados, __name e __age. A função __getitem__() permite acessar esses atributos com índices personalizados, como "name" ou "age", retornando None se o índice não corresponder a um atributo válido.
Verificação de Existência com o Operador in
O operador in pode verificar a presença de um valor em uma sequência ou coleção:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __contains__(self, prop):
        if prop == "name" or prop == "age": return True
        return False
tom = Person("Tom", 39)
print("name" in tom)        # True
print("id" in tom)          # FalseA função __contains__() implementa o operador in, verificando se a string dada representa um atributo da classe. Nesse caso, "name" in tom retorna True, enquanto "id" in tom retorna False.
Implementação de Operadores em Pares
Alguns operadores de comparação, como == e !=, são convenientes de serem implementados em pares, aproveitando um operador para definir o outro, evitando duplicação de lógica:
class Counter:
    def __init__(self, value):
        self.value = value
    def __eq__(self, other): return self.value == other.value
    def __ne__(self, other): return not (self == other)
    def __gt__(self, other): return self.value > other.value
    def __le__(self, other): return not (self > other)
    def __lt__(self, other): return self.value < other.value
    def __ge__(self, other): return not (self < other)
c1 = Counter(1)
c2 = Counter(2)
print(c1 == c2)     # False
print(c1 != c2)     # True
print(c1 < c2)      # True
print(c1 >= c2)     # FalseNeste exemplo, o operador != inverte o resultado do operador ==, e os operadores >= e <= são definidos como a negação dos operadores < e q, respectivamente. Isso permite uma implementação mais enxuta e evita duplicação de código.
Conclusão
Esses métodos de sobrecarga de operadores no Python permitem definir comportamentos customizados e enriquecem a experiência com objetos de classes personalizadas, tornando-as mais intuitivas para comparação, verificação de existência e acesso direto a propriedades.