Explore los Métodos Especiales en Clases de Python

PythonPythonBeginner
Practicar Ahora

Introducción

En este laboratorio, explorará algunos de los métodos especiales de Python, a menudo llamados métodos "dunder" debido a sus nombres con doble guion bajo (__). Obtendrá una comprensión práctica de cómo estos métodos le permiten personalizar el comportamiento de sus clases y objetos.

Aprenderá sobre el método __new__ para controlar la creación de instancias y el método __del__ para la destrucción de objetos. También verá cómo usar __slots__ para optimizar el uso de memoria y restringir atributos, y cómo hacer que las instancias de su clase sean invocables como funciones con el método __call__. A través de ejemplos prácticos, aprenderá a escribir código Python más eficiente y expresivo.

Este es un Guided Lab, que proporciona instrucciones paso a paso para ayudarte a aprender y practicar. Sigue las instrucciones cuidadosamente para completar cada paso y obtener experiencia práctica. Los datos históricos muestran que este es un laboratorio de nivel principiante con una tasa de finalización del 100%. Ha recibido una tasa de reseñas positivas del 100% por parte de los estudiantes.

Comprender y Utilizar el Método __new__

En este paso, explorará el método __new__. Mientras que __init__ se usa comúnmente para inicializar los atributos de un objeto después de que ha sido creado, __new__ es el método que realmente crea la instancia en primer lugar. Se llama antes que __init__.

Aquí están las diferencias clave:

  • __new__ es un método estático que toma la clase (cls) como su primer argumento. Es responsable de crear y devolver una nueva instancia de la clase.
  • __init__ es un método de instancia que toma la instancia (self) como su primer argumento. Inicializa el objeto recién creado y no devuelve nada.

Normalmente, no necesita anular (override) __new__ porque la implementación predeterminada de la clase object es suficiente. Sin embargo, es útil para casos avanzados como la implementación del patrón Singleton o la creación de instancias de tipos inmutables.

Veamos __new__ en acción. Creará una clase Dog que imprime un mensaje durante la creación de la instancia.

Primero, abra el archivo dog_cat.py desde el explorador de archivos en el lado izquierdo del IDE.

Agregue el siguiente código al archivo dog_cat.py. Este código define una clase base Animal y una subclase Dog que anula el método __new__.

## File Name: dog_cat.py

class Animal:
    def __init__(self, name):
        self._name = name
        print(f'Initializing {self._name} in Animal.')

    def say(self):
        print(self._name + ' is saying something')

class Dog(Animal):
    ## El primer parámetro es cls, que se refiere a la clase misma.
    ## También debe aceptar cualquier argumento pasado al constructor.
    def __new__(cls, name, age):
        print('A new Dog instance is being created.')
        ## Llama al método __new__ de la clase padre para crear la instancia.
        instance = super().__new__(cls)
        return instance

    def __init__(self, name, age):
        print(f'Initializing {name} in Dog.')
        ## Se llama al __init__ de la clase padre para establecer el nombre.
        super().__init__(name)
        self.age = age

    def say(self):
        print(self._name + ' is making a sound: wang wang wang...')

## Create a Dog instance
print("Creating a Dog object...")
d = Dog('Buddy', 5)
print("Dog object created.")
print(f"Dog's name: {d._name}, Age: {d.age}")

Guarde el archivo (Ctrl+S o Cmd+S).

Ahora, abra una terminal en su IDE (puede usar el menú Terminal > New Terminal). Ejecute el script para observar el orden de las llamadas a los métodos.

python ~/project/dog_cat.py

Verá la siguiente salida. Observe que __new__ se llama primero para crear la instancia, seguido por los métodos __init__ para inicializarla.

Creating a Dog object...
A new Dog instance is being created.
Initializing Buddy in Dog.
Initializing Buddy in Animal.
Dog object created.
Dog's name: Buddy, Age: 5

Esto demuestra que __new__ controla la creación del objeto, y __init__ lo configura posteriormente.

Implementar y Probar el Método __del__

En este paso, aprenderá sobre el método __del__. Este método se denomina finalizador o destructor. Se invoca cuando el contador de referencias de un objeto cae a cero, lo que significa que está a punto de ser destruido por el recolector de basura (garbage collector) de Python. A menudo se utiliza para tareas de limpieza, como cerrar conexiones de red o manejadores de archivos (file handles).

Puede eliminar una referencia a un objeto usando la declaración del. Cuando desaparece la última referencia, __del__ se llama automáticamente.

Agreguemos un método __del__ a nuestra clase Dog para ver cuándo se destruye un objeto.

Abra nuevamente el archivo dog_cat.py. Reemplace todo el contenido del archivo con el siguiente código. Esta versión elimina el código que crea una instancia de Dog (para evitar crearla cuando se importa el módulo) y agrega el método __del__ a la clase Dog.

## File Name: dog_cat.py

class Animal:
    def __init__(self, name):
        self._name = name

    def say(self):
        print(self._name + ' is saying something')

class Dog(Animal):
    def __new__(cls, name, age):
        instance = super().__new__(cls)
        return instance

    def __init__(self, name, age):
        super().__init__(name)
        self.age = age

    def say(self):
        print(self._name + ' is making a sound: wang wang wang...')

    ## Add this method to the Dog class
    def __del__(self):
        print(f'The Dog object {self._name} is being deleted.')

Guarde el archivo dog_cat.py.

Ahora, creemos un script separado para probar este comportamiento. Abra el archivo test_del.py desde el explorador de archivos.

Agregue el siguiente código a test_del.py. Este script creará dos instancias de Dog y luego eliminará una de ellas explícitamente.

## File Name: test_del.py

from dog_cat import Dog
import time

print("Creating two Dog objects: d1 and d2.")
d1 = Dog('Tom', 3)
d2 = Dog('John', 5)

print("\nDeleting reference to d1...")
del d1
print("Reference to d1 deleted.")

## The garbage collector may not run immediately.
## We add a small delay to give it time to run.
time.sleep(1)

print("\nScript is about to end. d2 will be deleted automatically.")

Guarde el archivo. Ahora, ejecute el script test_del.py en la terminal.

python ~/project/test_del.py

Observe la salida. El mensaje __del__ para Tom aparece después de que se llama a del d1. El mensaje para John aparece al final, ya que el objeto d2 es recolectado por el recolector de basura cuando el script finaliza.

Creating two Dog objects: d1 and d2.

Deleting reference to d1...
The Dog object Tom is being deleted.
Reference to d1 deleted.

Script is about to end. d2 will be deleted automatically.
The Dog object John is being deleted.

Nota: El momento exacto de la recolección de basura puede variar. __del__ se llama cuando el objeto es recolectado, no necesariamente inmediatamente después de que se usa del.

Controlar Atributos con __slots__

En este paso, aprenderá sobre __slots__. Por defecto, Python almacena los atributos de instancia en un diccionario especial llamado __dict__. Esto le permite agregar nuevos atributos a un objeto en cualquier momento. Sin embargo, esta flexibilidad utiliza memoria adicional.

Al definir un atributo __slots__ en su clase, puede especificar una lista fija de atributos que pueden tener las instancias. Esto tiene dos efectos principales:

  1. Ahorro de Memoria: Python utiliza una estructura interna más compacta en lugar de un __dict__ para cada instancia, lo que puede reducir significativamente el uso de memoria, especialmente al crear muchos objetos.
  2. Restricción de Atributos: Ya no puede agregar atributos a una instancia que no estén listados en __slots__. Esto ayuda a prevenir errores tipográficos y a imponer una estructura de objeto estricta.

Creemos un ejemplo para ver cómo funciona __slots__. Abra el archivo slots_example.py desde el explorador de archivos.

Agregue el siguiente código a slots_example.py:

## File Name: slots_example.py

class Player:
    ## Define the allowed attributes using __slots__
    __slots__ = ('name', 'level')

    def __init__(self, name, level):
        self.name = name
        self.level = level

## Create an instance of Player
p1 = Player('Hero', 10)

## Access the allowed attributes
print(f"Player name: {p1.name}")
print(f"Player level: {p1.level}")

## Now, try to add a new attribute that is NOT in __slots__
print("\nTrying to add a 'score' attribute...")
try:
    p1.score = 100
    print(f"Player score: {p1.score}")
except AttributeError as e:
    print(f"Caught an error: {e}")

## Also, check if the instance has a __dict__ attribute
print("\nChecking for __dict__...")
try:
    print(p1.__dict__)
except AttributeError as e:
    print(f"Caught an error: {e}")

Guarde el archivo. Ahora, ejecute el script slots_example.py en la terminal.

python ~/project/slots_example.py

La salida muestra que puede asignar valores a name y level, pero intentar asignar un valor a score genera un AttributeError. También confirma que la instancia no tiene un __dict__.

Player name: Hero
Player level: 10

Trying to add a 'score' attribute...
Caught an error: 'Player' object has no attribute 'score'

Checking for __dict__...
Caught an error: 'Player' object has no attribute '__dict__'

Esto demuestra cómo __slots__ puede imponer un conjunto fijo de atributos y optimizar la memoria al eliminar el diccionario de instancia.

Hacer Instancias Llamables con __call__

En este paso, explorará el método __call__. En Python, los objetos que pueden ser "llamados" usando paréntesis () como funciones se conocen como objetos llamables (callable objects). Las funciones y los métodos son inherentemente llamables.

Por defecto, las instancias de clase no son llamables. Sin embargo, si define el método especial __call__ en una clase, sus instancias se vuelven llamables. Cuando llama a una instancia como si fuera una función, se ejecuta el código dentro de su método __call__. Esto es útil para crear objetos que se comportan como funciones pero que también pueden mantener su propio estado interno.

Creemos una clase cuyas instancias puedan ser llamadas. Abra el archivo callable_instance.py desde el explorador de archivos.

Agregue el siguiente código a callable_instance.py:

## File Name: callable_instance.py

class Greeter:
    def __init__(self, greeting):
        ## This state is stored with the instance
        self.greeting = greeting
        print(f'Greeter initialized with "{self.greeting}"')

    ## Define the __call__ method to make instances callable
    def __call__(self, name):
        ## This code runs when the instance is called
        print(f"{self.greeting}, {name}!")

## Create an instance of Greeter
hello_greeter = Greeter("Hello")

## Check if the instance is callable using the built-in callable() function
print(f"Is hello_greeter callable? {callable(hello_greeter)}")

## Now, call the instance as if it were a function
print("\nCalling the instance:")
hello_greeter("Alice")
hello_greeter("Bob")

## Create another instance with a different state
goodbye_greeter = Greeter("Goodbye")
print("\nCalling the second instance:")
goodbye_greeter("Charlie")

Guarde el archivo. Ahora, ejecute el script callable_instance.py en la terminal.

python ~/project/callable_instance.py

La salida muestra que la instancia hello_greeter es efectivamente llamable. Cada vez que la llama, se ejecuta el método __call__, utilizando el estado (self.greeting) que se estableció durante la inicialización.

Greeter initialized with "Hello"
Is hello_greeter callable? True

Calling the instance:
Hello, Alice!
Hello, Bob!
Greeter initialized with "Goodbye"

Calling the second instance:
Goodbye, Charlie!

Esto demuestra cómo __call__ le permite crear objetos con estado que se asemejan a funciones, lo cual es una característica poderosa en la programación orientada a objetos.

Resumen

En este laboratorio, ha explorado varios métodos especiales potentes en Python. Aprendió a usar __new__ para controlar el proceso de creación de instancias, lo que le proporciona un punto de enganche (hook) antes de que se llame a __init__. Implementó el método __del__ para definir la lógica de limpieza que se ejecuta cuando un objeto es recolectado por el recolector de basura (garbage collected). También utilizó __slots__ para optimizar la memoria e imponer un modelo de atributos estricto al prevenir la creación de un __dict__ de instancia. Finalmente, hizo que sus objetos se comportaran como funciones implementando el método __call__. Al dominar estos métodos "dunder" (doble guion bajo), puede escribir clases más flexibles, eficientes y Pythonicas.

Morty Proxy This is a proxified and sanitized view of the page, visit original site.