Pular para o conteúdo

Como Usar Python facilmente para Iniciantes?

[

Operador e Sobrecarga de Funções em Classes Python Personalizadas

por Malay Agarwal

O Modelo de Dados do Python

Digamos que você tenha uma classe que represente um pedido online, contendo um carrinho (uma lista) e um cliente (uma string ou uma instância de outra classe que representa um cliente).

Nesse caso, é natural querer obter o tamanho da lista de carrinho. Alguém novo no Python pode decidir implementar um método chamado get_cart_len() em sua classe para fazer isso. Mas é possível configurar a função embutida len() de forma que ela retorne o comprimento da lista de carrinho quando receber nosso objeto como argumento.

Outro exemplo seria quando queremos adicionar algo ao carrinho. Novamente, alguém novo no Python pode pensar em implementar um método chamado append_to_cart() que recebe um item e o adiciona à lista de carrinho. Mas é possível configurar o operador + de forma que ele adicione um novo item ao carrinho.

O Python faz tudo isso usando métodos especiais. Esses métodos especiais permitem que você substitua o comportamento padrão dos operadores e funções embutidas para trabalhar com os objetos da sua classe personalizada.

Os Detalhes das Operações como len() e []

Os objetos em Python têm a capacidade de responder a várias operações internas, como len(), acessar elementos por índice ([]) e outras. Por exemplo, podemos ter uma lista de inteiros e obter o tamanho dessa lista usando a função len(). No entanto, como usar a mesma função em uma classe personalizada?

Ao usar a função len() em uma classe personalizada, o Python procura pelo método especial __len__() no objeto e retorna o resultado desse método. Se não houver um método __len__() definido, um erro será gerado.

Podemos personalizar o comportamento da função len() em nossa classe personalizada definindo o método __len__() na classe. Vamos ver um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
cart = Cart()
cart.items = [1, 2, 3, 4, 5]
print(len(cart)) # Output: 5

No exemplo acima, definimos o método __len__() em nossa classe Cart que retorna o comprimento da lista items. Agora, quando chamamos a função len() em um objeto Cart, o Python chama o método __len__() definido na classe e retorna o resultado.

Sobrecarga de Funções Embutidas

Além da função len(), podemos sobrecarregar outras funções embutidas do Python para trabalhar com objetos de nossa classe personalizada.

Dando um Tamanho aos Objetos Usando len()

Já vimos como sobrecarregar a função embutida len(). Agora, vamos ver como podemos fazer isso com outras funções embutidas.

Digamos que queremos que nossa classe Cart possa ser convertida em uma string usando a função str() embutida. Podemos fazer isso definindo o método __str__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
cart = Cart()
cart.items = [1, 2, 3, 4, 5]
print(str(cart)) # Output: Cart with 5 items

Neste exemplo, definimos o método __str__() em nossa classe Cart que retorna uma string formatada para ser exibida quando chamamos a função str() em um objeto Cart.

Fazendo os Objetos Funcionarem com abs()

Podemos sobrecarregar a função embutida abs() para trabalhar com nossos objetos personalizados. Por exemplo, digamos que queremos calcular o valor absoluto do total de itens no carrinho. Podemos fazer isso definindo o método __abs__() em nossa classe Cart. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
cart = Cart()
cart.items = [1, 2, 3, 4, 5]
print(abs(cart)) # Output: 5

No exemplo acima, definimos o método __abs__() em nossa classe Cart que calcula o valor absoluto da quantidade de itens no carrinho. Agora, quando chamamos a função abs() em um objeto Cart, o Python chama o método __abs__() definido na classe e retorna o resultado.

Imprimindo os Objetos de Forma Bonita Usando repr()

Podemos sobrecarregar a função embutida repr() para fornecer uma representação mais amigável dos nossos objetos personalizados. Por exemplo, digamos que queremos que nosso objeto Cart seja impresso de forma bonita ao usar a função repr(). Podemos fazer isso definindo o método __repr__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
def __repr__(self):
return f"Cart({self.items})"
cart = Cart()
cart.items = [1, 2, 3, 4, 5]
print(repr(cart)) # Output: Cart([1, 2, 3, 4, 5])

Neste exemplo, definimos o método __repr__() em nossa classe Cart que retorna uma representação do objeto em forma de string. Agora, quando chamamos a função repr() em um objeto Cart, o Python chama o método __repr__() definido na classe e retorna o resultado.

Tornando os Objetos Falsos ou Verdadeiros Usando bool()

Podemos sobrecarregar a função embutida bool() para determinar se nossos objetos personalizados são falsos ou verdadeiros. Por exemplo, digamos que queremos que nosso objeto Cart seja considerado verdadeiro apenas se houver pelo menos um item no carrinho. Podemos fazer isso definindo o método __bool__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
def __repr__(self):
return f"Cart({self.items})"
def __bool__(self):
return len(self.items) > 0
cart_empty = Cart()
cart_empty.items = []
cart_with_items = Cart()
cart_with_items.items = [1, 2, 3, 4, 5]
print(bool(cart_empty)) # Output: False
print(bool(cart_with_items)) # Output: True

Neste exemplo, definimos o método __bool__() em nossa classe Cart que retorna True se houver pelo menos um item no carrinho e False se não houver nenhum item. Agora, quando chamamos a função bool() em um objeto Cart, o Python chama o método __bool__() definido na classe e retorna o resultado.

Sobrecarga de Operadores Embutidos

Além das funções embutidas, também podemos sobrecarregar operadores embutidos para trabalhar com nossos objetos personalizados.

Tornando os Objetos Capazes de Serem Somados Usando +

Podemos sobrecarregar o operador + para adicionar objetos da nossa classe personalizada. Por exemplo, digamos que queremos adicionar dois objetos Cart para obter um único carrinho com todos os itens. Podemos fazer isso definindo o método __add__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
def __repr__(self):
return f"Cart({self.items})"
def __bool__(self):
return len(self.items) > 0
def __add__(self, other):
new_cart = Cart()
new_cart.items = self.items + other.items
return new_cart
cart1 = Cart()
cart1.items = [1, 2, 3]
cart2 = Cart()
cart2.items = [4, 5, 6]
combined_cart = cart1 + cart2
print(combined_cart.items) # Output: [1, 2, 3, 4, 5, 6]

No exemplo acima, definimos o método __add__() em nossa classe Cart que retorna um novo objeto Cart contendo todos os itens dos carrinhos somados. Agora, quando usamos o operador + entre dois objetos Cart, o Python chama o método __add__() definido na classe e retorna o novo carrinho combinado.

Atalhos: o Operador +=

Podemos fornecer um atalho para a operação de adição ao implementar o operador += em nossa classe personalizada. Por exemplo, digamos que queremos adicionar um item a um carrinho de forma mais conveniente, usando o operador +=. Podemos fazer isso definindo o método __iadd__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
def __repr__(self):
return f"Cart({self.items})"
def __bool__(self):
return len(self.items) > 0
def __add__(self, other):
new_cart = Cart()
new_cart.items = self.items + other.items
return new_cart
def __iadd__(self, item):
self.items.append(item)
return self
cart = Cart()
cart += 1
cart += 2
cart += 3
print(cart.items) # Output: [1, 2, 3]

No exemplo acima, definimos o método __iadd__() em nossa classe Cart que adiciona um item à lista de carrinho e retorna o próprio objeto Cart. Agora, quando usamos o operador += em um objeto Cart, o Python chama o método __iadd__() definido na classe e realiza a operação de adição diretamente no objeto.

Indexando e Fatiando Objetos Usando []

Podemos sobrecarregar o operador [] para indexar e fatiar objetos de nossa classe personalizada. Por exemplo, digamos que queremos acessar um item específico no carrinho ou obter uma parte do carrinho como uma nova lista. Podemos fazer isso definindo os métodos __getitem__() e __setitem__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
def __repr__(self):
return f"Cart({self.items})"
def __bool__(self):
return len(self.items) > 0
def __add__(self, other):
new_cart = Cart()
new_cart.items = self.items + other.items
return new_cart
def __iadd__(self, item):
self.items.append(item)
return self
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, index, value):
self.items[index] = value
cart = Cart()
cart.items = [1, 2, 3, 4, 5]
print(cart[2]) # Output: 3
cart[2] = 10
print(cart.items) # Output: [1, 2, 10, 4, 5]

