Criar um Novo Tipo Primitivo

Beginner

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

Introdução

Neste laboratório, você aprenderá como criar um novo tipo primitivo em Python e implementar métodos essenciais para ele. Você também obterá uma compreensão do protocolo de objetos do Python. Na maioria dos programas Python, tipos primitivos embutidos como int, float e str são usados para representar dados. No entanto, o Python permite que você crie tipos personalizados, como visto em módulos como decimal e fractions na biblioteca padrão.

Neste laboratório, você criará um novo tipo primitivo chamado MutInt (Inteiro Mutável). Ao contrário dos inteiros imutáveis do Python, MutInt pode ser modificado após a criação. Este exercício demonstrará os princípios fundamentais necessários para criar um tipo primitivo totalmente funcional em Python.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível iniciante com uma taxa de conclusão de 90%. Recebeu uma taxa de avaliações positivas de 97% dos estudantes.

Criando uma Classe MutInt Básica

Vamos começar criando uma classe básica para o nosso tipo Inteiro Mutável. Em programação, uma classe é como um projeto para criar objetos. Nesta etapa, criaremos a base do nosso novo tipo primitivo. Um tipo primitivo é um tipo de dado básico fornecido por uma linguagem de programação, e aqui vamos construir o nosso próprio personalizado.

  1. Abra o WebIDE e navegue até o diretório /home/labex/project. O WebIDE é um ambiente de desenvolvimento integrado onde você pode escrever, editar e executar seu código. Navegar para este diretório garante que todos os seus arquivos sejam organizados em um só lugar e possam interagir uns com os outros corretamente.

  2. Abra o arquivo mutint.py que foi criado para você na etapa de configuração. Este arquivo será o lar da nossa definição de classe MutInt.

  3. Adicione o seguinte código para definir uma classe MutInt básica:

## mutint.py

class MutInt:
    """
    Uma classe de inteiro mutável que permite que seu valor seja modificado após a criação.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Inicializa com um valor inteiro."""
        self.value = value

O atributo __slots__ é usado para definir os atributos que esta classe pode ter. Atributos são como variáveis que pertencem a um objeto da classe. Ao usar __slots__, dizemos ao Python para usar uma maneira mais eficiente em termos de memória para armazenar atributos. Neste caso, nossa classe MutInt terá apenas um único atributo chamado value. Isso significa que cada objeto da classe MutInt só poderá conter um pedaço de dados, que é o valor inteiro.

O método __init__ é o construtor para nossa classe. Um construtor é um método especial que é chamado quando um objeto da classe é criado. Ele recebe um parâmetro de valor e o armazena no atributo value da instância. Uma instância é um objeto individual criado a partir do projeto da classe.

Vamos testar nossa classe criando um script Python para usá-la:

  1. Crie um novo arquivo chamado test_mutint.py no mesmo diretório:
## 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}")

Neste script de teste, primeiro importamos a classe MutInt do arquivo mutint.py. Em seguida, criamos um objeto da classe MutInt com um valor inicial de 3. Imprimimos o valor inicial, depois o modificamos para 42 e imprimimos o novo valor. Finalmente, tentamos adicionar 10 ao objeto MutInt, o que resultará em um erro porque nossa classe ainda não suporta a operação de adição.

  1. Execute o script de teste executando o seguinte comando no terminal:
python3 /home/labex/project/test_mutint.py

O terminal é uma interface de linha de comando onde você pode executar vários comandos para interagir com seu sistema e seu código. Executar este comando executará o script test_mutint.py.

Você deve ver uma saída semelhante a esta:

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

Nossa classe MutInt armazena e atualiza um valor com sucesso. No entanto, ela tem várias limitações:

  • Não é exibida de forma agradável quando impressa
  • Não suporta operações matemáticas como adição
  • Não suporta comparações
  • Não suporta conversões de tipo

