Menu iconMenu icon
Python y SQL Biblia

Capítulo 6: Programación Orientada a Objetos en Python

6.3 Funciones Especiales de Python

Sumergámonos en las funciones especiales en Python, también conocidas como métodos "mágicos" o "dunder". Estos métodos proporcionan una forma simple de hacer que tus clases actúen como tipos integrados. Esto significa que puedes usar funciones específicas del tipo (como len o +) con tus objetos. Ya has visto estos en uso con el método __init__ para clases. ¡Explorémoslos más:

1. Métodos __str__ y __repr__

Los métodos __str__ y __repr__ en Python representan los objetos de la clase como una cadena: son métodos para la representación de una clase como una cadena. El método __str__ en Python representa los objetos de la clase como una cadena legible para humanos, mientras que el método __repr__ está destinado a ser una representación no ambigua del objeto, y idealmente debería contener más detalles que __str__. Si se define __repr__ y no __str__, los objetos se comportarán como si __str__=__repr__.

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'Employee[name={self.name}, age={self.age}]'

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, {self.age!r})'

emp = Employee('John Doe', 30)
print(str(emp)) # Employee[name=John Doe, age=30]
print(repr(emp)) # Employee('John Doe', 30)

2. Métodos __add__ y __sub__

Estos métodos se utilizan para sobrecargar los operadores + y -.

class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag

    def __add__(self, other):
        return Complex(self.real + other.real, self.imag + other.imag)

    def __sub__(self, other):
        return Complex(self.real - other.real, self.imag - other.imag)

    def __str__(self):
        return f'{self.real} + {self.imag}i'

c1 = Complex(1, 2)
c2 = Complex(2, 3)
c3 = c1 + c2
c4 = c1 - c2
print(c3) # 3 + 5i
print(c4) # -1 - 1i

3. Método __len__

El método __len__ devuelve la longitud (el número de elementos) de un objeto. El método solo debería ser implementado para clases que sean colecciones.

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def __len__(self):
        return len(self.items)

s = Stack()
s.push('Hello')
s.push('World')
print(len(s)) # 2

4. Métodos __getitem__ y __setitem__

El método __getitem__ se utiliza para implementar self[key] para acceso. Del mismo modo, __setitem__ se utiliza para la asignación a self[key].

class CustomDict:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, key):
        return self.items[key]

    def __setitem__(self, key, value):
        self.items[key] = value

custom_dict = CustomDict({'one': 1, 'two': 2})
print(custom_dict['one'])  # 1
custom_dict['three'] = 3
print(custom_dict['three'])  # 3

5. Métodos __eq__ y __ne__

Estos métodos se utilizan para sobrecargar los operadores (==) y (!=) respectivamente.

class Employee:
    def __init__(self, name, id):
        self.name = name
        self.id = id

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

    def __ne__(self, other):
        return self.id != other.id

emp1 = Employee('John', 'E101')
emp2 = Employee('Jane', 'E102')
emp3 = Employee('David', 'E101')

print(emp1 == emp2)  # False
print(emp1 == emp3)  # True
print(emp1 != emp3)  # False

6. Método __del__

El método __del__ es conocido como un método destructor en Python. Se llama cuando todas las referencias al objeto han sido eliminadas, es decir, cuando un objeto es recolectado por el recolector de basura.

class Test:
    def __init__(self):
        print('Constructor Executed')

    def __del__(self):
        print('Destructor Executed')

t1 = Test()  # Constructor Executed
t1 = None  # Destructor Executed

Como puedes ver, los métodos mágicos son la clave para el uso efectivo del paradigma de programación orientada a objetos en Python, lo que te permite definir comportamientos para clases personalizadas que son intuitivas de entender y fáciles de usar.

Decoradores en Python

De hecho, hay otro concepto de Python que podría ser interesante discutir en este capítulo: los decoradores en Python, que pueden ser muy útiles cuando deseas cambiar el comportamiento de un método sin cambiar su código fuente.

