Créer un nouveau type primitif

PythonPythonBeginner
Pratiquer maintenant

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

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Dans ce laboratoire, vous apprendrez à créer un nouveau type primitif en Python et à implémenter les méthodes essentielles pour celui - ci. Vous comprendrez également le protocole d'objets de Python. Dans la plupart des programmes Python, des types primitifs intégrés tels que int, float et str sont utilisés pour représenter les données. Cependant, Python vous permet de créer des types personnalisés, comme on le voit dans des modules tels que decimal et fractions de la bibliothèque standard.

Dans ce laboratoire, vous allez créer un nouveau type primitif appelé MutInt (Entier mutable). Contrairement aux entiers immuables de Python, MutInt peut être modifié après sa création. Cet exercice mettra en évidence les principes fondamentaux nécessaires pour créer un type primitif entièrement fonctionnel en Python.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/BasicConceptsGroup(["Basic Concepts"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python/BasicConceptsGroup -.-> python/type_conversion("Type Conversion") python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/constructor("Constructor") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/type_conversion -.-> lab-132443{{"Créer un nouveau type primitif"}} python/conditional_statements -.-> lab-132443{{"Créer un nouveau type primitif"}} python/function_definition -.-> lab-132443{{"Créer un nouveau type primitif"}} python/classes_objects -.-> lab-132443{{"Créer un nouveau type primitif"}} python/constructor -.-> lab-132443{{"Créer un nouveau type primitif"}} python/decorators -.-> lab-132443{{"Créer un nouveau type primitif"}} end

Création d'une classe MutInt de base

Commençons par créer une classe de base pour notre type Entier mutable. En programmation, une classe est comme un modèle pour créer des objets. Dans cette étape, nous allons créer les bases de notre nouveau type primitif. Un type primitif est un type de données de base fourni par un langage de programmation, et ici nous allons en construire un personnalisé.

  1. Ouvrez le WebIDE et accédez au répertoire /home/labex/project. Le WebIDE est un environnement de développement intégré où vous pouvez écrire, éditer et exécuter votre code. Accéder à ce répertoire garantit que tous vos fichiers sont organisés en un seul endroit et peuvent interagir correctement les uns avec les autres.

  2. Ouvrez le fichier mutint.py qui a été créé pour vous lors de l'étape de configuration. Ce fichier sera le lieu de définition de notre classe MutInt.

  3. Ajoutez le code suivant pour définir une classe MutInt de base :

## 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

L'attribut __slots__ est utilisé pour définir les attributs que cette classe peut avoir. Les attributs sont comme des variables appartenant à un objet de la classe. En utilisant __slots__, nous indiquons à Python d'utiliser une méthode plus efficace en mémoire pour stocker les attributs. Dans ce cas, notre classe MutInt n'aura qu'un seul attribut appelé value. Cela signifie que chaque objet de la classe MutInt ne pourra contenir qu'une seule donnée, qui est la valeur entière.

La méthode __init__ est le constructeur de notre classe. Un constructeur est une méthode spéciale qui est appelée lorsqu'un objet de la classe est créé. Il prend un paramètre de valeur et le stocke dans l'attribut value de l'instance. Une instance est un objet individuel créé à partir du modèle de la classe.

Testons notre classe en créant un script Python pour l'utiliser :

  1. Créez un nouveau fichier appelé test_mutint.py dans le même répertoire :
## 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}")

Dans ce script de test, nous importons d'abord la classe MutInt à partir du fichier mutint.py. Ensuite, nous créons un objet de la classe MutInt avec une valeur initiale de 3. Nous affichons la valeur initiale, puis nous la modifions à 42 et affichons la nouvelle valeur. Enfin, nous essayons d'ajouter 10 à l'objet MutInt, ce qui entraînera une erreur car notre classe ne prend pas encore en charge l'opération d'addition.

  1. Exécutez le script de test en exécutant la commande suivante dans le terminal :
python3 /home/labex/project/test_mutint.py

Le terminal est une interface en ligne de commande où vous pouvez exécuter diverses commandes pour interagir avec votre système et votre code. Exécuter cette commande exécutera le script test_mutint.py.

Vous devriez voir une sortie similaire à ceci :

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

Notre classe MutInt stocke et met à jour une valeur avec succès. Cependant, elle présente plusieurs limitations :

  • Elle n'est pas affichée correctement lorsqu'elle est imprimée
  • Elle ne prend pas en charge les opérations mathématiques telles que l'addition
  • Elle ne prend pas en charge les comparaisons
  • Elle ne prend pas en charge les conversions de type

Dans les étapes suivantes, nous allons résoudre ces limitations une par une pour que notre classe MutInt se comporte plus comme un véritable type primitif.

✨ Vérifier la solution et pratiquer

Amélioration de la représentation sous forme de chaîne de caractères

Lorsque vous affichez un objet MutInt en Python, vous verrez une sortie du type <__main__.MutInt object at 0x...>. Cette sortie n'est pas très utile car elle ne vous indique pas la valeur réelle de l'objet MutInt. Pour faciliter la compréhension de ce que représente l'objet, nous allons implémenter des méthodes spéciales pour la représentation sous forme de chaîne de caractères.

  1. Ouvrez le fichier mutint.py dans le WebIDE et mettez - le à jour avec le code suivant :
## 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)

Nous avons ajouté trois méthodes importantes à la classe MutInt :

  • __str__() : Cette méthode est appelée lorsque vous utilisez la fonction str() sur l'objet ou lorsque vous affichez directement l'objet. Elle doit retourner une chaîne de caractères lisible par un humain.
  • __repr__() : Cette méthode fournit la représentation sous forme de chaîne de caractères "officielle" de l'objet. Elle est principalement utilisée pour le débogage et devrait idéalement retourner une chaîne de caractères qui, si elle est passée à la fonction eval(), recréerait l'objet.
  • __format__() : Cette méthode vous permet d'utiliser le système de formatage de chaînes de caractères de Python avec vos objets MutInt. Vous pouvez utiliser des spécifications de formatage telles que le remplissage et le formatage de nombres.
  1. Créez un nouveau fichier de test appelé test_string_repr.py pour tester ces nouvelles méthodes :
## 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)}")

Dans ce fichier de test, nous importons d'abord la classe MutInt. Ensuite, nous créons un objet MutInt avec la valeur 3. Nous testons les méthodes __str__() et __repr__() en utilisant les fonctions str() et repr(). Nous testons également l'affichage direct, le formatage de chaînes de caractères et la mutabilité de l'objet MutInt.

  1. Exécutez le script de test :
python3 /home/labex/project/test_string_repr.py

Lorsque vous exécutez cette commande, Python exécutera le script test_string_repr.py. Vous devriez voir une sortie similaire à ceci :

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)

Maintenant, nos objets MutInt s'affichent correctement. La représentation sous forme de chaîne de caractères montre la valeur sous - jacente, et nous pouvons utiliser le formatage de chaînes de caractères tout comme avec les entiers normaux.

La différence entre __str__() et __repr__() est que __str__() est destinée à produire une sortie conviviale pour l'humain, tandis que __repr__() devrait idéalement produire une chaîne de caractères qui, lorsqu'elle est passée à eval(), recréerait l'objet. C'est pourquoi nous avons inclus le nom de la classe dans la méthode __repr__().

La méthode __format__() permet à notre objet de fonctionner avec le système de formatage de Python, nous pouvons donc utiliser des spécifications de formatage telles que le remplissage et le formatage de nombres.

✨ Vérifier la solution et pratiquer

Ajout d'opérations mathématiques

Actuellement, notre classe MutInt ne prend pas en charge les opérations mathématiques telles que l'addition. En Python, pour activer de telles opérations pour une classe personnalisée, nous devons implémenter des méthodes spéciales. Ces méthodes spéciales sont également connues sous le nom de "méthodes magiques" ou "méthodes dunder" car elles sont entourées de doubles underscores. Ajoutons la fonctionnalité d'addition en implémentant les méthodes spéciales pertinentes pour les opérations arithmétiques.

  1. Ouvrez le fichier mutint.py dans le WebIDE et mettez - le à jour avec le code suivant :
## 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

Nous avons ajouté trois nouvelles méthodes à la classe MutInt :

  • __add__() : Cette méthode est appelée lorsque l'opérateur + est utilisé avec notre objet MutInt du côté gauche. À l'intérieur de cette méthode, nous vérifions d'abord si l'opérande other est une instance de MutInt ou un int. Si c'est le cas, nous effectuons l'addition et retournons un nouvel objet MutInt avec le résultat. Si l'opérande other est autre chose, nous retournons NotImplemented. Cela indique à Python d'essayer d'autres méthodes ou de lever une TypeError.
  • __radd__() : Cette méthode est appelée lorsque l'opérateur + est utilisé avec notre objet MutInt du côté droit. Étant donné que l'addition est une opération commutative (c'est - à - dire que a + b est le même que b + a), nous pouvons simplement réutiliser la méthode __add__.
  • __iadd__() : Cette méthode est appelée lorsque l'opérateur += est utilisé sur notre objet MutInt. Au lieu de créer un nouvel objet, elle modifie l'objet MutInt existant et le retourne.
  1. Créez un nouveau fichier de test appelé test_math_ops.py pour tester ces nouvelles méthodes :
## 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}")

Dans ce fichier de test, nous importons d'abord la classe MutInt. Ensuite, nous créons quelques objets MutInt et effectuons différents types d'opérations d'addition. Nous testons également l'addition en place et le cas où une opération non prise en charge (l'addition d'un nombre à virgule flottante) est tentée.

  1. Exécutez le script de test :
python3 /home/labex/project/test_math_ops.py

Vous devriez voir une sortie similaire à ceci :

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'

Maintenant, notre classe MutInt prend en charge les opérations d'addition de base. Remarquez que lorsque nous avons utilisé +=, tant a que f ont été mis à jour. Cela montre que a += 10 a modifié l'objet existant plutôt que de créer un nouveau.

Ce comportement avec les objets mutables est similaire aux types mutables intégrés de Python comme les listes. Par exemple :

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

En revanche, pour les types immuables comme les tuples, += crée un nouvel objet :

c = (1, 2, 3)
d = c
c += (4, 5)  ## c is a new object, d still points to the old one
✨ Vérifier la solution et pratiquer

Implémentation d'opérations de comparaison

Actuellement, nos objets MutInt ne peuvent pas être comparés entre eux ou avec des entiers normaux. En Python, les opérations de comparaison telles que ==, <, <=, >, >= sont très utiles lorsqu'on travaille avec des objets. Elles nous permettent de déterminer les relations entre différents objets, ce qui est crucial dans de nombreux scénarios de programmation tels que le tri, le filtrage et les instructions conditionnelles. Ajoutons donc la fonctionnalité de comparaison à notre classe MutInt en implémentant les méthodes spéciales pour les opérations de comparaison.

  1. Ouvrez le fichier mutint.py dans le WebIDE et mettez - le à jour avec le code suivant :
## 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

Nous avons apporté plusieurs améliorations clés :

  1. Importez et utilisez le décorateur @total_ordering du module functools. Le décorateur @total_ordering est un outil puissant en Python. Il nous permet d'économiser beaucoup de temps et d'efforts lors de l'implémentation des méthodes de comparaison pour une classe. Au lieu de définir manuellement les six méthodes de comparaison (__eq__, __ne__, __lt__, __le__, __gt__, __ge__), nous n'avons besoin que de définir __eq__ et une autre méthode de comparaison (dans notre cas, __lt__). Le décorateur générera ensuite automatiquement les quatre autres méthodes de comparaison pour nous.

  2. Ajoutez la méthode __eq__() pour gérer les comparaisons d'égalité (==). Cette méthode est utilisée pour vérifier si deux objets MutInt ou un objet MutInt et un entier ont la même valeur.

  3. Ajoutez la méthode __lt__() pour gérer les comparaisons de "inférieur à" (<). Cette méthode est utilisée pour déterminer si un objet MutInt ou un objet MutInt comparé à un entier a une valeur plus petite.

  4. Créez un nouveau fichier de test appelé test_comparisons.py pour tester ces nouvelles méthodes :

## 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)

Dans ce fichier de test, nous créons plusieurs objets MutInt et effectuons différentes opérations de comparaison sur eux. Nous comparons également des objets MutInt avec des entiers normaux et un type différent (une chaîne de caractères dans ce cas). En exécutant ces tests, nous pouvons vérifier que nos méthodes de comparaison fonctionnent comme prévu.

  1. Exécutez le script de test :
python3 /home/labex/project/test_comparisons.py

Vous devriez voir une sortie similaire à ceci :

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

Maintenant, notre classe MutInt prend en charge toutes les opérations de comparaison.

Le décorateur @total_ordering est particulièrement utile car il nous évite d'avoir à implémenter manuellement les six méthodes de comparaison. En fournissant simplement __eq__ et __lt__, Python peut dériver automatiquement les quatre autres méthodes de comparaison.

Lors de l'implémentation de classes personnalisées, il est généralement une bonne pratique de les faire fonctionner à la fois avec des objets du même type et avec des types intégrés lorsque cela a du sens. C'est pourquoi nos méthodes de comparaison gèrent à la fois les objets MutInt et les entiers normaux. De cette façon, notre classe MutInt peut être utilisée plus flexiblement dans différents scénarios de programmation.

✨ Vérifier la solution et pratiquer

Ajout de conversions de type

Notre classe MutInt prend actuellement en charge les opérations d'addition et de comparaison. Cependant, elle ne fonctionne pas avec les fonctions de conversion intégrées de Python telles que int() et float(). Ces fonctions de conversion sont très utiles en Python. Par exemple, lorsque vous souhaitez convertir une valeur en entier ou en nombre à virgule flottante pour différents calculs ou opérations, vous vous appuyez sur ces fonctions. Ajoutons donc à notre classe MutInt les capacités de fonctionner avec elles.

  1. Ouvrez le fichier mutint.py dans le WebIDE et mettez - le à jour avec le code suivant :
## 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

Nous avons ajouté trois nouvelles méthodes à la classe MutInt :

  1. __int__() : Cette méthode est appelée lorsque vous utilisez la fonction int() sur un objet de notre classe MutInt. Par exemple, si vous avez un objet MutInt nommé a et que vous écrivez int(a), Python appellera la méthode __int__() de l'objet a.
  2. __float__() : De même, cette méthode est appelée lorsque vous utilisez la fonction float() sur notre objet MutInt.
  3. __index__() : Cette méthode est utilisée pour les opérations qui nécessitent spécifiquement un index entier. Par exemple, lorsque vous souhaitez accéder à un élément dans une liste à l'aide d'un index, ou effectuer des opérations de longueur binaire, Python a besoin d'un index entier.

La méthode __index__ est cruciale pour les opérations qui nécessitent un index entier, comme l'indexation de listes, le découpage (slicing) et les opérations de longueur binaire. Dans notre implémentation simple, nous l'avons définie comme étant la même que __int__ car la valeur de notre objet MutInt peut être directement utilisée comme index entier.

  1. Créez un nouveau fichier de test appelé test_conversions.py pour tester ces nouvelles méthodes :
## 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 = 5
print(f"\nAfter changing value to 5:")
print(f"int(a): {int(a)}")
print(f"names[a]: {names[a]}")
  1. Exécutez le script de test :
python3 /home/labex/project/test_conversions.py

Vous devriez voir une sortie similaire à ceci :

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

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

Maintenant, notre classe MutInt peut être convertie en types standard Python et utilisée dans des opérations qui nécessitent un index entier.

La méthode __index__ est particulièrement importante. Elle a été introduite en Python pour permettre aux objets d'être utilisés dans des situations où un index entier est requis, comme l'indexation de listes, les opérations bit - à - bit et diverses fonctions telles que hex(), oct() et bin().

Avec ces ajouts, notre classe MutInt est maintenant un type primitif assez complet. Elle peut être utilisée dans la plupart des contextes où un entier normal serait utilisé, avec l'avantage supplémentaire d'être mutable.

Implémentation complète de MutInt

Voici notre implémentation complète de MutInt avec toutes les fonctionnalités que nous avons ajoutées :

## 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

Cette implémentation couvre les aspects clés de la création d'un nouveau type primitif en Python. Pour la rendre encore plus complète, vous pourriez implémenter des méthodes supplémentaires pour d'autres opérations telles que la soustraction, la multiplication, la division, etc.

✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire, vous avez appris à créer votre propre type primitif en Python. Plus précisément, vous avez maîtrisé la création d'une classe d'entiers mutables similaire aux types intégrés, l'implémentation de méthodes spéciales pour l'affichage des objets, l'ajout de la prise en charge des opérations mathématiques et de comparaison, ainsi que la conversion de types pour différents contextes Python.

Ces concepts sont essentiels pour comprendre le modèle d'objets de Python et peuvent être utilisés pour créer des types personnalisés qui s'intègrent bien aux opérations intégrées. Pour approfondir vos connaissances, envisagez d'implémenter plus d'opérations mathématiques, d'ajouter la prise en charge d'autres fonctions intégrées et d'explorer des types complexes tels que les collections personnalisées. Les types personnalisés en Python sont un outil puissant pour étendre le langage selon des besoins spécifiques.