Nas próximas etapas, abordaremos essas limitações uma por uma para fazer com que nossa classe MutInt se comporte mais como um verdadeiro tipo primitivo.

Melhorando a Representação de String

Quando você imprime um objeto MutInt em Python, você verá uma saída como <__main__.MutInt object at 0x...>. Essa saída não é muito útil porque não informa o valor real do objeto MutInt. Para facilitar a compreensão do que o objeto representa, vamos implementar métodos especiais para representação de string.

  1. Abra mutint.py no WebIDE e atualize-o com o seguinte código:
## mutint.py

class MutInt:
    """
    Uma classe de inteiro mutável que permite que seu valor seja modificado após a criação.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Inicializa com um valor inteiro."""
        self.value = value

    def __str__(self):
        """Retorna uma representação de string para impressão."""
        return str(self.value)

    def __repr__(self):
        """Retorna uma representação de string amigável para desenvolvedores."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Suporta formatação de string com especificações de formato."""
        return format(self.value, fmt)

Adicionamos três métodos importantes à classe MutInt:

  • __str__(): Este método é chamado quando você usa a função str() no objeto ou quando você imprime o objeto diretamente. Ele deve retornar uma string legível por humanos.
  • __repr__(): Este método fornece a representação de string "oficial" do objeto. Ele é usado principalmente para depuração e, idealmente, deve retornar uma string que, se passada para a função eval(), recriaria o objeto.
  • __format__(): Este método permite que você use o sistema de formatação de string do Python com seus objetos MutInt. Você pode usar especificações de formato como preenchimento e formatação de números.
  1. Crie um novo arquivo de teste chamado test_string_repr.py para testar esses novos 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)}")

Neste arquivo de teste, primeiro importamos a classe MutInt. Em seguida, criamos um objeto MutInt com o valor 3. Testamos os métodos __str__() e __repr__() usando as funções str() e repr(). Também testamos a impressão direta, a formatação de string e a mutabilidade do objeto MutInt.

  1. Execute o script de teste:
python3 /home/labex/project/test_string_repr.py

Quando você executa este comando, o Python executará o script test_string_repr.py. Você deve ver uma saída semelhante 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)

Agora nossos objetos MutInt são exibidos de forma agradável. A representação de string mostra o valor subjacente, e podemos usar a formatação de string como com inteiros regulares.

A diferença entre __str__() e __repr__() é que __str__() é projetado para produzir uma saída amigável para humanos, enquanto __repr__() idealmente deve produzir uma string que, quando passada para eval(), recriaria o objeto. É por isso que incluímos o nome da classe no método __repr__().

O método __format__() permite que nosso objeto trabalhe com o sistema de formatação do Python, para que possamos usar especificações de formato como preenchimento e formatação de números.

Adicionando Operações Matemáticas

Atualmente, nossa classe MutInt não suporta operações matemáticas como adição. Em Python, para habilitar tais operações para uma classe personalizada, precisamos implementar métodos especiais. Esses métodos especiais também são conhecidos como "métodos mágicos" ou "métodos dunder" porque são cercados por sublinhados duplos. Vamos adicionar a funcionalidade de adição implementando os métodos especiais relevantes para operações aritméticas.

  1. Abra mutint.py no WebIDE e atualize-o com o seguinte código:
## mutint.py

class MutInt:
    """
    Uma classe de inteiro mutável que permite que seu valor seja modificado após a criação.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Inicializa com um valor inteiro."""
        self.value = value

    def __str__(self):
        """Retorna uma representação de string para impressão."""
        return str(self.value)

    def __repr__(self):
        """Retorna uma representação de string amigável para desenvolvedores."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Suporta formatação de string com especificações de formato."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Lida com a adição: 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):
        """Lida com a adição invertida: other + self."""
        ## For commutative operations like +, we can reuse __add__
        return self.__add__(other)

    def __iadd__(self, other):
        """Lida com a adição in-place: self += other."""
        if isinstance(other, MutInt):
            self.value += other.value
            return self
        elif isinstance(other, int):
            self.value += other
            return self
        else:
            return NotImplemented

Adicionamos três novos métodos à classe MutInt:

  • __add__(): Este método é chamado quando o operador + é usado com nosso objeto MutInt no lado esquerdo. Dentro deste método, primeiro verificamos se o operando other é uma instância de MutInt ou um int. Se for, realizamos a adição e retornamos um novo objeto MutInt com o resultado. Se o operando other for outra coisa, retornamos NotImplemented. Isso diz ao Python para tentar outros métodos ou lançar um TypeError.
  • __radd__(): Este método é chamado quando o operador + é usado com nosso objeto MutInt no lado direito. Como a adição é uma operação comutativa (ou seja, a + b é o mesmo que b + a), podemos simplesmente reutilizar o método __add__.
  • __iadd__(): Este método é chamado quando o operador += é usado em nosso objeto MutInt. Em vez de criar um novo objeto, ele modifica o objeto MutInt existente e o retorna.
  1. Crie um novo arquivo de teste chamado test_math_ops.py para testar esses novos 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}")

Neste arquivo de teste, primeiro importamos a classe MutInt. Em seguida, criamos alguns objetos MutInt e realizamos diferentes tipos de operações de adição. Também testamos a adição in-place e o caso em que uma operação não suportada (adicionando um float) é tentada.

  1. Execute o script de teste:
python3 /home/labex/project/test_math_ops.py

Você deve ver uma saída semelhante 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'

Agora nossa classe MutInt suporta operações básicas de adição. Observe que quando usamos +=, tanto a quanto f foram atualizados. Isso mostra que a += 10 modificou o objeto existente em vez de criar um novo.

Este comportamento com objetos mutáveis é semelhante aos tipos mutáveis embutidos do Python, como listas. Por exemplo:

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

Em contraste, para tipos imutáveis como tuplas, += cria um novo objeto:

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

Implementando Operações de Comparação

Atualmente, nossos objetos MutInt não podem ser comparados entre si ou com inteiros regulares. Em Python, operações de comparação como ==, <, <=, >, >= são muito úteis ao trabalhar com objetos. Elas nos permitem determinar relações entre diferentes objetos, o que é crucial em muitos cenários de programação, como classificação, filtragem e instruções condicionais. Então, vamos adicionar a funcionalidade de comparação à nossa classe MutInt implementando os métodos especiais para operações de comparação.

  1. Abra mutint.py no WebIDE e atualize-o com o seguinte código:
## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    Uma classe de inteiro mutável que permite que seu valor seja modificado após a criação.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Inicializa com um valor inteiro."""
        self.value = value

    def __str__(self):
        """Retorna uma representação de string para impressão."""
        return str(self.value)

    def __repr__(self):
        """Retorna uma representação de string amigável para desenvolvedores."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Suporta formatação de string com especificações de formato."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Lida com a adição: 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):
        """Lida com a adição invertida: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Lida com a adição in-place: 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):
        """Lida com a comparação de igualdade: 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):
        """Lida com a comparação menor que: self < other."""
        if isinstance(other, MutInt):
            return self.value < other.value
        elif isinstance(other, int):
            return self.value < other
        else:
            return NotImplemented

Fizemos várias melhorias importantes:

  1. Importamos e usamos o decorador @total_ordering do módulo functools. O decorador @total_ordering é uma ferramenta poderosa em Python. Ele nos ajuda a economizar muito tempo e esforço ao implementar métodos de comparação para uma classe. Em vez de definir manualmente todos os seis métodos de comparação (__eq__, __ne__, __lt__, __le__, __gt__, __ge__), só precisamos definir __eq__ e um outro método de comparação (no nosso caso, __lt__). O decorador então gerará automaticamente os quatro métodos de comparação restantes para nós.

  2. Adicionamos o método __eq__() para lidar com comparações de igualdade (==). Este método é usado para verificar se dois objetos MutInt ou um objeto MutInt e um inteiro têm o mesmo valor.

  3. Adicionamos o método __lt__() para lidar com comparações menor que (<). Este método é usado para determinar se um objeto MutInt ou um objeto MutInt comparado a um inteiro tem um valor menor.

  4. Crie um novo arquivo de teste chamado test_comparisons.py para testar esses novos 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)

Neste arquivo de teste, criamos vários objetos MutInt e realizamos diferentes operações de comparação neles. Também comparamos objetos MutInt com inteiros regulares e um tipo diferente (uma string neste caso). Ao executar esses testes, podemos verificar se nossos métodos de comparação funcionam como esperado.

  1. Execute o script de teste:
python3 /home/labex/project/test_comparisons.py

Você deve ver uma saída semelhante 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

Agora nossa classe MutInt suporta todas as operações de comparação.

O decorador @total_ordering é particularmente útil porque nos poupa de ter que implementar todos os seis métodos de comparação manualmente. Ao fornecer apenas __eq__ e __lt__, o Python pode derivar os outros quatro métodos de comparação automaticamente.

Ao implementar classes personalizadas, geralmente é uma boa prática fazê-las funcionar tanto com objetos do mesmo tipo quanto com tipos embutidos, onde isso faz sentido. É por isso que nossos métodos de comparação lidam com objetos MutInt e inteiros regulares. Dessa forma, nossa classe MutInt pode ser usada de forma mais flexível em diferentes cenários de programação.

Adicionando Conversões de Tipo

Nossa classe MutInt atualmente suporta operações de adição e comparação. No entanto, ela não funciona com as funções de conversão embutidas do Python, como int() e float(). Essas funções de conversão são muito úteis em Python. Por exemplo, quando você deseja converter um valor em um inteiro ou um número de ponto flutuante para diferentes cálculos ou operações, você confia nessas funções. Então, vamos adicionar as capacidades à nossa classe MutInt para trabalhar com elas.

  1. Abra mutint.py no WebIDE e atualize-o com o seguinte código:
## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    Uma classe de inteiro mutável que permite que seu valor seja modificado após a criação.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Inicializa com um valor inteiro."""
        self.value = value

    def __str__(self):
        """Retorna uma representação de string para impressão."""
        return str(self.value)

    def __repr__(self):
        """Retorna uma representação de string amigável para desenvolvedores."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Suporta formatação de string com especificações de formato."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Lida com a adição: 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):
        """Lida com a adição invertida: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Lida com a adição in-place: 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):
        """Lida com a comparação de igualdade: 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):
        """Lida com a comparação menor que: 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):
        """Converte para int."""
        return self.value

    def __float__(self):
        """Converte para float."""
        return float(self.value)

    __index__ = __int__  ## Suporta indexação de array e outras operações que exigem um índice

    def __lshift__(self, other):
        """Lida com deslocamento para a esquerda: 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):
        """Lida com deslocamento para a esquerda invertido: other << self."""
        if isinstance(other, int):
            return MutInt(other << self.value)
        else:
            return NotImplemented

Adicionamos três novos métodos à classe MutInt:

  1. __int__(): Este método é chamado quando você usa a função int() em um objeto da nossa classe MutInt. Por exemplo, se você tiver um objeto MutInt a e escrever int(a), o Python chamará o método __int__() do objeto a.
  2. __float__(): Semelhantemente, este método é chamado quando você usa a função float() em nosso objeto MutInt.
  3. __index__(): Este método é usado para operações que exigem especificamente um índice inteiro. Por exemplo, quando você deseja acessar um elemento em uma lista usando um índice ou realizar operações de bit, o Python precisa de um índice inteiro.
  4. __lshift__(): Este método lida com operações de deslocamento para a esquerda quando o objeto MutInt está no lado esquerdo do operador <<.
  5. __rlshift__(): Este método lida com operações de deslocamento para a esquerda quando o objeto MutInt está no lado direito do operador <<.

O método __index__ é crucial para operações que exigem um índice inteiro, como indexação de lista, fatiamento e operações de bit. Em nossa implementação simples, definimos que ele seja o mesmo que __int__ porque o valor do nosso objeto MutInt pode ser usado diretamente como um índice inteiro.

Os métodos __lshift__ e __rlshift__ são essenciais para suportar operações de deslocamento para a esquerda bit a bit. Eles permitem que nossos objetos MutInt participem de operações bit a bit, o que é um requisito comum para tipos semelhantes a inteiros.

  1. Crie um novo arquivo de teste chamado test_conversions.py para testar esses novos 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. Execute o script de teste:
python3 /home/labex/project/test_conversions.py

Você deve ver uma saída semelhante 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

Agora nossa classe MutInt pode ser convertida para tipos Python padrão e usada em operações que exigem um índice inteiro.

O método __index__ é particularmente importante. Ele foi introduzido no Python para permitir que objetos fossem usados em situações em que um índice inteiro é necessário, como indexação de lista, operações bit a bit e várias funções como hex(), oct() e bin().

Com essas adições, nossa classe MutInt agora é um tipo primitivo bastante completo. Ele pode ser usado na maioria dos contextos em que um inteiro regular seria usado, com a vantagem adicional de ser mutável.

Implementação Completa do MutInt

Aqui está nossa implementação completa do MutInt com todos os recursos que adicionamos:

## mutint.py

from functools import total_ordering

@total_ordering
class MutInt:
    """
    Uma classe de inteiro mutável que permite que seu valor seja modificado após a criação.
    """
    __slots__ = ['value']

    def __init__(self, value):
        """Inicializa com um valor inteiro."""
        self.value = value

    def __str__(self):
        """Retorna uma representação de string para impressão."""
        return str(self.value)

    def __repr__(self):
        """Retorna uma representação de string amigável para desenvolvedores."""
        return f'MutInt({self.value!r})'

    def __format__(self, fmt):
        """Suporta formatação de string com especificações de formato."""
        return format(self.value, fmt)

    def __add__(self, other):
        """Lida com a adição: 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):
        """Lida com a adição invertida: other + self."""
        return self.__add__(other)

    def __iadd__(self, other):
        """Lida com a adição in-place: 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):
        """Lida com a comparação de igualdade: 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):
        """Lida com a comparação menor que: 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):
        """Converte para int."""
        return self.value

    def __float__(self):
        """Converte para float."""
        return float(self.value)

    __index__ = __int__  ## Suporta indexação de array e outras operações que exigem um índice

    def __lshift__(self, other):
        """Lida com deslocamento para a esquerda: 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):
        """Lida com deslocamento para a esquerda invertido: other << self."""
        if isinstance(other, int):
            return MutInt(other << self.value)
        else:
            return NotImplemented

Esta implementação cobre os principais aspectos da criação de um novo tipo primitivo em Python. Para torná-lo ainda mais completo, você pode implementar métodos adicionais para outras operações como subtração, multiplicação, divisão, etc.

Resumo

Neste laboratório, você aprendeu como criar seu próprio tipo primitivo em Python. Especificamente, você dominou a criação de uma classe de inteiro mutável semelhante aos tipos embutidos, implementando métodos especiais para exibição de objetos, adicionando suporte para operações matemáticas e de comparação e habilitando conversões de tipo para vários contextos Python.

Esses conceitos são essenciais para entender o modelo de objetos do Python e podem ser usados para criar tipos personalizados que se integram bem com operações embutidas. Para aprofundar seus conhecimentos, considere implementar mais operações matemáticas, adicionar suporte para outras funções embutidas e explorar tipos complexos, como coleções personalizadas. Tipos personalizados em Python são uma ferramenta poderosa para estender a linguagem para necessidades específicas.