Python Avanzado: ¿Qué son los métodos mágicos?

Este artículo destaca los métodos especiales de Python que todo programador de Python debe conocer

Farhad Malik

Follow

16 de mayo, 2020 – 14 min read

Los métodos mágicos nos ayudan a enriquecer nuestras aplicaciones. Añaden indirectamente magia a nuestro código Python. Este es un tema de nivel avanzado para los desarrolladores de Python y lo recomiendo a todos los que estén o tengan la intención de usar el lenguaje de programación Python.

Los métodos mágicos nos dan más control sobre cómo se comporta nuestra aplicación.

Este artículo pretende explicar qué son los métodos mágicos y cómo se pueden usar para construir aplicaciones Python. Proporcionará una visión general de los métodos mágicos más utilizados para una serie de tipos de datos.

El propósito de esbozar los métodos mágicos clave es para que entendamos si queremos anular estos métodos en nuestras clases personalizadas para enriquecer las aplicaciones.

Los métodos mágicos hacen que el lenguaje de programación Python sea extremadamente potente

Foto de Rodion Kutsaev en Unsplash

¿Qué son los métodos mágicos de Python?

Los métodos mágicos de Python también se conocen como métodos especiales o métodos dunder. Están rodeados de doble guión bajo, por ejemplo, __init__().

Un objeto puede tener un número de métodos mágicos.

Recuerda que todo en Python es un objeto, incluyendo una variable/función/clase, etc. Los objetos son la abstracción de python para los datos.

Los métodos mágicos se utilizan para construir e inicializar nuevos objetos, nos ayudan a recuperar un objeto como un diccionario, se utilizan para eliminar un objeto entre otras operaciones. Se utilizan cuando se invoca el operador +, o incluso cuando queremos representar un objeto como una cadena.

Aunque todos los métodos en Python son públicos, la convención de codificación es rodear todos los métodos privados con doble guión bajo __<method>__()

Esto implica que los métodos mágicos están destinados a ser métodos privados. También significa que el llamador de un objeto no debe invocar el método directamente, ya que el método está destinado a ser invocado por la clase internamente que tiene el método mágico.

Podemos anular los métodos mágicos para proporcionar nuestra propia funcionalidad personalizada.

Foto de Cristian Escobar en Unsplash

Explicaré los conceptos de los métodos mágicos mediante la creación de una clase personalizada y a continuación daré un repaso a los métodos mágicos más importantes en un entero, una cadena, una lista y un diccionario.

A medida que avancemos en el artículo, comenzará a construirse una imagen mucho más clara de por qué existen los métodos mágicos y cómo utilizarlos.

Si quieres entender el lenguaje de programación Python desde el nivel principiante hasta el avanzado, entonces te recomiendo encarecidamente el siguiente artículo:

Empezaré el tema de los métodos mágicos creando una clase personalizada y luego explicaré cómo se usan los métodos mágicos.

El caso de uso para entender los métodos mágicos

En el fragmento de abajo, he creado una clase llamada Human y luego he instanciado una instancia de la clase Human.

Este fragmento de código nos servirá para entender los métodos mágicos.

Nota, el id del objeto human es de tipo entero, el atributo name es de tipo string, la propiedad addresses es de tipo list y la propiedad maps es de tipo dictionary.

He agrupado los métodos mágicos en varias secciones de tipos de datos para facilitar la lectura. Sin embargo, los mismos métodos mágicos se encuentran en diferentes tipos de datos.

Los métodos mágicos se pueden anular para enriquecer la funcionalidad y crear la lógica personalizada que mejor se adapte a las necesidades del negocio.

Clase:

Entendamos los métodos mágicos que están asociados al objeto humano. Si ejecuto el método dir(human), se listarán todas las funciones y nombres de atributos del objeto human.

Hay un total de 29 métodos/atributos que están asociados al objeto human. Entre ellos, 26 son los métodos mágicos.

Es un número bastante grande de métodos especiales. Estos métodos se heredan del tipo base de la clase Human. Por lo tanto, son los métodos predefinidos que podemos utilizar/sustituir para enriquecer las clases.

La siguiente parte de la sección explicará los métodos mágicos clave.

__delattr__

Este método se llama cuando intentamos eliminar un atributo de una clase.

Podemos anular la funcionalidad implementando el método en la clase Human:

def __delattr__(self, item):
print('Deleting attribute ' + item)
return object.__delattr__(self, item)

Ahora, cada vez que intentemos borrar un atributo, se mostrará el mensaje: Borrando atributo

También existe el método __setattr__() para asignar un valor a un atributo y __getattr__() para obtener un valor del atributo.

Un caso de uso de__delattr__() puede ser para evitar que se borren determinados atributos de un objeto o cuando queremos realizar acciones específicas cuando se borra un atributo concreto.

__dict__

Este método devuelve un diccionario que representa al objeto. Las claves del diccionario son los atributos del objeto y los valores son los valores de los atributos.

Como instancia:

human = Human(2, 'Malik').__dict__
print(human)

El código anterior devuelve:

{‘id’: 2, ‘name’: ‘Malik’, ‘addresses’: , ‘mapas’: {}}

__dir__

Podemos anular el método dir() anulando el método __dir__() de la clase. Como ejemplo, podemos eliminar los métodos internos del resultado que devuelve el método dir():

__eq__

Este método es llamado cuando intentamos realizar la operación ==. Consideremos que dos objetos humanos son iguales cuando su atributo id es igual aunque su nombre sea diferente.

Podemos anular el método __eq__() para conseguir esta funcionalidad:

def __eq__(self, other):
return self.id == other.id

Ahora devolverá True:

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
print(first == second)

__format__

Cuando intentamos hacer string.format(), se invoca internamente el método __format__().

__ge__

Como ejemplo, supongamos que hay dos objetos Humanos:

first = Human(1, 'Farhad')
second = Human(2, 'Malik')

Tengamos en cuenta también que nuestro proyecto tiene la regla de que un objeto humano con un Id mayor se considera mayor que el otro objeto humano. Por lo tanto, podemos anular el método __gt__() e implementar la lógica personalizada:

def __ge__(self, other):
return self.id >= other.id

Esto ahora devolverá False porque el id del segundo objeto humano es mayor que el primer objeto humano:

print(first >= second)
Returns False

También hay un método __lt__() que se ejecuta cuando se realiza el operador ≤.

__hash__

Hashing se utiliza para convertir un objeto en un entero. El hashing se realiza cuando intentamos establecer un elemento en un diccionario/set.

Un buen algoritmo de hashing resulta en un menor número de colisiones de hashing. Podemos proporcionar nuestro propio algoritmo de hash anulando el método __hash__().

Consideremos que el id del objeto Human se supone que es único en nuestra aplicación. El algoritmo __hash__() puede ser anulado para devolver el self.id como el entero hash:

def __hash__(self):
return self.id

Podemos crear dos objetos y guardarlos en una colección de conjuntos. Cuando consultemos la longitud del conjunto, esperaremos dos elementos en el conjunto porque ambos objetos tienen un id diferente.

first = Human(1, 'Farhad')
second = Human(2, 'Malik')
my_set = set()
print(len(my_set))
#returns 2

Si ahora establecemos el id como 1 para ambos objetos y repetimos el ejercicio, entonces sólo veremos 1 elemento en el conjunto porque ambos objetos tienen la misma clave hash ya que su atributo id es el mismo, aunque su atributo name sea diferente.

first = Human(1, 'Farhad')
second = Human(1, 'Malik')
my_set = set()
print(len(my_set))
#returns 1

__init__

El método __init__() se ejecuta cuando queremos instanciar una nueva instancia de una clase llamando a su constructor.

Como instancia, cuando intentamos ejecutar:

human = Human(1, 'farhad')

Entonces se ejecuta el método __init__().

Podemos anular la funcionalidad y pasar en él nuestros propios argumentos y comportamiento personalizados.

Como instancia, el método __init__() de la clase Human es:

def __init__(self, id, name, addresses=, maps={}):
self.id = id
self.name = name
self.addresses = addresses
self.maps = maps

Foto de Cederic X en Unsplash

__init_subclass__

Este es uno de los casos de uso de las metaclases. Cuando una clase es subclasificada y su objeto es creado entonces el método __init_subclass__() es llamado.

Esencialmente, el método informa al padre que ha sido subclasificado. Este gancho puede entonces inicializar todas las subclases de una clase dada. Por lo tanto, el método se utiliza para registrar subclases y asignar valores por defecto a los atributos de las subclases. Por lo tanto, nos permite personalizar la inicialización de las subclases.

Explicaré cómo funcionan las metaclases en mi próximo artículo.

__new__

Cuando queremos instanciar/crear una nueva instancia de una clase entonces se ejecuta el método __new__(cls).

Como ejemplo, consideremos que queremos imprimir ‘Human creating…’ siempre que se llame al constructor Human().

Podemos anular la funcionalidad del método __new__(cls) como se muestra a continuación:

def __new__(cls, *args, **kwargs):
print('Human creating...')
return object.__new__(cls)

Como resultado, Human creating… se imprime cuando intentamos crear una instancia:

human = Human(1, 'Farhad', , {'London':2, 'UK':3})

__sizeof__

Este método es llamado cuando ejecutamos sys.getsizeof(). Devuelve el tamaño del objeto en memoria, en bytes.

__str__

Imprime: id=1. name=Farhad.

La función __str__() debe intentar devolver una representación amigable del objeto.

__weakref__

Este objeto __weakref__ devuelve la lista de referencias débiles al objeto destino. Esencialmente, ayuda a la recolección de basura a informar a las referencias débiles que el referente ha sido recolectado. Por lo tanto, evita que los objetos accedan a los punteros subyacentes.

Foto de Yousef Espanioly en Unsplash

Integer

Esto nos lleva a la siguiente sección del artículo. Podemos ver que la propiedad id del objeto human es de tipo int. Si a continuación realizamos dir() sobre la propiedad id y filtramos los métodos que están rodeados de dobles guiones bajos, nos encontraremos con que hay en total 62 métodos mágicos.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.id)))))

Aquí explicaré los métodos clave:

__add__

Este método es llamado cuando intentamos sumar dos números.

Como ejemplo:

hombre.id + 2 es lo mismo que humano.id.__add__(2)

__y__

Este método se ejecuta cuando intentamos utilizar el operador & e.g.:

return self & another_value

__bool__

Este método se ejecuta cuando intentamos realizar la comprobación booleana de un objeto e.g.

self != 123

__floordiv__

Este método se ejecuta cuando ejecutamos el operador //.

Foto de Johannes Plenio en Unsplash

__getnewargs__

Ocasionalmente, hacemos pickle de objetos en Python. El decapado crea una representación de flujo de bytes de un objeto en la memoria que puede guardarse en el disco si es necesario.

El método __getnewargs__() informa al proceso de decapado sobre cómo necesita cargar de nuevo el objeto decapado para recrear el objeto de destino. En particular, cómo debe crearse el objeto pasando los argumentos al método new(). De ahí el nombre ‘get new args’.

__index__

Consecuentemente, un objeto puede ser convertido en un entero ejecutando el método __index__(). También podemos anular el método y proporcionar nuestra propia funcionalidad de cómo debe generarse el índice.

__invert__

Este método se ejecuta cuando utilizamos el operador ~.

Como instancia:

first = Human(1, 'Farhad')
print(~first.id)

Es lo mismo que ejecutar:

first = Human(1, 'Farhad')
print(first.id.__invert__())

__lshift__

Este método nos da el desplazamiento a la izquierda de un valor por ejemplo el valor self <<. Podemos sobrecargar el operador << anulando el método __lshift__().

Nota: __rshift__() se ejecuta cuando realizamos el operador >>.

__mod__

Este método se llama cuando utilizamos el operador %.

__neg__

Este método se ejecuta cuando utilizamos el operador negativo – por ejemplo.

first.id — second.id

__subclasshook__

Este método puede ser sobrescrito para personalizar el método issubclass(). Esencialmente, devuelve True si una clase es una subclase y False si no lo es. El método también devuelve NotImplemented lo que permite utilizar el algoritmo existente.

El método puede personalizar el resultado del método issubclass().

Foto de Patrick Selin en Unsplash

Cadena

Esto nos lleva a la siguiente sección del artículo.

Podemos ver que la propiedad name del objeto human es de tipo string. Si a continuación realizamos dir(humano.nombre) sobre la propiedad y filtramos los métodos que están rodeados de doble guión bajo, nos daremos cuenta de que hay en total 33 métodos mágicos.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.name)))))

Aquí explicaré los 4 métodos clave ya que en el resto del artículo ya se han destacado la mayoría de los métodos mágicos.

__contains__

Este método se ejecuta cuando intentamos comprobar si un carácter dado existe.

__len__

Este método devuelve la longitud de la cadena. Se ejecuta cuando realizamos el método len().

Si queremos contar sólo con caracteres específicos para calcular la longitud de la cadena entonces el método __()__ puede ser sobrescrito para proporcionar esa funcionalidad.

__repr__

Este método se ejecuta cuando queremos crear una representación amigable para el desarrollador de un objeto.

Nota, el __repr__ se ejecuta cuando imprimimos(objeto) si no tenemos una implementación de __str__() en nuestra clase.

def __repr__(self):
return f'id={self.id} ({type(self.id)}). name={self.name} ({type(self.name)})'print(Human(1, 'Farhad'))

Esto imprimirá:

id=1 (<clase ‘int’>). name=Farhad (<clase ‘str’>)

El método __repr__() debe estar destinado a producir la representación oficial de un objeto.

__iadd__

Ocasionalmente, utilizamos un operador de adición junto a la asignación e.g.

self.id += 1

Esto equivale a self.id = self.id + 1

El método iadd() se ejecuta cuando realizamos la adición con la asignación.

Además, el método __ipow__() se ejecuta cuando se realiza **=.

El método mágico __iand__() es para realizar un AND a nivel de bits con asignación y __ior__() se llama cuando intentamos hacer != como: i != j

Lista

Esto nos lleva a la siguiente sección del artículo. La propiedad addresses del objeto human es de tipo list. Si a continuación realizamos dir(humano.direcciones) sobre la propiedad direcciones y filtramos los métodos que están rodeados de doble guión bajo, nos encontraremos con que hay en total 35 métodos mágicos.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.addresses)))))

Aquí explicaré los métodos clave:

__reduce__

Cuando un objeto es decapado, se ejecuta el método __reduce__() para devolver un objeto que ayude al decapante a entender cómo construirlo de nuevo.

__reduce_ex__

El método __reduce_ex__() es preferido por el pickle sobre el método __reduce__().

El método __reduce_ex__() toma un argumento entero que es la versión del protocolo. Proporciona compatibilidad con versiones anteriores para el decapado y se utiliza para construir el flujo de bytes decapado al objeto.

__reversed__

El método __reversed__() se ejecuta cuando intentamos invertir una colección en la secuencia inversa.

Se ejecuta cuando se llama a reversed(collection) o collection.reverse(). A veces, decidimos alterar la funcionalidad del método reversed().

Sobreescribiendo el método __reversed__(), podemos conseguir el resultado deseado.

Diccionario

Esto nos lleva a la quinta sección del artículo.

El diccionario es uno de los principales tipos construidos en Python. Si realizamos dir(human.maps) y filtramos los métodos que están rodeados de doble guión bajo, nos encontraremos con que hay en total 29 métodos mágicos.

print(len(list(filter(lambda x: x.startswith('__') and x.endswith('__'), dir(human1.maps)))))

Entre los 29 métodos mágicos, explicaré aquí los 4 métodos clave:

__delitem__

Este método se ejecuta cuando borramos un elemento e.g.

del dictionary

__getitem__

Este método se ejecuta cuando intentamos obtener un elemento para una clave:

item = dictionary

__setitem__

Este método se ejecuta cuando intentamos establecer un elemento en el diccionario:

dictionary = item

__iter__

Este método devuelve un iterador para la colección. Un iterador nos ayuda a iterar sobre una colección.

Podemos anular cómo se ejecuta el iterador() anulando el método __iter__().

Por último, una nota sobre __call__()

¿Y si quisiéramos hacer que nuestro objeto fuera llamable? Consideremos que queremos convertir el objeto humano en una función human() invocable?

El método __call__() nos permite tratar las clases como funciones.

human = Human(1, 'Farhad')
human()

Podemos conseguir esta funcionalidad proporcionando la implementación del método mágico __call__() en nuestra clase Human.

Esto imprimirá:

Has intentado llamar a
Argumentos: ()
Palabra clave Argumentos: {}
id=1 (<clase ‘int’>). name=Farhad (<clase ‘str’>)
Llamada completada

Al sobrescribir el método __call__(), ahora podemos implementar un decorador para devolver un objeto como función o incluso llamar a aquellas bibliotecas que aceptan funciones como argumentos pasando los objetos reales.

Foto de Dollar Gill en Unsplash

Resumen

Este es un tema de nivel avanzado para los desarrolladores de Python y lo recomiendo a todo aquel que esté/tenga intención de utilizar el lenguaje de programación Python.

Este artículo pretendía explicar qué son los métodos mágicos y cómo se pueden utilizar para construir aplicaciones en Python. Proporcionó una visión general de los métodos mágicos más utilizados en una clase personalizada, enteros, cadenas, listas y tipos de datos de diccionario.

Aunque todos los métodos en Python son públicos, la convención de codificación es rodear todos los métodos privados por dobles guiones bajos __<method>__()

Esto implica que los métodos mágicos están destinados a ser métodos privados. También significa que el llamador de un objeto no debe invocar directamente el método y el método es invocado por la clase internamente que tiene el método mágico. Los métodos mágicos nos permiten tener más control sobre cómo se comporta nuestra aplicación.

Los métodos mágicos pueden ser sobrescritos para enriquecer la funcionalidad y crear una lógica personalizada que se adapte mejor a las necesidades del negocio.

El propósito de esbozar los métodos mágicos clave es para que entendamos si queremos sobrescribir estos métodos en nuestras clases personalizadas para enriquecer las aplicaciones.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.