Un decorador en Python es una herramienta poderosa que ayuda a los desarrolladores a modificar el comportamiento de una función, método o definición de clase sin tener que reescribir todo el código. Es una función de orden superior que toma otra función como argumento y devuelve una versión modificada de ella.

El decorador modifica el objeto original, que se le pasa como argumento, y devuelve una versión actualizada que está vinculada al nombre utilizado en la definición. Los decoradores se utilizan ampliamente en la comunidad de Python y son una característica clave del lenguaje que permite a los desarrolladores escribir un código más conciso y elegante.

Son particularmente útiles cuando se trabaja con bases de código grandes, ya que permiten a los desarrolladores realizar cambios en el comportamiento de una función sin tener que modificar su implementación. Además, los decoradores se pueden utilizar para agregar nueva funcionalidad a una función, como registro, almacenamiento en caché o autenticación, sin tener que modificar su código fuente.

En general, los decoradores son una herramienta poderosa que puede ayudar a los desarrolladores a escribir un código más eficiente y mantenible en Python.

Ejemplo:

Aquí tienes un ejemplo básico de un decorador en Python:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Cuando ejecutas este código, verás:hen you run this code, you'll see:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

En el ejemplo anterior, @my_decorator es un decorador. Las funciones que toman otras funciones como argumentos también se llaman funciones de orden superior. En este caso, my_decorator es una función de orden superior.

El símbolo @ es solo azúcar sintáctica que nos permite aplicar fácilmente un decorador a una función. La línea @my_decorator es equivalente a say_hello = my_decorator(say_hello).

Esto puede ser mucho para asimilar si eres nuevo en los decoradores. No te preocupes. Los decoradores son una herramienta poderosa en Python, pero pueden ser un poco difíciles de entender al principio. Simplemente tómate tu tiempo con este concepto, juega un poco con algunos ejemplos y le tomarás la mano.

El concepto de decoradores abre todo un nuevo mundo de posibilidades en Python. Se pueden utilizar para registro, aplicación de control de acceso y autenticación, limitación de velocidad, almacenamiento en caché y mucho más.

Los fabricantes de decoradores se pueden utilizar cuando deseas usar un decorador, pero necesitas proporcionarle argumentos. Un fabricante de decoradores es una función que devuelve un decorador. Así es cómo puedes crear uno:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

greet("World")

En este ejemplo, repeat(num_times=3) devuelve un decorador que repetirá la función decorada tres veces. Esto se llama un fabricante de decoradores.

Cuando ejecutas este código, verás:

Hello World
Hello World
Hello World

Como puedes ver, la función greet fue llamada tres veces.

Este es un uso más avanzado de los decoradores, pero una vez que los entiendes, pueden ser increíblemente poderosos y ayudar a que tu código sea más legible y mantenible. La capacidad de modificar el comportamiento de una función de una manera tan limpia y legible es una de las cosas que hace que Python sea un gran lenguaje para trabajar.

6.3 Funciones Especiales de Python

Sumergámonos en las funciones especiales en Python, también conocidas como métodos "mágicos" o "dunder". Estos métodos proporcionan una forma simple de hacer que tus clases actúen como tipos integrados. Esto significa que puedes usar funciones específicas del tipo (como len o +) con tus objetos. Ya has visto estos en uso con el método __init__ para clases. ¡Explorémoslos más:

1. Métodos __str__ y __repr__

Los métodos __str__ y __repr__ en Python representan los objetos de la clase como una cadena: son métodos para la representación de una clase como una cadena. El método __str__ en Python representa los objetos de la clase como una cadena legible para humanos, mientras que el método __repr__ está destinado a ser una representación no ambigua del objeto, y idealmente debería contener más detalles que __str__. Si se define __repr__ y no __str__, los objetos se comportarán como si __str__=__repr__.

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'Employee[name={self.name}, age={self.age}]'

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, {self.age!r})'

emp = Employee('John Doe', 30)
print(str(emp)) # Employee[name=John Doe, age=30]
print(repr(emp)) # Employee('John Doe', 30)

2. Métodos __add__ y __sub__

Estos métodos se utilizan para sobrecargar los operadores + y -.

class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag

    def __add__(self, other):
        return Complex(self.real + other.real, self.imag + other.imag)

    def __sub__(self, other):
        return Complex(self.real - other.real, self.imag - other.imag)

    def __str__(self):
        return f'{self.real} + {self.imag}i'

c1 = Complex(1, 2)
c2 = Complex(2, 3)
c3 = c1 + c2
c4 = c1 - c2
print(c3) # 3 + 5i
print(c4) # -1 - 1i

3. Método __len__

El método __len__ devuelve la longitud (el número de elementos) de un objeto. El método solo debería ser implementado para clases que sean colecciones.

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def __len__(self):
        return len(self.items)

s = Stack()
s.push('Hello')
s.push('World')
print(len(s)) # 2

4. Métodos __getitem__ y __setitem__

El método __getitem__ se utiliza para implementar self[key] para acceso. Del mismo modo, __setitem__ se utiliza para la asignación a self[key].

class CustomDict:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, key):
        return self.items[key]

    def __setitem__(self, key, value):
        self.items[key] = value

custom_dict = CustomDict({'one': 1, 'two': 2})
print(custom_dict['one'])  # 1
custom_dict['three'] = 3
print(custom_dict['three'])  # 3

5. Métodos __eq__ y __ne__

Estos métodos se utilizan para sobrecargar los operadores (==) y (!=) respectivamente.

class Employee:
    def __init__(self, name, id):
        self.name = name
        self.id = id

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

    def __ne__(self, other):
        return self.id != other.id

emp1 = Employee('John', 'E101')
emp2 = Employee('Jane', 'E102')
emp3 = Employee('David', 'E101')

print(emp1 == emp2)  # False
print(emp1 == emp3)  # True
print(emp1 != emp3)  # False

6. Método __del__

El método __del__ es conocido como un método destructor en Python. Se llama cuando todas las referencias al objeto han sido eliminadas, es decir, cuando un objeto es recolectado por el recolector de basura.

class Test:
    def __init__(self):
        print('Constructor Executed')

    def __del__(self):
        print('Destructor Executed')

t1 = Test()  # Constructor Executed
t1 = None  # Destructor Executed

Como puedes ver, los métodos mágicos son la clave para el uso efectivo del paradigma de programación orientada a objetos en Python, lo que te permite definir comportamientos para clases personalizadas que son intuitivas de entender y fáciles de usar.

Decoradores en Python

De hecho, hay otro concepto de Python que podría ser interesante discutir en este capítulo: los decoradores en Python, que pueden ser muy útiles cuando deseas cambiar el comportamiento de un método sin cambiar su código fuente.

Un decorador en Python es una herramienta poderosa que ayuda a los desarrolladores a modificar el comportamiento de una función, método o definición de clase sin tener que reescribir todo el código. Es una función de orden superior que toma otra función como argumento y devuelve una versión modificada de ella.

El decorador modifica el objeto original, que se le pasa como argumento, y devuelve una versión actualizada que está vinculada al nombre utilizado en la definición. Los decoradores se utilizan ampliamente en la comunidad de Python y son una característica clave del lenguaje que permite a los desarrolladores escribir un código más conciso y elegante.

Son particularmente útiles cuando se trabaja con bases de código grandes, ya que permiten a los desarrolladores realizar cambios en el comportamiento de una función sin tener que modificar su implementación. Además, los decoradores se pueden utilizar para agregar nueva funcionalidad a una función, como registro, almacenamiento en caché o autenticación, sin tener que modificar su código fuente.

En general, los decoradores son una herramienta poderosa que puede ayudar a los desarrolladores a escribir un código más eficiente y mantenible en Python.

Ejemplo:

Aquí tienes un ejemplo básico de un decorador en Python:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Cuando ejecutas este código, verás:hen you run this code, you'll see:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

En el ejemplo anterior, @my_decorator es un decorador. Las funciones que toman otras funciones como argumentos también se llaman funciones de orden superior. En este caso, my_decorator es una función de orden superior.

El símbolo @ es solo azúcar sintáctica que nos permite aplicar fácilmente un decorador a una función. La línea @my_decorator es equivalente a say_hello = my_decorator(say_hello).

Esto puede ser mucho para asimilar si eres nuevo en los decoradores. No te preocupes. Los decoradores son una herramienta poderosa en Python, pero pueden ser un poco difíciles de entender al principio. Simplemente tómate tu tiempo con este concepto, juega un poco con algunos ejemplos y le tomarás la mano.

El concepto de decoradores abre todo un nuevo mundo de posibilidades en Python. Se pueden utilizar para registro, aplicación de control de acceso y autenticación, limitación de velocidad, almacenamiento en caché y mucho más.

Los fabricantes de decoradores se pueden utilizar cuando deseas usar un decorador, pero necesitas proporcionarle argumentos. Un fabricante de decoradores es una función que devuelve un decorador. Así es cómo puedes crear uno:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

greet("World")

En este ejemplo, repeat(num_times=3) devuelve un decorador que repetirá la función decorada tres veces. Esto se llama un fabricante de decoradores.

Cuando ejecutas este código, verás:

Hello World
Hello World
Hello World

Como puedes ver, la función greet fue llamada tres veces.

Este es un uso más avanzado de los decoradores, pero una vez que los entiendes, pueden ser increíblemente poderosos y ayudar a que tu código sea más legible y mantenible. La capacidad de modificar el comportamiento de una función de una manera tan limpia y legible es una de las cosas que hace que Python sea un gran lenguaje para trabajar.

6.3 Funciones Especiales de Python

Sumergámonos en las funciones especiales en Python, también conocidas como métodos "mágicos" o "dunder". Estos métodos proporcionan una forma simple de hacer que tus clases actúen como tipos integrados. Esto significa que puedes usar funciones específicas del tipo (como len o +) con tus objetos. Ya has visto estos en uso con el método __init__ para clases. ¡Explorémoslos más:

1. Métodos __str__ y __repr__

Los métodos __str__ y __repr__ en Python representan los objetos de la clase como una cadena: son métodos para la representación de una clase como una cadena. El método __str__ en Python representa los objetos de la clase como una cadena legible para humanos, mientras que el método __repr__ está destinado a ser una representación no ambigua del objeto, y idealmente debería contener más detalles que __str__. Si se define __repr__ y no __str__, los objetos se comportarán como si __str__=__repr__.

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'Employee[name={self.name}, age={self.age}]'

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, {self.age!r})'

emp = Employee('John Doe', 30)
print(str(emp)) # Employee[name=John Doe, age=30]
print(repr(emp)) # Employee('John Doe', 30)

2. Métodos __add__ y __sub__

Estos métodos se utilizan para sobrecargar los operadores + y -.

class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag

    def __add__(self, other):
        return Complex(self.real + other.real, self.imag + other.imag)

    def __sub__(self, other):
        return Complex(self.real - other.real, self.imag - other.imag)

    def __str__(self):
        return f'{self.real} + {self.imag}i'

c1 = Complex(1, 2)
c2 = Complex(2, 3)
c3 = c1 + c2
c4 = c1 - c2
print(c3) # 3 + 5i
print(c4) # -1 - 1i

3. Método __len__

El método __len__ devuelve la longitud (el número de elementos) de un objeto. El método solo debería ser implementado para clases que sean colecciones.

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def __len__(self):
        return len(self.items)

s = Stack()
s.push('Hello')
s.push('World')
print(len(s)) # 2

4. Métodos __getitem__ y __setitem__

El método __getitem__ se utiliza para implementar self[key] para acceso. Del mismo modo, __setitem__ se utiliza para la asignación a self[key].

class CustomDict:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, key):
        return self.items[key]

    def __setitem__(self, key, value):
        self.items[key] = value

custom_dict = CustomDict({'one': 1, 'two': 2})
print(custom_dict['one'])  # 1
custom_dict['three'] = 3
print(custom_dict['three'])  # 3

5. Métodos __eq__ y __ne__

Estos métodos se utilizan para sobrecargar los operadores (==) y (!=) respectivamente.

class Employee:
    def __init__(self, name, id):
        self.name = name
        self.id = id

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

    def __ne__(self, other):
        return self.id != other.id

emp1 = Employee('John', 'E101')
emp2 = Employee('Jane', 'E102')
emp3 = Employee('David', 'E101')

print(emp1 == emp2)  # False
print(emp1 == emp3)  # True
print(emp1 != emp3)  # False

6. Método __del__

El método __del__ es conocido como un método destructor en Python. Se llama cuando todas las referencias al objeto han sido eliminadas, es decir, cuando un objeto es recolectado por el recolector de basura.

class Test:
    def __init__(self):
        print('Constructor Executed')

    def __del__(self):
        print('Destructor Executed')

t1 = Test()  # Constructor Executed
t1 = None  # Destructor Executed

Como puedes ver, los métodos mágicos son la clave para el uso efectivo del paradigma de programación orientada a objetos en Python, lo que te permite definir comportamientos para clases personalizadas que son intuitivas de entender y fáciles de usar.

Decoradores en Python

De hecho, hay otro concepto de Python que podría ser interesante discutir en este capítulo: los decoradores en Python, que pueden ser muy útiles cuando deseas cambiar el comportamiento de un método sin cambiar su código fuente.

Un decorador en Python es una herramienta poderosa que ayuda a los desarrolladores a modificar el comportamiento de una función, método o definición de clase sin tener que reescribir todo el código. Es una función de orden superior que toma otra función como argumento y devuelve una versión modificada de ella.

El decorador modifica el objeto original, que se le pasa como argumento, y devuelve una versión actualizada que está vinculada al nombre utilizado en la definición. Los decoradores se utilizan ampliamente en la comunidad de Python y son una característica clave del lenguaje que permite a los desarrolladores escribir un código más conciso y elegante.

Son particularmente útiles cuando se trabaja con bases de código grandes, ya que permiten a los desarrolladores realizar cambios en el comportamiento de una función sin tener que modificar su implementación. Además, los decoradores se pueden utilizar para agregar nueva funcionalidad a una función, como registro, almacenamiento en caché o autenticación, sin tener que modificar su código fuente.

En general, los decoradores son una herramienta poderosa que puede ayudar a los desarrolladores a escribir un código más eficiente y mantenible en Python.

Ejemplo:

Aquí tienes un ejemplo básico de un decorador en Python:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Cuando ejecutas este código, verás:hen you run this code, you'll see:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

En el ejemplo anterior, @my_decorator es un decorador. Las funciones que toman otras funciones como argumentos también se llaman funciones de orden superior. En este caso, my_decorator es una función de orden superior.

El símbolo @ es solo azúcar sintáctica que nos permite aplicar fácilmente un decorador a una función. La línea @my_decorator es equivalente a say_hello = my_decorator(say_hello).

Esto puede ser mucho para asimilar si eres nuevo en los decoradores. No te preocupes. Los decoradores son una herramienta poderosa en Python, pero pueden ser un poco difíciles de entender al principio. Simplemente tómate tu tiempo con este concepto, juega un poco con algunos ejemplos y le tomarás la mano.

El concepto de decoradores abre todo un nuevo mundo de posibilidades en Python. Se pueden utilizar para registro, aplicación de control de acceso y autenticación, limitación de velocidad, almacenamiento en caché y mucho más.

Los fabricantes de decoradores se pueden utilizar cuando deseas usar un decorador, pero necesitas proporcionarle argumentos. Un fabricante de decoradores es una función que devuelve un decorador. Así es cómo puedes crear uno:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

greet("World")

En este ejemplo, repeat(num_times=3) devuelve un decorador que repetirá la función decorada tres veces. Esto se llama un fabricante de decoradores.

Cuando ejecutas este código, verás:

Hello World
Hello World
Hello World

Como puedes ver, la función greet fue llamada tres veces.

Este es un uso más avanzado de los decoradores, pero una vez que los entiendes, pueden ser increíblemente poderosos y ayudar a que tu código sea más legible y mantenible. La capacidad de modificar el comportamiento de una función de una manera tan limpia y legible es una de las cosas que hace que Python sea un gran lenguaje para trabajar.

6.3 Funciones Especiales de Python

Sumergámonos en las funciones especiales en Python, también conocidas como métodos "mágicos" o "dunder". Estos métodos proporcionan una forma simple de hacer que tus clases actúen como tipos integrados. Esto significa que puedes usar funciones específicas del tipo (como len o +) con tus objetos. Ya has visto estos en uso con el método __init__ para clases. ¡Explorémoslos más:

1. Métodos __str__ y __repr__

Los métodos __str__ y __repr__ en Python representan los objetos de la clase como una cadena: son métodos para la representación de una clase como una cadena. El método __str__ en Python representa los objetos de la clase como una cadena legible para humanos, mientras que el método __repr__ está destinado a ser una representación no ambigua del objeto, y idealmente debería contener más detalles que __str__. Si se define __repr__ y no __str__, los objetos se comportarán como si __str__=__repr__.

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'Employee[name={self.name}, age={self.age}]'

    def __repr__(self):
        return f'{self.__class__.__name__}({self.name!r}, {self.age!r})'

emp = Employee('John Doe', 30)
print(str(emp)) # Employee[name=John Doe, age=30]
print(repr(emp)) # Employee('John Doe', 30)

2. Métodos __add__ y __sub__

Estos métodos se utilizan para sobrecargar los operadores + y -.

class Complex:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag

    def __add__(self, other):
        return Complex(self.real + other.real, self.imag + other.imag)

    def __sub__(self, other):
        return Complex(self.real - other.real, self.imag - other.imag)

    def __str__(self):
        return f'{self.real} + {self.imag}i'

c1 = Complex(1, 2)
c2 = Complex(2, 3)
c3 = c1 + c2
c4 = c1 - c2
print(c3) # 3 + 5i
print(c4) # -1 - 1i

3. Método __len__

El método __len__ devuelve la longitud (el número de elementos) de un objeto. El método solo debería ser implementado para clases que sean colecciones.

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def __len__(self):
        return len(self.items)

s = Stack()
s.push('Hello')
s.push('World')
print(len(s)) # 2

4. Métodos __getitem__ y __setitem__

El método __getitem__ se utiliza para implementar self[key] para acceso. Del mismo modo, __setitem__ se utiliza para la asignación a self[key].

class CustomDict:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, key):
        return self.items[key]

    def __setitem__(self, key, value):
        self.items[key] = value

custom_dict = CustomDict({'one': 1, 'two': 2})
print(custom_dict['one'])  # 1
custom_dict['three'] = 3
print(custom_dict['three'])  # 3

5. Métodos __eq__ y __ne__

Estos métodos se utilizan para sobrecargar los operadores (==) y (!=) respectivamente.

class Employee:
    def __init__(self, name, id):
        self.name = name
        self.id = id

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

    def __ne__(self, other):
        return self.id != other.id

emp1 = Employee('John', 'E101')
emp2 = Employee('Jane', 'E102')
emp3 = Employee('David', 'E101')

print(emp1 == emp2)  # False
print(emp1 == emp3)  # True
print(emp1 != emp3)  # False

6. Método __del__

El método __del__ es conocido como un método destructor en Python. Se llama cuando todas las referencias al objeto han sido eliminadas, es decir, cuando un objeto es recolectado por el recolector de basura.

class Test:
    def __init__(self):
        print('Constructor Executed')

    def __del__(self):
        print('Destructor Executed')

t1 = Test()  # Constructor Executed
t1 = None  # Destructor Executed

Como puedes ver, los métodos mágicos son la clave para el uso efectivo del paradigma de programación orientada a objetos en Python, lo que te permite definir comportamientos para clases personalizadas que son intuitivas de entender y fáciles de usar.

Decoradores en Python

De hecho, hay otro concepto de Python que podría ser interesante discutir en este capítulo: los decoradores en Python, que pueden ser muy útiles cuando deseas cambiar el comportamiento de un método sin cambiar su código fuente.

Un decorador en Python es una herramienta poderosa que ayuda a los desarrolladores a modificar el comportamiento de una función, método o definición de clase sin tener que reescribir todo el código. Es una función de orden superior que toma otra función como argumento y devuelve una versión modificada de ella.

El decorador modifica el objeto original, que se le pasa como argumento, y devuelve una versión actualizada que está vinculada al nombre utilizado en la definición. Los decoradores se utilizan ampliamente en la comunidad de Python y son una característica clave del lenguaje que permite a los desarrolladores escribir un código más conciso y elegante.

Son particularmente útiles cuando se trabaja con bases de código grandes, ya que permiten a los desarrolladores realizar cambios en el comportamiento de una función sin tener que modificar su implementación. Además, los decoradores se pueden utilizar para agregar nueva funcionalidad a una función, como registro, almacenamiento en caché o autenticación, sin tener que modificar su código fuente.

En general, los decoradores son una herramienta poderosa que puede ayudar a los desarrolladores a escribir un código más eficiente y mantenible en Python.

Ejemplo:

Aquí tienes un ejemplo básico de un decorador en Python:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Cuando ejecutas este código, verás:hen you run this code, you'll see:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

En el ejemplo anterior, @my_decorator es un decorador. Las funciones que toman otras funciones como argumentos también se llaman funciones de orden superior. En este caso, my_decorator es una función de orden superior.

El símbolo @ es solo azúcar sintáctica que nos permite aplicar fácilmente un decorador a una función. La línea @my_decorator es equivalente a say_hello = my_decorator(say_hello).

Esto puede ser mucho para asimilar si eres nuevo en los decoradores. No te preocupes. Los decoradores son una herramienta poderosa en Python, pero pueden ser un poco difíciles de entender al principio. Simplemente tómate tu tiempo con este concepto, juega un poco con algunos ejemplos y le tomarás la mano.

El concepto de decoradores abre todo un nuevo mundo de posibilidades en Python. Se pueden utilizar para registro, aplicación de control de acceso y autenticación, limitación de velocidad, almacenamiento en caché y mucho más.

Los fabricantes de decoradores se pueden utilizar cuando deseas usar un decorador, pero necesitas proporcionarle argumentos. Un fabricante de decoradores es una función que devuelve un decorador. Así es cómo puedes crear uno:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}")

greet("World")

En este ejemplo, repeat(num_times=3) devuelve un decorador que repetirá la función decorada tres veces. Esto se llama un fabricante de decoradores.

Cuando ejecutas este código, verás:

Hello World
Hello World
Hello World

Como puedes ver, la función greet fue llamada tres veces.

Este es un uso más avanzado de los decoradores, pero una vez que los entiendes, pueden ser increíblemente poderosos y ayudar a que tu código sea más legible y mantenible. La capacidad de modificar el comportamiento de una función de una manera tan limpia y legible es una de las cosas que hace que Python sea un gran lenguaje para trabajar.