Métodos Mágicos Numéricos Personalizados

PythonPythonBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este tutorial, cubriremos los métodos mágicos de Python relacionados con las operaciones numéricas. Los métodos mágicos son métodos especiales en las clases de Python que comienzan y terminan con guiones bajos dobles (__). También se les conoce como métodos "dunder" (doble guión bajo).

Estos métodos mágicos te permiten definir cómo se comportan las instancias de tu clase con ciertas operaciones, como la adición o la resta.

Cubriremos las siguientes secciones:

  1. Operadores Unarios
  2. Operadores Binarios
  3. Operaciones In-place

¡Comencemos!


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/BasicConceptsGroup -.-> python/numeric_types("Numeric Types") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") subgraph Lab Skills python/numeric_types -.-> lab-7838{{"Métodos Mágicos Numéricos Personalizados"}} python/classes_objects -.-> lab-7838{{"Métodos Mágicos Numéricos Personalizados"}} end

Operadores Unarios

Las operaciones unarias son operaciones que involucran un solo operando, como la negación, el valor absoluto, etc.

Comencemos con un objeto simple. En number.py, cree una clase llamada MyNumber que tenga un atributo value.

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

__neg__

El método mágico __neg__ define cómo debe comportarse la operación de negación. Cuando se utiliza el operador - en una instancia de su clase, este método se llama.

    #... (código anterior en number.py)

    def __neg__(self) -> 'MyNumber':
        """Devuelve la negación del valor de la instancia."""
        return MyNumber(-self.value)

__abs__

El método mágico __abs__ define cómo debe comportarse la operación de valor absoluto. Cuando se utiliza la función abs() en una instancia de su clase, este método se llama.

    #... (código anterior en number.py)

    def __abs__(self) -> 'MyNumber':
        """Devuelve el valor absoluto del valor de la instancia."""
        return MyNumber(abs(self.value))

__round__

El método mágico __round__ define cómo debe comportarse la operación de redondeo. Cuando se utiliza la función round() en una instancia de su clase, este método se llama.

    #... (código anterior en number.py)

    def __round__(self, ndigits: int = None) -> 'MyNumber':
        """Redondea el valor de la instancia al número entero más cercano o al número especificado de dígitos."""
        return MyNumber(round(self.value, ndigits))

__floor__

El método mágico __floor__ define cómo debe comportarse la operación de piso. Cuando se utiliza la función math.floor() en una instancia de su clase, este método se llama.

## El módulo math debe ser importado al principio de number.py
import math

    #... (código anterior en number.py)

    def __floor__(self) -> 'MyNumber':
        """Devuelve el mayor entero menor o igual al valor de la instancia."""
        return MyNumber(math.floor(self.value))

__ceil__

El método mágico __ceil__ define cómo debe comportarse la operación de techo. Cuando se utiliza la función math.ceil() en una instancia de su clase, este método se llama.

    #... (código anterior en number.py)

    def __ceil__(self) -> 'MyNumber':
        """Devuelve el menor entero mayor o igual al valor de la instancia."""
        return MyNumber(math.ceil(self.value))

Ejemplo: Usando los Operadores Unarios

Ahora que hemos definido los operadores unarios para nuestra clase MyNumber, veamos cómo funcionan en unary_example.py:

import math
from number import MyNumber

## Crea un nuevo objeto MyNumber
a = MyNumber(5)
## Usa el método __neg__ con la función print
print(f'{a.value=}, {-a.value=}')  ## Salida: a.value=5, -a.value=-5

## Crea otro nuevo objeto MyNumber
a = MyNumber(-5)
## Usa el método __abs__ con la función print
print(f'{a.value=}, {abs(a).value=}')  ## Salida: a.value=-5, abs(a).value=5

## Crea el tercer nuevo objeto MyNumber
a = MyNumber(5.678)
## Usa el método __round__ con la función print
print(f'{a.value=}, {round(a, 2).value=}')  ## Salida: a.value=5.678, round(a, 2).value=5.68

## Usa el método __floor__ con la función print
print(f'{a.value=}, {math.floor(a).value=}')  ## Salida: a.value=5.678, math.floor(a).value=5

## Usa el método __ceil__ con la función print
print(f'{a.value=}, {math.ceil(a).value=}')  ## Salida: a.value=5.678, math.ceil(a).value=6

Luego escriba el siguiente comando en la terminal para ejecutar el script.

python unary_example.py

Operadores Binarios

Las operaciones binarias son operaciones que involucran dos operandos, como las operaciones aritméticas de adición, sustracción, multiplicación y división, así como las operaciones de comparación de igualdad, desigualdad, menor que, mayor que, etc.

__add__

El método mágico __add__ define cómo debe comportarse la operación de adición. Cuando se utiliza el operador + en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __add__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve la suma del valor de la instancia y el valor de la otra instancia."""
        return MyNumber(self.value + other.value)

__sub__

El método mágico __sub__ define cómo debe comportarse la operación de sustracción. Cuando se utiliza el operador - en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __sub__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve la diferencia entre el valor de la instancia y el valor de la otra instancia."""
        return MyNumber(self.value - other.value)

__mul__

El método mágico __mul__ define cómo debe comportarse la operación de multiplicación. Cuando se utiliza el operador * en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __mul__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve el producto del valor de la instancia y el valor de la otra instancia."""
        return MyNumber(self.value * other.value)

__truediv__

El método mágico __truediv__ define cómo debe comportarse la operación de división real. Cuando se utiliza el operador / en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __truediv__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve el resultado de dividir el valor de la instancia por el valor de la otra instancia."""
        return MyNumber(self.value / other.value)

__floordiv__

El método mágico __floordiv__ define cómo debe comportarse la operación de división entera. Cuando se utiliza el operador // en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __floordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve el mayor entero menor o igual al resultado de dividir el valor de la instancia por el valor de la otra instancia."""
        return MyNumber(self.value // other.value)

__mod__

El método mágico __mod__ define cómo debe comportarse la operación de módulo. Cuando se utiliza el operador % en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __mod__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve el residuo de dividir el valor de la instancia por el valor de la otra instancia."""
        return MyNumber(self.value % other.value)

__pow__

El método mágico __pow__ define cómo debe comportarse la operación de potencia. Cuando se utiliza el operador ** o la función pow() en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __pow__(self, other: 'MyNumber') -> 'MyNumber':
        """Devuelve el valor de la instancia elevado a la potencia del valor de la otra instancia."""
        return MyNumber(self.value ** other.value)

Ejemplo: Usando los Operadores Binarios

Ahora que hemos definido los operadores binarios para nuestra clase MyNumber, veamos cómo funcionan en binary_example.py:

from number import MyNumber

## Crea dos nuevos objetos MyNumber
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## Salida: a.value=5, b.value=3

## Usa el método __add__ con la función print
print(f'{(a+b).value=}')  ## Salida: (a+b).value=8

## Usa el método __sub__ con la función print
print(f'{(a-b).value=}')  ## Salida: (a-b).value=2

## Usa el método __mul__ con la función print
print(f'{(a*b).value=}')  ## Salida: (a*b).value=15

## Usa el método __truediv__ con la función print
print(f'{(a/b).value=}')  ## Salida: (a/b).value=1.6666666666666667

## Usa el método __floordiv__ con la función print
print(f'{(a//b).value=}')  ## Salida: (a//b).value=1

## Usa el método __mod__ con la función print
print(f'{(a%b).value=}')  ## Salida: (a%b).value=2

## Usa el método __pow__ con la función print
print(f'{(a**b).value=}')  ## Salida: (a**b).value=125

Luego escriba el siguiente comando en la terminal para ejecutar el script.

python binary_example.py

Operaciones In-place

Las operaciones in-place son operaciones que modifican el valor de un objeto en el lugar, sin crear un nuevo objeto. Se denotan por los operadores de asignación aumentada, como +=, -=, *=, /=, etc.

Si el operador in-place no está definido para una clase de Python, entonces se usará el operador binario en su lugar cuando se intente una operación in-place.

Hay un ejemplo en inplace_example1.py, cambie los operadores binarios a operadores in-place:

from number import MyNumber

## Crea dos nuevos objetos MyNumber
a = MyNumber(5)
b = MyNumber(3)
print(f'{a.value=}, {b.value=}') ## Salida: a.value=5, b.value=3

a += b
## Usa el método __add__ con la función print
print(f'despues de a+=b: {a.value=}')  ## Salida:después de a+=b: (a+b).value=8

Para ejecutar el ejemplo, escriba el siguiente comando en la terminal:

python inplace_example1.py

El resultado muestra que se usó el __add__ cuando se intentó la operación +=.

Luego implementaremos las operaciones in-place en MyNumber, y su comportamiento con los operadores binarios correspondientes será ligeramente diferente.

__iadd__

El método mágico __iadd__ define cómo debe comportarse la operación de adición in-place. Cuando se utiliza el operador += en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __iadd__(self, other: 'MyNumber') -> 'MyNumber':
        """Agrega el valor de la otra instancia al valor de la instancia in-place."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value += other.value
        print(f'despues de +=: {self.value=}')
        return self

__isub__

El método mágico __isub__ define cómo debe comportarse la operación de sustracción in-place. Cuando se utiliza el operador -= en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __isub__(self, other: 'MyNumber') -> 'MyNumber':
        """Resta el valor de la otra instancia al valor de la instancia in-place."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value -= other.value
        print(f'despues de -=: {self.value=}')
        return self

__imul__

El método mágico __imul__ define cómo debe comportarse la operación de multiplicación in-place. Cuando se utiliza el operador *= en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __imul__(self, other: 'MyNumber') -> 'MyNumber':
        """Multiplica el valor de la instancia por el valor de la otra instancia in-place."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value *= other.value
        print(f'despues de *=: {self.value=}')
        return self

__itruediv__

El método mágico __itruediv__ define cómo debe comportarse la operación de división real in-place. Cuando se utiliza el operador /= en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __itruediv__(self, other: 'MyNumber') -> 'MyNumber':
        """Divide el valor de la instancia por el valor de la otra instancia in-place."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value /= other.value
        print(f'despues de /=: {self.value=}')
        return self

__ifloordiv__

El método mágico __ifloordiv__ define cómo debe comportarse la operación de división entera in-place. Cuando se utiliza el operador //= en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __ifloordiv__(self, other: 'MyNumber') -> 'MyNumber':
        """Realiza una división entera in-place del valor de la instancia por el valor de la otra instancia."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value //= other.value
        print(f'despues de //=: {self.value=}')
        return self

__imod__

El método mágico __imod__ define cómo debe comportarse la operación de módulo in-place. Cuando se utiliza el operador %= en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __imod__(self, other: 'MyNumber') -> 'MyNumber':
        """Realiza una operación de módulo in-place del valor de la instancia por el valor de la otra instancia."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value %= other.value
        print(f'despues de %=: {self.value=}')
        return self

__ipow__

El método mágico __ipow__ define cómo debe comportarse la operación de potencia in-place. Cuando se utiliza el operador **= en instancias de su clase, este método se llama.

    #... (código anterior en number.py)

    def __ipow__(self, other: 'MyNumber') -> 'MyNumber':
        """Eleva el valor de la instancia a la potencia del valor de la otra instancia in-place."""
        print(f'entrada: {self.value=}, {other.value=}')
        self.value **= other.value
        print(f'despues de **=: {self.value=}')
        return self

Ejemplo: Usando las Operaciones In-place

Ahora que hemos definido los operadores in-place para nuestra clase MyNumber, veamos cómo funcionan en inplace_example2.py:

from number import MyNumber

## Crea un nuevo objeto MyNumber
a = MyNumber(13)

## Usa el método __iadd__
a += MyNumber(5)
## Salida:
## entrada: self.value=13, other.value=5
## después de +=: self.value=18

## Usa el método __isub__
a -= MyNumber(5)
## Salida:
## entrada: self.value=18, other.value=5
## después de -=: self.value=13

## Usa el método __imul__
a *= MyNumber(5)
## Salida:
## entrada: self.value=13, other.value=5
## después de *=: self.value=65

## Usa el método __itruediv__
a /= MyNumber(5)
## Salida:
## entrada: self.value=65, other.value=5
## después de /=: self.value=13.0

## Usa el método __ifloordiv__
a //= MyNumber(2)
## Salida:
## entrada: self.value=13.0, other.value=2
## después de //=: self.value=6.0

## Usa el método __imod__
a %= MyNumber(4)
## Salida:
## entrada: self.value=6.0, other.value=4
## después de %=: self.value=2.0

## Usa el método __ipow__
a **= MyNumber(3)
## Salida:
## entrada: self.value=2.0, other.value=3
## después de **=: self.value=8.0

Luego escriba el siguiente comando en la terminal para ejecutar el script.

python inplace_example2.py

Resumen

En este tutorial, exploramos los métodos mágicos de Python relacionados con las operaciones numéricas, los cuales te permiten definir un comportamiento personalizado para tus clases cuando interactúan con diferentes tipos de operaciones numéricas. Cubrimos operadores unarios, operadores binarios y operaciones in-place, aprendiendo cómo implementar cada método mágico en el camino.

Al implementar estos métodos mágicos en tus clases personalizadas, puedes crear objetos intuitivos y fáciles de usar que funcionan sin problemas con las operaciones estándar de Python. Esto no solo mejora la legibilidad de tu código, sino que también lo hace más mantenible y amigable para el usuario.

A medida que sigues desarrollando tus habilidades en Python, considera experimentar con otros métodos mágicos para personalizar aún más el comportamiento de tus clases y crear abstracciones aún más poderosas.