Personaliza tu clase de Python con métodos Magic o Dunder

Los métodos mágicos aseguran un modelo de datos consistente que retiene la función heredada de la clase integrada mientras proporciona un comportamiento de clase personalizado. Estos métodos pueden enriquecer el diseño de la clase y mejorar la legibilidad del lenguaje. Entonces, en este artículo, veremos cómo hacer uso de los métodos mágicos, cómo funciona y los métodos mágicos disponibles en Python. Repasemos cada una de las secciones:

Sintaxis del método mágico

Un método que está envuelto por dos guiones bajos en ambos lados se llama métodos mágicos. El motivo detrás del método mágico es sobrecargar los métodos integrados de Python y sus operadores. Aquí, _syntax evita que los programadores definan el mismo nombre para métodos personalizados. Cada método mágico cumple su propósito. Consideremos un ejemplo que verifica la equivalencia. Ejemplo: 

Python3

class EquivalenceClass(object):
    def __eq__(self, other):
        return type(self) == type(other)
 
print(EquivalenceClass() == EquivalenceClass())
print(EquivalenceClass() == 'MyClass')

Producción

True
False

El método __eq__ toma dos argumentos, uno mismo y el objeto, para verificar la igualdad. Lo que es importante entender es que el método __eq__ se invoca cuando los dos objetos se comparan usando el operador ==. Repasemos algunos de los métodos mágicos comunes en python.

Métodos mágicos comunes

En Python, tenemos una amplia gama de métodos mágicos, cada uno cumple su propósito. Aquí revisaremos algunos de los métodos mágicos comunes:

  • Creación
  • Destrucción
  • Conversión de tipo
  • comparaciones

Creación

Los métodos mágicos enredados en la creación se realizan cuando se crea una instancia de clase. Dos de los métodos mágicos asociados son los métodos __init__ y __new__.

método __init__

El método __init__ de un objeto se ejecuta inmediatamente después de la creación de la instancia. Aquí, el método toma un argumento posicional, uno mismo, y cualquier cantidad de argumentos opcionales o de palabras clave. Veamos un ejemplo simple: Ejemplo: 

Python3

class InitClass(object):
    def __init__(self):
        print('Executing the __init__ method.')
 
ic = InitClass()

Producción

Executing the __init__ method.

Aquí, el punto esencial a tener en cuenta es que no está llamando al método __init__. En cambio, el intérprete de Python hace la llamada a la instanciación del objeto. Consideremos un ejemplo, que toma un argumento opcional: 

Python3

class Square(object):
    def __init__(self, number = 2):
        self._number = number
 
    def square(self):
        return self._number**2
 
s = Square()
print('Number: % i' % s._number)
print('Square: % i' % s.square())

Producción

Number: 2
Square: 4

Aquí podemos notar que el método __init__ usa el valor predeterminado (2) en ausencia de un argumento opcional. Veamos algunos datos sobre el método __init__:

  • El método __init__ proporciona datos iniciales al objeto, no para crear un objeto.
  • Solo devuelve Ninguno; al devolver algo que no sea Ninguno, se genera TypeError.
  • Personaliza la instanciación de una clase.

A continuación, procederemos al método __new__. 

__Nuevo método

El método __new__ crea y devuelve la instancia de una clase. El argumento principal del método __new__ es la clase que se debe instanciar, y el resto son los argumentos mencionados durante la llamada de clase. Exploremos a través de un ejemplo: Ejemplo: 

Python3

class Students(object):
    def __init__(self, idNo, grade):
        self._idNo = idNo
        self._grade = grade
 
    def __new__(cls, idNo, grade):
        print("Creating Instance")
        instance = super(Students, cls).__new__(cls)
        if 5 <= grade <= 10:
            return instance
        else:
            return None
 
    def __str__(self):
        return '{0}({1})'.format(self.__class__.__name__, self.__dict__)
 
 
stud1 = Students(1, 7)
print(stud1)
 
stud2 = Students(2, 12)
print(stud2)

Producción

 
Creating Instance
Students({'_idNo': 1, '_grade': 7})
Creating Instance
None

En la mayoría de los casos, no necesitamos definir un método __nuevo__. Si optamos por una implementación del método __new__, entonces es imprescindible hacer referencia a la superclase. Otro punto esencial a tener en cuenta, el método __init__ de la clase instanciada get se ejecuta solo si el método __new__ devuelve una instancia de la misma clase.

Destrucción

método __del__

El método __del__ se invoca al destruir una instancia de una clase, ya sea mediante la eliminación directa o la restauración de la memoria por parte del recolector de elementos no utilizados. Examinemos el siguiente código: 

Python3

class MyClass(object):
    def __del__(self):
        print('Destroyed')
 
MyClass()
'Immutable String - not assigned to a variable'

Producción

Destroyed

¿Qué pasa cuando creamos un objeto sin asignarle una variable? El recolector de elementos no utilizados mantendrá el registro de los objetos que no están referenciados a una variable y lo eliminará cuando se ejecute otra instrucción del programa. Aquí, creamos un objeto de MyClass sin asignarlo a una variable. Tras la ejecución de la declaración del programa (string inmutable, no asignada a una variable), el recolector de basura destruye el objeto MyClass. Lo mismo sucede, cuando eliminamos el objeto directamente; Pero aquí la eliminación ocurre de inmediato. Solo prueba el siguiente código. x = MiClase() del x

Conversión de tipo

La conversión de tipos se refiere a la conversión de un tipo de datos a otro; Python proporciona varios métodos mágicos para manejar la conversión.

  • método __str__
  • Métodos __int__, __float__ y __complex__
  • método __bool__

método __str__

El método __str__ requiere un argumento posicional, self, y devuelve una string. Se llama cuando se pasa un objeto al constructor str(). Consideremos un ejemplo: 

Python3

class MyString(object):
    def __str__(self):
        return 'My String !'
 
print(str(MyString()))

Producción

My String!

Echemos un vistazo a otra situación que invoca el método __str__. El escenario es el uso de %s en una string de formato, que a su vez invoca el método __str__. 

Python3

class HelloClass(object):
    def __str__(self):
        return 'George'
 
print('Hello, % s' % HelloClass())

Producción

Hello, George

Métodos __int__, __float__ y __complex__

El método __int__ se ejecuta al llamar al constructor int y devuelve un int; Convierte los objetos complejos en tipo int primitivo. Del mismo modo, los métodos __float__ y _complex__ se ejecutan al pasar el objeto al constructor flotante y complejo, respectivamente.

método __bool__

El método mágico __bool__ en python toma un argumento posicional y devuelve verdadero o falso. Su propósito es verificar si un objeto es verdadero o falso, o convertirlo explícitamente a un valor booleano.

comparaciones

Los métodos mágicos de comparación se invocan cuando verificamos la equivalencia (==, !=) o las relaciones (<, y > =). Cada uno de estos operadores en python se asigna a sus métodos mágicos correspondientes. 

Igualdad binaria

1. Método __eq__ El método __eq__ se ejecuta cuando se comparan dos objetos usando el operador ==. Se necesitan dos argumentos posicionales: el yo y el objeto para verificar la igualdad. En la mayoría de los casos, si se define el objeto del lado izquierdo, primero se comprueba su equivalencia. Veamos a través de un ejemplo: 

Python3

class MyEquivalence(object):
    def __eq__(self, other):
        print('MyEquivalence:\n'
              '% r\n % r' %(self, other))
        return self is other
 
class YourEquivalence(object):
    def __eq__(self, other):
        print('Your Equivalence:\n'
              '% r\n % r' %(self, other))
        return self is other
 
eq1 = MyEquivalence()
eq2 = YourEquivalence()
# checking for equivalence where eq1 is at the left side
print(eq1 == eq2)
# checking for equivalence where eq2 is at the left side
print(eq2 == eq1)

Producción

MyEquivalence:
<__main__.MyEquivalence object at 0x7fa1d38e16d8>
<__main__.YourEquivalence object at 0x7fa1d1ea37b8>
False
Your Equivalence:
<__main__.YourEquivalence object at 0x7fa1d1ea37b8>
<__main__.MyEquivalence object at 0x7fa1d38e16d8>
False

La regla de ordenación no se aplica si un objeto es una subclase directa del otro. Examinemos a través de un ejemplo: 

Python3

class MyEquivalence(object):
    def __eq__(self, other):
        print('MyEquivalence:\n'
              '% r\n % r' %(self, other))
        return self is other
 
class MySubEquivalence(MyEquivalence):
    def __eq__(self, other):
        print('MySubEquivalence:\n'
              '% r\n % r' %(self, other))
        return self is other
 
eqMain = MyEquivalence()
eqSub = MySubEquivalence()
 
# eqMain at the right side
print(eqMain == eqSub)
 
# eqSub at the right side
print(eqSub == eqMain)

Producción

MySubEquivalence:
<__main__.MySubEquivalence object at 0x7f299ce802b0>
 <__main__.MyEquivalence object at 0x7f299e8be6d8>
False
MySubEquivalence:
<__main__.MySubEquivalence object at 0x7f299ce802b0>
 <__main__.MyEquivalence object at 0x7f299e8be6d8>
False

2. Método __ne__ El método mágico __ne__ se ejecuta cuando se utiliza el operador !=. En la mayoría de los casos, no necesitamos definir el método __ne__; Al usar el operador !=, el intérprete de python ejecutará el método __eq__ e invertirá el resultado.

Comparaciones relativas: métodos __lt__ y __le__, __gt__ y __ge__

Los métodos __lt__ y __le__ se invocan cuando se utilizan los operadores < y <=, respectivamente. Y los métodos __gt__ y __ge__ se invocan al utilizar los operadores > y >=, respectivamente. Sin embargo, no es necesario usar todos estos 4 métodos; el uso de los métodos __lt__ y __gt__ cumplirá el propósito. Simplemente examine los puntos a continuación para comprender por qué no requerimos todos estos métodos: 1. Los métodos __ge__ y __le__ se pueden reemplazar con el inverso de los métodos __lt__ y __gt__, respectivamente. 2. La disyunción de los métodos __lt__ y __eq__ se puede usar en lugar del método __le__ y, de manera similar, los métodos __gt__ y __eq__ para el método __ge__. Echemos un vistazo al siguiente ejemplo. Aquí, compararemos el objeto en función de su tiempo de creación. 

Python3

import time
class ObjectCreationTime(object):
    def __init__(self, objName):
        self._created = time.time()
        self._objName = objName
 
    def __lt__(self, other):
        print('Creation Time:\n'
              '% s:% f\n % s:% f' %(self._objName, self._created,
                                 other._objName, other._created))
        return self._created < other._created
 
    def __gt__(self, other):
        print('Creation Time:\n'
              '% s:% f\n % s:% f' %(self._objName, self._created,
                                 other._objName, other._created))
        return self._created > other._created
 
obj1 = ObjectCreationTime('obj1')
obj2 = ObjectCreationTime('obj2')
print(obj1 < obj2)
print(obj1 > obj2)

Producción

Creation Time:
obj1:1590679265.753279
obj2:1590679265.753280
True
Creation Time:
obj1:1590679265.753279
obj2:1590679265.753280
False

Métodos mágicos para operadores binarios

Veamos 3 métodos mágicos proporcionados por python para operadores binarios.

  • Método vainilla
  • Método inverso
  • Método en el lugar

Método vainilla

Considere una expresión, x + y; En el método Vanilla, esta expresión se asigna a x.__add__(y). Consideremos otra expresión, y – x. Aquí, la expresión se asigna a y.__sub__(x). De manera similar, a * b se asigna a a.__mul__(b) y a / b se asigna a a.__truediv__(b), y así sucesivamente. Un punto a tener en cuenta, se invoca el método del objeto del lado izquierdo y pasa el objeto del lado derecho como parámetro. En el caso de x + y, se invoca el método __add__ de x y se pasa y como parámetro. Examinemos con un ejemplo. 

Python3

class Count(object):
    def __init__(self, count):
        self._count = count
    def __add__(self, other):
        total_count = self._count + other._count
        return Count(total_count)
    def __str__(self):
        return 'Count: % i' % self._count
 
 
 
c1 = Count(2)
c2 = Count(5)
c3 = c1 + c2
print(c3)

Producción

Count: 7

Método inverso

En el método Vanilla, el método del objeto del lado izquierdo se invoca al ejecutar un operador binario. Sin embargo, si el objeto del lado izquierdo no tiene un método para que el operador binario mapee, se llama al método inverso; comprueba el método del objeto del lado derecho para mapear. Echemos un vistazo al siguiente ejemplo: 

Python3

class Count(object):
    def __init__(self, count):
        self._count = count
 
    def __add__(self, other):
        total_count = self._count + other._count
        return Count(total_count)
 
    def __radd__(self, other):
        if other == 0:
            return self
        else:
            return self.__add__(other)
 
    def __str__(self):
        return 'Count:% i' % self._count
     
c2 = Count(2)
c3 = 0 + c2
print(c3)

Producción

Count:2

Dado que 0 no tiene el método __add__ correspondiente, el intérprete de python llamaría al método __radd__ – c2.__radd__(0). De manera similar, si el método __sub__ no está definido, llamaría a __rsub __.

Método en el lugar

Tanto las operaciones de cálculo como las de asignación se realizan mientras se utilizan los métodos in situ. Algunos de los operadores que se asignan a métodos in situ son +=, -=, *=, etc. Los nombres de los métodos locales van precedidos de i. Por ejemplo, la instrucción x += y correspondería a x.__iadd__(y), y así sucesivamente. Veamos el siguiente ejemplo: 

Python3

class inPlace(object):
    def __init__(self, value):
        self._value = value
    def __iadd__(self, other):
        self._value = self._value + other._value
        return self._value
    def __str__(object):
        return self._value
 
inP1 = inPlace(5)
inP2 = inPlace(3)
inP1 += inP2
print(inP1)

Producción

8

Métodos mágicos para operadores unarios

  • método __pos__
  • método __neg__
  • método __invertir__

método __pos__

El método __pos__ se invoca usando el operador +. Hemos visto que el operador + también funciona como un operador binario. No se preocupe, el intérprete de Python sabe cuál usar, unario o binario, según la situación. El método __pos__ toma un solo argumento posicional, self, realiza la operación y devuelve el resultado. Examinemos a través de un ejemplo: 

Python3

class unaryOp(object):
    def __init__(self, value):
        self._value = value
    def __pos__(self):
        print('__pos__ magic method')
        return(+self._value)
    
up = unaryOp(5)
print(+up)

Producción

__pos__ magic method
5

método __neg__

El método __neg__ se llama usando el operador –. Este operador también actúa como un operador binario, pero según la situación, el intérprete determina qué método mágico mapear. El método mágico __neg__ acepta un solo argumento posicional, self, opera y devuelve el resultado. Veamos el siguiente ejemplo: 

Python3

class unaryOp(object):
    def __init__(self, value):
        self._value = value
    def __neg__(self):
        print('__neg__ magic method')
        return(-self._value)
 
up = unaryOp(5)
print(-up)

Producción

__neg__ magic method
-5

método __invertir__

El último operador unario es el método __invert__, que se invoca mediante el operador ~. La sentencia ~x es equivalente a x.__invert__(). Consideremos un ejemplo: 

Python3

class invertClass(object):
    def __init__(self, value):
        self._value = value
    def __invert__(self):
        return self._value[::-1]
    def __str__(self):
        return self._value
 
invrt = invertClass('Hello, George')
invertedValue = ~invrt
print(invertedValue)

Producción

egroeG, olleH

Algunos otros métodos mágicos

Analicemos algunos otros métodos mágicos:

  • método __len__
  • método __repr__
  • __contiene__ método

Sobrecarga del método __len__

El método len() invoca el método mágico __len__. Toma un argumento posicional y devuelve la longitud del objeto. Veamos el siguiente código: 

Python3

class RectangleClass(object):
    def __init__(self, area, breadth):
        self._area = area
        self._breadth = breadth
         
    def __len__(self):
        return int(self._area / self._breadth)
 
rc = RectangleClass(90, 5)
print(len(rc))

Producción

18

Importancia del método __repr__

El método mágico __repr__ ayuda a representar un objeto en la terminal interactiva de Python. Se necesita un argumento posicional: uno mismo. Veamos cómo se representa un objeto en la terminal interactiva de Python sin sobrecargar el método __repr__. 

Python3

class RectangleClass(object):
    def __init__(self, area, breadth):
        self._area = area
        self._breadth = breadth
         
    def __len__(self):
        return int(self._area / self._breadth)
 
## use python interactive terminal to check object representation.
RectangleClass(90, 5)

Producción

<__main__.RectangleClass object at 0x7f9ecaae9710>

Podemos ver, devuelve la dirección del objeto en la memoria, que no es tan útil. Veamos cómo podemos sobrecargar el método __repr__ para devolver una representación de objeto útil. 

Python3

class RectangleClass(object):
    def __init__(self, area, breadth):
        self._area = area
        self._breadth = breadth
         
    def __len__(self):
        return int(self._area / self._breadth)
 
    def __repr__(self):
        """object representation"""
        return 'RectangleClass(area =% d, breadth =% d)' %\
               (self._area, self._breadth)
          
RectangleClass(90, 5)  
RectangleClass(80, 4)  

Producción

RectangleClass(area=90, breadth=5)
RectangleClass(area=80, breadth=4)

__contiene__ método mágico

El método __contains__ se llama cuando se ejecuta la expresión ‘in’. Toma dos argumentos posicionales, self y item, y devuelve verdadero si el elemento está presente o, de lo contrario, devuelve falso. Examinemos a través de un ejemplo: 

Python3

import datetime
 
class DateClass(object):
    def __init__(self, startDate, endDate):
        self.startDate = startDate
        self.endDate = endDate
 
    def __contains__(self, item):
        """ check whether a date is between the given range and
        return true or false"""
        return self.startDate <= item <= self.endDate
 
dtObj = DateClass(datetime.date(2019, 1, 1), datetime.date(2021, 12, 31))
result = datetime.date(2020, 6, 4) in dtObj
print("Whether (2020, 6, 4) is within the mentioned date range? ", result)
 
result = datetime.date(2022, 8, 2) in dtObj
print("Whether (2022, 8, 2) is within the mentioned date range? ", result)

Producción

Whether (2020, 6, 4) is within the mentioned date range?  True
Whether (2022, 8, 2) is within the mentioned date range?  False

Resumen Por lo tanto, podemos concluir que los métodos mágicos son un modelo de datos consistente para personalizar el comportamiento de la clase y mejorar la legibilidad sin perder su característica heredada. Sin embargo, antes de dar una función personalizada, asegúrese de que la personalización sea necesaria o no.

Publicación traducida automáticamente

Artículo escrito por SonuGeorge y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *