Métodos Mágicos Numéricos Personalizados

PythonBeginner
Pratique Agora

Introdução

Neste tutorial, abordaremos os métodos mágicos do Python relacionados a operações numéricas. Métodos mágicos são métodos especiais em classes Python que começam e terminam com dois sublinhados (__). Eles também são conhecidos como métodos "dunder" (double underscores).

Esses métodos mágicos permitem que você defina como as instâncias de sua classe se comportam com certas operações, como adição ou subtração.

Cobriremos as seguintes seções:

  1. Operadores Unários
  2. Operadores Binários
  3. Operações In-place

Vamos começar!

Operadores Unários

Operações unárias são operações que envolvem um único operando, como negação, valor absoluto, etc.

Vamos começar com um objeto simples. Em number.py, crie uma classe chamada MyNumber que tenha um atributo value.

class MyNumber:
    def __init__(self, value: float):
        self.value = value

__neg__

O método mágico __neg__ define como a operação de negação deve se comportar. Quando você usa o operador - em uma instância da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __neg__(self) -> 'MyNumber':
        """Returns the negation of the instance's value."""
        return MyNumber(-self.value)

__abs__

O método mágico __abs__ define como a operação de valor absoluto deve se comportar. Quando você usa a função abs() em uma instância da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __abs__(self) -> 'MyNumber':
        """Returns the absolute value of the instance's value."""
        return MyNumber(abs(self.value))

__round__

O método mágico __round__ define como a operação de arredondamento deve se comportar. Quando você usa a função round() em uma instância da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __round__(self, ndigits: int = None) -> 'MyNumber':
        """Rounds the instance's value to the nearest whole number or specified number of digits."""
        return MyNumber(round(self.value, ndigits))

__floor__

O método mágico __floor__ define como a operação de chão (floor) deve se comportar. Quando você usa a função math.floor() em uma instância da sua classe, este método é chamado.

## math module should be import at the top of number.py
import math

    ## ... (previous code in number.py)

    def __floor__(self) -> 'MyNumber':
        """Returns the largest integer less than or equal to the instance's value."""
        return MyNumber(math.floor(self.value))

__ceil__

O método mágico __ceil__ define como a operação de teto (ceiling) deve se comportar. Quando você usa a função math.ceil() em uma instância da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __ceil__(self) -> 'MyNumber':
        """Returns the smallest integer greater than or equal to the instance's value."""
        return MyNumber(math.ceil(self.value))

Exemplo: Usando os Operadores Unários

Agora que definimos os operadores unários para nossa classe MyNumber, vamos ver como eles funcionam em unary_example.py:

import math
from number import MyNumber

## Create a new MyNumber object
a = MyNumber(5)
## Use the __neg__ method with the print function
print(f'{a.value=}, {-a.value=}')  ## Output: a.value=5, -a.value=-5

## Create another new MyNumber object
a = MyNumber(-5)
## Use the __abs__ method with the print function
print(f'{a.value=}, {abs(a).value=}')  ## Output: a.value=-5, abs(a).value=5

## Create the third new MyNumber object
a = MyNumber(5.678)
## Use the __round__ method with the print function
print(f'{a.value=}, {round(a, 2).value=}')  ## Output: a.value=5.678, round(a, 2).value=5.68

## Use the __floor__ method with the print function
print(f'{a.value=}, {math.floor(a).value=}')  ## Output: a.value=5.678, math.floor(a).value=5

## Use the __ceil__ method with the print function
print(f'{a.value=}, {math.ceil(a).value=}')  ## Output: a.value=5.678, math.ceil(a).value=6

Em seguida, digite o seguinte comando no terminal para executar o script.

python unary_example.py

Operadores Binários

Operações binárias são operações que envolvem dois operandos, como operações aritméticas como adição, subtração, multiplicação e divisão, bem como operações de comparação como igualdade, desigualdade, menor que, maior que, etc.

__add__

O método mágico __add__ define como a operação de adição deve se comportar. Quando você usa o operador + em

instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __add__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the sum of the instance's value and the other instance's value."""
        return MyNumber(self.value + other.value)

__sub__

O método mágico __sub__ define como a operação de subtração deve se comportar. Quando você usa o operador - em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __sub__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the difference of the instance's value and the other instance's value."""
        return MyNumber(self.value - other.value)

__mul__

O método mágico __mul__ define como a operação de multiplicação deve se comportar. Quando você usa o operador * em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __mul__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the product of the instance's value and the other instance's value."""
        return MyNumber(self.value * other.value)

__truediv__

O método mágico __truediv__ define como a operação de divisão verdadeira deve se comportar. Quando você usa o operador / em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __truediv__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the result of dividing the instance's value by the other instance's value."""
        return MyNumber(self.value / other.value)

__floordiv__

O método mágico __floordiv__ define como a operação de divisão inteira (floor division) deve se comportar. Quando você usa o operador // em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __floordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the largest integer less than or equal to the result of dividing the instance's value by the other instance's value."""
        return MyNumber(self.value // other.value)

__mod__

O método mágico __mod__ define como a operação de módulo deve se comportar. Quando você usa o operador % em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __mod__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the remainder of dividing the instance's value by the other instance's value."""
        return MyNumber(self.value % other.value)

__pow__

O método mágico __pow__ define como a operação de potência deve se comportar. Quando você usa o operador ** ou a função pow() em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __pow__(self, other: 'MyNumber') -> 'MyNumber':
        """Returns the instance's value raised to the power of the other instance's value."""
        return MyNumber(self.value ** other.value)

Exemplo: Usando os Operadores Binários

Agora que definimos os operadores binários para nossa classe MyNumber, vamos ver como eles funcionam em binary_example.py:

from number import MyNumber

## Create two new MyNumber objects
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## Output: a.value=5, b.value=3

## Use the __add__ method with the print function
print(f'{(a+b).value=}')  ## Output: (a+b).value=8

## Use the __sub__ method with the print function
print(f'{(a-b).value=}')  ## Output: (a-b).value=2

## Use the __mul__ method with the print function
print(f'{(a*b).value=}')  ## Output: (a*b).value=15

## Use the __truediv__ method with the print function
print(f'{(a/b).value=}')  ## Output: (a/b).value=1.6666666666666667

## Use the __floordiv__ method with the print function
print(f'{(a//b).value=}')  ## Output: (a//b).value=1

## Use the __mod__ method with the print function
print(f'{(a%b).value=}')  ## Output: (a%b).value=2

## Use the __pow__ method with the print function
print(f'{(a**b).value=}')  ## Output: (a**b).value=125

Em seguida, digite o seguinte comando no terminal para executar o script.

python binary_example.py

Operações In-place

Operações in-place são operações que modificam o valor de um objeto no local, sem criar um novo objeto. Elas são denotadas pelos operadores de atribuição aumentados, como +=, -=, *=, /=, etc.

Se o operador in-place não estiver definido para uma classe Python, o operador binário será usado em vez disso quando uma operação in-place for tentada.

Há um exemplo em inplace_example1.py, altere os operadores binários para operadores in-place:

from number import MyNumber

## Create two new MyNumber objects
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## Output: a.value=5, b.value=3

a += b
## Use the __add__ method with the print function
print(f'after a+=b: {a.value=}')  ## Output:after a+=b: (a+b).value=8

Para executar o exemplo, digite o seguinte comando no terminal:

python inplace_example1.py

O resultado mostra que o __add__ foi usado quando a operação += foi tentada.

Em seguida, implementaremos operações in-place em MyNumber, e seu comportamento com os operadores binários correspondentes será ligeiramente diferente.

__iadd__

O método mágico __iadd__ define como a operação de adição in-place deve se comportar. Quando você usa o operador += em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __iadd__(self, other: 'MyNumber') -> 'MyNumber':
        """Adds the other instance's value to the instance's value in-place."""
        print(f'input: {self.value=}, {other.value=}')
        self.value += other.value
        print(f'after +=: {self.value=}')
        return self

__isub__

O método mágico __isub__ define como a operação de subtração in-place deve se comportar. Quando você usa o operador -= em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __isub__(self, other: 'MyNumber') -> 'MyNumber':
        """Subtracts the other instance's value from the instance's value in-place."""
        print(f'input: {self.value=}, {other.value=}')
        self.value -= other.value
        print(f'after -=: {self.value=}')
        return self

__imul__

O método mágico __imul__ define como a operação de multiplicação in-place deve se comportar. Quando você usa o operador *= em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __imul__(self, other: 'MyNumber') -> 'MyNumber':
        """Multiplies the instance's value by the other instance's value in-place."""
        print(f'input: {self.value=}, {other.value=}')
        self.value *= other.value
        print(f'after *=: {self.value=}')
        return self

__itruediv__

O método mágico __itruediv__ define como a operação de divisão verdadeira in-place deve se comportar. Quando você usa o operador /= em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __itruediv__(self, other: 'MyNumber') -> 'MyNumber':
        """Divides the instance's value by the other instance's value in-place."""
        print(f'input: {self.value=}, {other.value=}')
        self.value /= other.value
        print(f'after /=: {self.value=}')
        return self

__ifloordiv__

O método mágico __ifloordiv__ define como a operação de divisão inteira (floor division) in-place deve se comportar. Quando você usa o operador //= em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __ifloordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """Performs in-place floor division on the instance's value by the other instance's value."""
        print(f'input: {self.value=}, {other.value=}')
        self.value //= other.value
        print(f'after //=: {self.value=}')
        return self

__imod__

O método mágico __imod__ define como a operação de módulo in-place deve se comportar. Quando você usa o operador %= em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __imod__(self, other: 'MyNumber') -> 'MyNumber':
        """Performs in-place modulo operation on the instance's value by the other instance's value."""
        print(f'input: {self.value=}, {other.value=}')
        self.value %= other.value
        print(f'after %=: {self.value=}')
        return self

__ipow__

O método mágico __ipow__ define como a operação de potência in-place deve se comportar. Quando você usa o operador **= em instâncias da sua classe, este método é chamado.

    ## ... (previous code in number.py)

    def __ipow__(self, other: 'MyNumber') -> 'MyNumber':
        """Raises the instance's value to the power of the other instance's value in-place."""
        print(f'input: {self.value=}, {other.value=}')
        self.value **= other.value
        print(f'after **=: {self.value=}')
        return self

Exemplo: Usando as Operações In-place

Agora que definimos os operadores in-place para nossa classe MyNumber, vamos ver como eles funcionam em inplace_example2.py:

from number import MyNumber

## Create a new MyNumber objects
a = MyNumber(13)

## Use the __iadd__ method
a += MyNumber(5)
## Output:
## input: self.value=13, other.value=5
## after +=: self.value=18

## Use the __isub__ method
a -= MyNumber(5)
## Output:
## input: self.value=18, other.value=5
## after -=: self.value=13

## Use the __imul__ method
a *= MyNumber(5)
## Output:
## input: self.value=13, other.value=5
## after *=: self.value=65

## Use the __itruediv__ method
a /= MyNumber(5)
## Output:
## input: self.value=65, other.value=5
## after /=: self.value=13.0

## Use the __ifloordiv__ method
a //= MyNumber(2)
## Output:
## input: self.value=13.0, other.value=2
## after //=: self.value=6.0

## Use the __imod__ method
a %= MyNumber(4)
## Output:
## input: self.value=6.0, other.value=4
## after %=: self.value=2.0

## Use the __ipow__ method
a **= MyNumber(3)
## Output:
## input: self.value=2.0, other.value=3
## after **=: self.value=8.0

Em seguida, digite o seguinte comando no terminal para executar o script.

python inplace_example2.py

Resumo

Neste tutorial, exploramos os métodos mágicos do Python relacionados a operações numéricas, que permitem definir um comportamento personalizado para suas classes ao interagir com diferentes tipos de operações numéricas. Cobrimos operadores unários, operadores binários e operações in-place, aprendendo como implementar cada método mágico ao longo do caminho.

Ao implementar esses métodos mágicos em suas classes personalizadas, você pode criar objetos intuitivos e fáceis de usar que funcionam perfeitamente com as operações padrão do Python. Isso não apenas melhora a legibilidade do seu código, mas também o torna mais sustentável e amigável ao usuário.

À medida que você continua a desenvolver suas habilidades em Python, considere experimentar outros métodos mágicos para personalizar ainda mais o comportamento de suas classes e criar abstrações ainda mais poderosas.