No exemplo acima, definimos os métodos __getitem__() e __setitem__() em nossa classe Cart que permitem o acesso aos itens da lista de carrinho usando o operador []. Além disso, modificamos o valor de um item específico no carrinho definindo um novo valor usando o operador [] acompanhado pelo operador =.

Operadores Reversos: Tornando as Classes Matematicamente Corretas

Podemos sobrecarregar operadores reversos para tornar nossas classes compatíveis com operações matemáticas. Por exemplo, digamos que queremos que nosso objeto Cart possa ser adicionado a um número inteiro ou a uma lista. Podemos fazer isso definindo os métodos __radd__() e __radd__() em nossa classe. Veja um exemplo:

class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Cart with {len(self)} items"
def __abs__(self):
return abs(len(self.items))
def __repr__(self):
return f"Cart({self.items})"
def __bool__(self):
return len(self.items) > 0
def __add__(self, other):
new_cart = Cart()
new_cart.items = self.items + other.items
return new_cart
def __iadd__(self, item):
self.items.append(item)
return self
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, index, value):
self.items[index] = value
def __radd__(self, other):
new_cart = Cart()
if isinstance(other, int):
new_cart.items = [other] + self.items
elif isinstance(other, list):
new_cart.items = other + self.items
return new_cart
def __add__(self, other):
return self.__radd__(other)
cart1 = Cart()
cart1.items = [1, 2, 3]
cart2 = Cart()
cart2.items = [4, 5, 6]
combined_cart = cart1 + cart2
print(combined_cart.items) # Output: [1, 2, 3, 4, 5, 6]
cart_with_number = 10 + cart1
print(cart_with_number.items) # Output: [10, 1, 2, 3]
cart_with_list = [0, 1, 2] + cart1
print(cart_with_list.items) # Output: [0, 1, 2, 1, 2, 3]

No exemplo acima, definimos os métodos __radd__() e __add__() em nossa classe Cart para fornecer suporte à adição reversa. Agora, podemos adicionar um objeto Cart a um número inteiro ou a uma lista usando o operador + da forma correta.

Um Exemplo Completo

Agora que você entende como sobrecarregar operadores e funções embutidas em classes Python personalizadas, vamos ver um exemplo completo. Imagine que queremos criar uma classe para representar uma matriz bidimensional. Queremos que essa classe possa ser adicionada e subtraída de outras matrizes, seja indexada e fatiada, e que possa ser multiplicada por um escalar. Veja o exemplo abaixo:

class Matrix:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.data = [[0] * cols for _ in range(rows)]
def __repr__(self):
return f"Matrix({self.data})"
def __add__(self, other):
if self.rows != other.rows or self.cols != other.cols:
raise ValueError("Matrices must have the same shape")
result = Matrix(self.rows, self.cols)
for i in range(self.rows):
for j in range(self.cols):
result.data[i][j] = self.data[i][j] + other.data[i][j]
return result
def __sub__(self, other):
if self.rows != other.rows or self.cols != other.cols:
raise ValueError("Matrices must have the same shape")
result = Matrix(self.rows, self.cols)
for i in range(self.rows):
for j in range(self.cols):
result.data[i][j] = self.data[i][j] - other.data[i][j]
return result
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __mul__(self, scalar):
result = Matrix(self.rows, self.cols)
for i in range(self.rows):
for j in range(self.cols):
result.data[i][j] = self.data[i][j] * scalar
return result
matrix1 = Matrix(2, 2)
matrix1.data = [[1, 2], [3, 4]]
print(matrix1) # Output: Matrix([[1, 2], [3, 4]])
matrix2 = Matrix(2, 2)
matrix2.data = [[5, 6], [7, 8]]
print(matrix2) # Output: Matrix([[5, 6], [7, 8]])
print(matrix1 + matrix2) # Output: Matrix([[6, 8], [10, 12]])
print(matrix1 - matrix2) # Output: Matrix([[-4, -4], [-4, -4]])
print(matrix1 * 2) # Output: Matrix([[2, 4], [6, 8]])

Neste exemplo, estamos usando uma classe Matrix para representar matrizes bidimensionais. Sobrecarregamos os operadores +, - e * para adicionar, subtrair e multiplicar matrizes, respectivamente. Também sobrecarregamos os operadores [] para indexar e fatiar as matrizes. Todos esses operadores fornecem o comportamento esperado para matrizes.

Resumo e Recursos

Neste artigo, você aprendeu como sobrecarregar operadores e funções embutidas em classes Python personalizadas. Você viu como usar métodos especiais para substituir o comportamento padrão dos operadores e funções embutidas para trabalhar com objetos de suas classes personalizadas.

Lembre-se de que a sobrecarga de operadores e funções embutidas pode tornar seus objetos mais Pythonicos e oferecer uma experiência mais conveniente e intuitiva ao trabalhar com eles.

Recursos adicionais de Python: