Crear un nuevo tipo primitivo

Beginner

This tutorial is from open-source community. Access the source code

Introducción

En este laboratorio, aprenderás cómo crear un nuevo tipo primitivo en Python e implementar métodos esenciales para él. También adquirirás una comprensión del protocolo de objetos de Python. En la mayoría de los programas de Python, se utilizan tipos primitivos incorporados como int, float y str para representar datos. Sin embargo, Python te permite crear tipos personalizados, como se ve en módulos como decimal y fractions en la biblioteca estándar.

En este laboratorio, crearás un nuevo tipo primitivo llamado MutInt (Entero mutable). A diferencia de los enteros inmutables de Python, MutInt se puede modificar después de su creación. Este ejercicio mostrará los principios fundamentales necesarios para crear un tipo primitivo completamente funcional en Python.

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 90%. Ha recibido una tasa de reseñas positivas del 97% por parte de los estudiantes.

Creación de una clase MutInt básica

Comencemos creando una clase básica para nuestro tipo de entero mutable. En programación, una clase es como un plano para crear objetos. En este paso, crearemos la base de nuestro nuevo tipo primitivo. Un tipo primitivo es un tipo de dato básico proporcionado por un lenguaje de programación, y aquí vamos a construir el nuestro propio personalizado.

  1. Abre el WebIDE y navega hasta el directorio /home/labex/project. El WebIDE es un entorno de desarrollo integrado donde puedes escribir, editar y ejecutar tu código. Navegar a este directorio asegura que todos tus archivos estén organizados en un solo lugar y puedan interactuar correctamente entre sí.

  2. Abre el archivo mutint.py que se creó para ti en el paso de configuración. Este archivo será el lugar donde definiremos nuestra clase MutInt.

  3. Añade el siguiente código para definir una clase MutInt básica:

## mutint.py

class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

El atributo __slots__ se utiliza para definir los atributos que esta clase puede tener. Los atributos son como variables que pertenecen a un objeto de la clase. Al usar __slots__, le decimos a Python que utilice una forma más eficiente en memoria para almacenar los atributos. En este caso, nuestra clase MutInt solo tendrá un único atributo llamado value. Esto significa que cada objeto de la clase MutInt solo podrá contener un dato, que es el valor entero.

El método __init__ es el constructor de nuestra clase. Un constructor es un método especial que se llama cuando se crea un objeto de la clase. Toma un parámetro value y lo almacena en el atributo value de la instancia. Una instancia es un objeto individual creado a partir del plano de la clase.

Vamos a probar nuestra clase creando un script de Python para usarla:

  1. Crea un nuevo archivo llamado test_mutint.py en el mismo directorio:
## test_mutint.py

from mutint import MutInt

## Create a MutInt object
a = MutInt(3)
print(f"Created MutInt with value: {a.value}")

## Modify the value (demonstrating mutability)
a.value = 42
print(f"Modified value to: {a.value}")

## Try adding (this will fail)
try:
    result = a + 10
    print(f"Result of a + 10: {result}")
except TypeError as e:
    print(f"Error when adding: {e}")

En este script de prueba, primero importamos la clase MutInt del archivo mutint.py. Luego creamos un objeto de la clase MutInt con un valor inicial de 3. Imprimimos el valor inicial, luego lo modificamos a 42 e imprimimos el nuevo valor. Finalmente, intentamos sumar 10 al objeto MutInt, lo que resultará en un error porque nuestra clase aún no admite la operación de suma.

  1. Ejecuta el script de prueba ejecutando el siguiente comando en la terminal:
python3 /home/labex/project/test_mutint.py

La terminal es una interfaz de línea de comandos donde puedes ejecutar varios comandos para interactuar con tu sistema y tu código. Ejecutar este comando ejecutará el script test_mutint.py.

Deberías ver una salida similar a esta:

Created MutInt with value: 3
Modified value to: 42
Error when adding: unsupported operand type(s) for +: 'MutInt' and 'int'

Nuestra clase MutInt almacena y actualiza un valor correctamente. Sin embargo, tiene varias limitaciones:

  • No se muestra correctamente cuando se imprime.
  • No admite operaciones matemáticas como la suma.
  • No admite comparaciones.
  • No admite conversiones de tipo.

En los siguientes pasos, abordaremos estas limitaciones una por una para hacer que nuestra clase MutInt se comporte más como un verdadero tipo primitivo.

Mejorando la representación en cadena

Cuando imprimes un objeto MutInt en Python, verás una salida como <__main__.MutInt object at 0x...>. Esta salida no es muy útil porque no te dice el valor real del objeto MutInt. Para facilitar la comprensión de lo que representa el objeto, implementaremos métodos especiales para la representación en cadena.

  1. Abre mutint.py en el WebIDE y actualízalo con el siguiente código:
## mutint.py

class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer-friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

Hemos agregado tres métodos importantes a la clase MutInt:

  • __str__(): Este método se llama cuando se utiliza la función str() en el objeto o cuando se imprime el objeto directamente. Debe devolver una cadena legible por humanos.
  • __repr__(): Este método proporciona la representación en cadena "oficial" del objeto. Se utiliza principalmente para depuración y, idealmente, debe devolver una cadena que, si se pasa a la función eval(), recrearía el objeto.
  • __format__(): Este método permite utilizar el sistema de formato de cadenas de Python con los objetos MutInt. Puedes utilizar especificaciones de formato como relleno y formato numérico.
  1. Crea un nuevo archivo de prueba llamado test_string_repr.py para probar estos nuevos métodos:
## test_string_repr.py

from mutint import MutInt

## Create a MutInt object
a = MutInt(3)

## Test string representation
print(f"str(a): {str(a)}")
print(f"repr(a): {repr(a)}")

## Test direct printing
print(f"Print a: {a}")

## Test string formatting
print(f"Formatted with padding: '{a:*^10}'")
print(f"Formatted as decimal: '{a:d}'")

## Test mutability
a.value = 42
print(f"After changing value, repr(a): {repr(a)}")

En este archivo de prueba, primero importamos la clase MutInt. Luego creamos un objeto MutInt con el valor 3. Probamos los métodos __str__() y __repr__() utilizando las funciones str() y repr(). También probamos la impresión directa, el formato de cadenas y la mutabilidad del objeto MutInt.

  1. Ejecuta el script de prueba:
python3 /home/labex/project/test_string_repr.py

Cuando ejecutes este comando, Python ejecutará el script test_string_repr.py. Deberías ver una salida similar a esta:

str(a): 3
repr(a): MutInt(3)
Print a: 3
Formatted with padding: '****3*****'
Formatted as decimal: '3'
After changing value, repr(a): MutInt(42)

Ahora nuestros objetos MutInt se muestran correctamente. La representación en cadena muestra el valor subyacente y podemos utilizar el formato de cadenas al igual que con los enteros normales.

La diferencia entre __str__() y __repr__() es que __str__() está destinado a producir una salida amigable para el usuario, mientras que __repr__() debe, idealmente, producir una cadena que, cuando se pase a eval(), recrearía el objeto. Es por eso que incluimos el nombre de la clase en el método __repr__().

El método __format__() permite que nuestro objeto funcione con el sistema de formato de Python, por lo que podemos utilizar especificaciones de formato como relleno y formato numérico.

Añadiendo operaciones matemáticas

Actualmente, nuestra clase MutInt no admite operaciones matemáticas como la suma. En Python, para habilitar tales operaciones en una clase personalizada, necesitamos implementar métodos especiales. Estos métodos especiales también se conocen como "métodos mágicos" o "métodos dunder" porque están rodeados de dos guiones bajos. Vamos a agregar la funcionalidad de suma implementando los métodos especiales relevantes para las operaciones aritméticas.

  1. Abre mutint.py en el WebIDE y actualízalo con el siguiente código:
## mutint.py

class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer-friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Handle addition: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Handle reversed addition: other + self."""
        ## For commutative operations like +, we can reuse __add__
        return self.__add__(other)

    def __iadd__(self, other):
        """Handle in-place addition: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

Hemos agregado tres nuevos métodos a la clase MutInt:

  • __add__(): Este método se llama cuando se utiliza el operador + con nuestro objeto MutInt en el lado izquierdo. Dentro de este método, primero comprobamos si el operando other es una instancia de MutInt o un int. Si es así, realizamos la suma y devolvemos un nuevo objeto MutInt con el resultado. Si el operando other es algo diferente, devolvemos NotImplemented. Esto le dice a Python que intente otros métodos o levante un TypeError.
  • __radd__(): Este método se llama cuando se utiliza el operador + con nuestro objeto MutInt en el lado derecho. Dado que la suma es una operación conmutativa (es decir, a + b es lo mismo que b + a), podemos simplemente reutilizar el método __add__.
  • __iadd__(): Este método se llama cuando se utiliza el operador += en nuestro objeto MutInt. En lugar de crear un nuevo objeto, modifica el objeto MutInt existente y lo devuelve.
  1. Crea un nuevo archivo de prueba llamado test_math_ops.py para probar estos nuevos métodos:
## test_math_ops.py

from mutint import MutInt

## Create MutInt objects
a = MutInt(3)
b = MutInt(5)

## Test regular addition
c = a + b
print(f"a + b = {c}")

## Test addition with int
d = a + 10
print(f"a + 10 = {d}")

## Test reversed addition
e = 7 + a
print(f"7 + a = {e}")

## Test in-place addition
print(f"Before a += 5: a = {a}")
a += 5
print(f"After a += 5: a = {a}")

## Test in-place addition with reference sharing
f = a  ## f and a point to the same object
print(f"Before a += 10: a = {a}, f = {f}")
a += 10
print(f"After a += 10: a = {a}, f = {f}")

## Test unsupported operation
try:
    result = a + 3.5  ## Adding a float is not supported
    print(f"a + 3.5 = {result}")
except TypeError as e:
    print(f"Error when adding float: {e}")

En este archivo de prueba, primero importamos la clase MutInt. Luego creamos algunos objetos MutInt y realizamos diferentes tipos de operaciones de suma. También probamos la suma in-place y el caso en el que se intenta una operación no admitida (sumar un número de punto flotante).

  1. Ejecuta el script de prueba:
python3 /home/labex/project/test_math_ops.py

Deberías ver una salida similar a esta:

a + b = MutInt(8)
a + 10 = MutInt(13)
7 + a = MutInt(10)
Before a += 5: a = MutInt(3)
After a += 5: a = MutInt(8)
Before a += 10: a = MutInt(8), f = MutInt(8)
After a += 10: a = MutInt(18), f = MutInt(18)
Error when adding float: unsupported operand type(s) for +: 'MutInt' and 'float'

Ahora nuestra clase MutInt admite operaciones de suma básicas. Observa que cuando usamos +=, tanto a como f se actualizaron. Esto muestra que a += 10 modificó el objeto existente en lugar de crear uno nuevo.

Este comportamiento con objetos mutables es similar al de los tipos mutables integrados de Python, como las listas. Por ejemplo:

a = [1, 2, 3]
b = a
a += [4, 5]  ## Both a and b are updated

En contraste, para tipos inmutables como las tuplas, += crea un nuevo objeto:

c = (1, 2, 3)
d = c
c += (4, 5)  ## c is a new object, d still points to the old one

Implementando operaciones de comparación

Actualmente, nuestros objetos MutInt no se pueden comparar entre sí ni con enteros normales. En Python, las operaciones de comparación como ==, <, <=, >, >= son muy útiles cuando se trabajan con objetos. Nos permiten determinar relaciones entre diferentes objetos, lo cual es crucial en muchos escenarios de programación, como la ordenación, el filtrado y las declaraciones condicionales. Entonces, vamos a agregar funcionalidad de comparación a nuestra clase MutInt implementando los métodos especiales para las operaciones de comparación.

  1. Abre mutint.py en el WebIDE y actualízalo con el siguiente código:
## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer-friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Handle addition: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Handle reversed addition: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Handle in-place addition: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

    def __eq__(self, other):
        """Handle equality comparison: self == other."""
        if isinstance(other, MutInt):
            return self.value == other.value
        elif isinstance(other, int):
            return self.value == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """Handle less-than comparison: self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

Hemos realizado varias mejoras clave:

  1. Importar y utilizar el decorador @total_ordering del módulo functools. El decorador @total_ordering es una herramienta poderosa en Python. Nos ayuda a ahorrar mucho tiempo y esfuerzo al implementar métodos de comparación para una clase. En lugar de definir manualmente los seis métodos de comparación (__eq__, __ne__, __lt__, __le__, __gt__, __ge__), solo necesitamos definir __eq__ y otro método de comparación (en nuestro caso, __lt__). El decorador generará automáticamente los otros cuatro métodos de comparación para nosotros.

  2. Agregar el método __eq__() para manejar las comparaciones de igualdad (==). Este método se utiliza para comprobar si dos objetos MutInt o un objeto MutInt y un entero tienen el mismo valor.

  3. Agregar el método __lt__() para manejar las comparaciones de menor que (<). Este método se utiliza para determinar si un objeto MutInt o un objeto MutInt comparado con un entero tiene un valor menor.

  4. Crea un nuevo archivo de prueba llamado test_comparisons.py para probar estos nuevos métodos:

## test_comparisons.py

from mutint import MutInt

## Create MutInt objects
a = MutInt(3)
b = MutInt(3)
c = MutInt(5)

## Test equality
print(f"a == b: {a == b}")  ## Should be True (same value)
print(f"a == c: {a == c}")  ## Should be False (different values)
print(f"a == 3: {a == 3}")  ## Should be True (comparing with int)
print(f"a == 5: {a == 5}")  ## Should be False (different values)

## Test less than
print(f"a < c: {a < c}")    ## Should be True (3 < 5)
print(f"c < a: {c < a}")    ## Should be False (5 is not < 3)
print(f"a < 4: {a < 4}")    ## Should be True (3 < 4)

## Test other comparisons (provided by @total_ordering)
print(f"a <= b: {a <= b}")  ## Should be True (3 <= 3)
print(f"a > c: {a > c}")    ## Should be False (3 is not > 5)
print(f"c >= a: {c >= a}")  ## Should be True (5 >= 3)

## Test with a different type
print(f"a == '3': {a == '3'}")  ## Should be False (different types)

En este archivo de prueba, creamos varios objetos MutInt y realizamos diferentes operaciones de comparación en ellos. También comparamos objetos MutInt con enteros normales y un tipo diferente (una cadena en este caso). Al ejecutar estas pruebas, podemos verificar que nuestros métodos de comparación funcionan como se espera.

  1. Ejecuta el script de prueba:
python3 /home/labex/project/test_comparisons.py

Deberías ver una salida similar a esta:

a == b: True
a == c: False
a == 3: True
a == 5: False
a < c: True
c < a: False
a < 4: True
a <= b: True
a > c: False
c >= a: True
a == '3': False

Ahora nuestra clase MutInt admite todas las operaciones de comparación.

El decorador @total_ordering es especialmente útil porque nos ahorra tener que implementar manualmente los seis métodos de comparación. Al proporcionar solo __eq__ y __lt__, Python puede derivar automáticamente los otros cuatro métodos de comparación.

Al implementar clases personalizadas, generalmente es una buena práctica hacer que funcionen tanto con objetos del mismo tipo como con tipos integrados cuando tiene sentido. Es por eso que nuestros métodos de comparación manejan tanto objetos MutInt como enteros normales. De esta manera, nuestra clase MutInt se puede utilizar de manera más flexible en diferentes escenarios de programación.

Agregando Conversiones de Tipo

Nuestra clase MutInt actualmente soporta operaciones de adición y comparación. Sin embargo, no funciona con las funciones de conversión integradas de Python como int() y float(). Estas funciones de conversión son muy útiles en Python. Por ejemplo, cuando quieres convertir un valor a un entero o a un número de punto flotante para diferentes cálculos u operaciones, dependes de estas funciones. Así que, agreguemos las capacidades a nuestra clase MutInt para que funcione con ellas.

  1. Abre mutint.py en el WebIDE y actualízalo con el siguiente código:
## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer - friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Handle addition: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Handle reversed addition: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Handle in - place addition: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

    def __eq__(self, other):
        """Handle equality comparison: self == other."""
        if isinstance(other, MutInt):
            return self.value == other.value
        elif isinstance(other, int):
            return self.value == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """Handle less - than comparison: self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

    def __int__(self):
        """Convert to int."""
        return self.value

    def __float__(self):
        """Convert to float."""
        return float(self.value)

    __index__ = __int__  ## Support array indexing and other operations requiring an index

    def __lshift__(self, other):
        """Handle left shift: self << other."""
        if isinstance(other, MutInt):
            return MutInt(self.value << other.value)
        elif isinstance(other, int):
            return MutInt(self.value << other)
        else:
            return NotImplemented

    def __rlshift__(self, other):
        """Handle reversed left shift: other << self."""
        if isinstance(other, int):
            return MutInt(other << self.value)
        else:
            return NotImplemented

Hemos añadido tres nuevos métodos a la clase MutInt:

  1. __int__(): Este método es llamado cuando usas la función int() en un objeto de nuestra clase MutInt. Por ejemplo, si tienes un objeto MutInt llamado a, y escribes int(a), Python llamará al método __int__() del objeto a.
  2. __float__(): De manera similar, este método es llamado cuando usas la función float() en nuestro objeto MutInt.
  3. __index__(): Este método se utiliza para operaciones que requieren específicamente un índice entero. Por ejemplo, cuando quieres acceder a un elemento en una lista usando un índice, o realizar operaciones de longitud de bits (bit-length operations), Python necesita un índice entero.
  4. __lshift__(): Este método maneja las operaciones de desplazamiento a la izquierda (left shift operations) cuando el objeto MutInt está en el lado izquierdo del operador <<.
  5. __rlshift__(): Este método maneja las operaciones de desplazamiento a la izquierda (left shift operations) cuando el objeto MutInt está en el lado derecho del operador <<.

El método __index__ es crucial para operaciones que demandan un índice entero, como el indexado de listas, el slicing (rebanado) y las operaciones de longitud de bits. En nuestra implementación simple, lo establecemos para que sea el mismo que __int__ porque el valor de nuestro objeto MutInt puede ser usado directamente como un índice entero.

Los métodos __lshift__ y __rlshift__ son esenciales para soportar las operaciones bit a bit de desplazamiento a la izquierda. Permiten que nuestros objetos MutInt participen en operaciones bit a bit, lo cual es un requisito común para los tipos similares a enteros.

  1. Crea un nuevo archivo de prueba llamado test_conversions.py para probar estos nuevos métodos:
## test_conversions.py

from mutint import MutInt

## Create a MutInt object
a = MutInt(3)

## Test conversions
print(f"int(a): {int(a)}")
print(f"float(a): {float(a)}")

## Test using as an index
names = ['Dave', 'Guido', 'Paula', 'Thomas', 'Lewis']
print(f"names[a]: {names[a]}")

## Test using in bit operations (requires __index__)
print(f"1 << a: {1 << a}")  ## Shift left by 3

## Test hex/oct/bin functions (requires __index__)
print(f"hex(a): {hex(a)}")
print(f"oct(a): {oct(a)}")
print(f"bin(a): {bin(a)}")

## Modify and test again
a.value = 4
print(f"\nAfter changing value to 4:")
print(f"int(a): {int(a)}")
print(f"names[a]: {names[a]}")
  1. Ejecuta el script de prueba:
python3 /home/labex/project/test_conversions.py

Deberías ver una salida similar a esta:

int(a): 3
float(a): 3.0
names[a]: Thomas
1 << a: 8
hex(a): 0x3
oct(a): 0o3
bin(a): 0b11

After changing value to 4:
int(a): 4
names[a]: Lewis

Ahora nuestra clase MutInt puede ser convertida a tipos estándar de Python y usada en operaciones que requieren un índice entero.

El método __index__ es particularmente importante. Fue introducido en Python para permitir que los objetos sean usados en situaciones donde se requiere un índice entero, como el indexado de listas, las operaciones bit a bit y varias funciones como hex(), oct() y bin().

Con estas adiciones, nuestra clase MutInt es ahora un tipo primitivo bastante completo. Puede ser usado en la mayoría de los contextos donde un entero regular sería usado, con el beneficio añadido de ser mutable.

Implementación Completa de MutInt

Aquí está nuestra implementación completa de MutInt con todas las características que hemos añadido:

## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    A mutable integer class that allows its value to be modified after creation.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Initialize with an integer value."""
        self.value = value

    def __str__(self):
        """Return a string representation for printing."""
        return str(self.value)

    def __repr__(self):
        """Return a developer - friendly string representation."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Support string formatting with format specifications."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Handle addition: self + other."""
        if isinstance(other, MutInt):
            return MutInt(self.value + other.value)
        elif isinstance(other, int):
            return MutInt(self.value + other)
        else:
            return NotImplemented

    def __radd__(self, other):
        """Handle reversed addition: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Handle in - place addition: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

    def __eq__(self, other):
        """Handle equality comparison: self == other."""
        if isinstance(other, MutInt):
            return self.value == other.value
        elif isinstance(other, int):
            return self.value == other
        else:
            return NotImplemented

    def __lt__(self, other):
        """Handle less - than comparison: self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

    def __int__(self):
        """Convert to int."""
        return self.value

    def __float__(self):
        """Convert to float."""
        return float(self.value)

    __index__ = __int__  ## Support array indexing and other operations requiring an index

    def __lshift__(self, other):
        """Handle left shift: self << other."""
        if isinstance(other, MutInt):
            return MutInt(self.value << other.value)
        elif isinstance(other, int):
            return MutInt(self.value << other)
        else:
            return NotImplemented

    def __rlshift__(self, other):
        """Handle reversed left shift: other << self."""
        if isinstance(other, int):
            return MutInt(other << self.value)
        else:
            return NotImplemented

Esta implementación cubre los aspectos clave de la creación de un nuevo tipo primitivo en Python. Para hacerlo aún más completo, podrías implementar métodos adicionales para otras operaciones como la resta, la multiplicación, la división, etc.

Resumen

En este laboratorio, has aprendido cómo crear tu propio tipo primitivo en Python. Específicamente, has dominado la creación de una clase de entero mutable similar a los tipos integrados, la implementación de métodos especiales para la visualización de objetos, la adición de soporte para operaciones matemáticas y de comparación, y la habilitación de conversiones de tipos para diversos contextos de Python.

Estos conceptos son esenciales para entender el modelo de objetos de Python y se pueden utilizar para crear tipos personalizados que se integren bien con las operaciones integradas. Para profundizar en tus conocimientos, considera implementar más operaciones matemáticas, agregar soporte para otras funciones integradas y explorar tipos complejos como colecciones personalizadas. Los tipos personalizados en Python son una herramienta poderosa para extender el lenguaje según necesidades específicas.