Création de classes de bas niveau

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 allez apprendre les étapes de bas niveau impliquées dans la création d'une classe en Python. Comprendre comment les classes sont construites à l'aide de la fonction type() offre une meilleure compréhension des fonctionnalités orientées objet de Python.

Vous allez également implémenter des techniques de création de classes personnalisées. Les fichiers validate.py et structure.py seront modifiés au cours de ce laboratoire, vous permettant d'appliquer vos nouvelles connaissances dans un contexte pratique.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) 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") subgraph Lab Skills python/conditional_statements -.-> lab-132517{{"Création de classes de bas niveau"}} python/function_definition -.-> lab-132517{{"Création de classes de bas niveau"}} python/classes_objects -.-> lab-132517{{"Création de classes de bas niveau"}} python/constructor -.-> lab-132517{{"Création de classes de bas niveau"}} end

Création manuelle de classe

En programmation Python, les classes sont un concept fondamental qui vous permet de regrouper des données et des fonctions. Habituellement, nous définissons des classes en utilisant la syntaxe standard de Python. Par exemple, voici une simple classe Stock. Cette classe représente une action avec des attributs tels que name, shares et price, et elle a des méthodes pour calculer le coût et vendre des actions.

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, nshares):
        self.shares -= nshares

Mais avez-vous déjà vous demandé comment Python crée réellement une classe en coulisse ? Et si nous voulions créer cette classe sans utiliser la syntaxe standard des classes ? Dans cette section, nous allons explorer comment les classes Python sont construites au niveau inférieur.

Lancer le shell interactif Python

Pour commencer à expérimenter la création manuelle de classes, nous devons ouvrir un shell interactif Python. Ce shell nous permet d'exécuter le code Python ligne par ligne, ce qui est idéal pour apprendre et tester.

Ouvrez un terminal dans WebIDE et lancez le shell interactif Python en tapant les commandes suivantes. La première commande cd ~/project change le répertoire courant pour le répertoire du projet, et la deuxième commande python3 lance le shell interactif Python 3.

cd ~/project
python3

Définition des méthodes comme des fonctions ordinaires

Avant de créer une classe manuellement, nous devons définir les méthodes qui feront partie de la classe. En Python, les méthodes ne sont que des fonctions associées à une classe. Donc, définissons les méthodes que nous voulons dans notre classe comme des fonctions Python ordinaires.

def __init__(self, name, shares, price):
    self.name = name
    self.shares = shares
    self.price = price

def cost(self):
    return self.shares * self.price

def sell(self, nshares):
    self.shares -= nshares

Ici, la fonction __init__ est une méthode spéciale dans les classes Python. C'est ce qu'on appelle un constructeur, et il est utilisé pour initialiser les attributs de l'objet lorsqu'une instance de la classe est créée. La méthode cost calcule le coût total des actions, et la méthode sell réduit le nombre d'actions.

Création d'un dictionnaire de méthodes

Maintenant que nous avons défini nos méthodes comme des fonctions ordinaires, nous devons les organiser d'une manière que Python puisse comprendre lors de la création de la classe. Nous le faisons en créant un dictionnaire qui contiendra toutes les méthodes de notre classe.

methods = {
    '__init__': __init__,
    'cost': cost,
    'sell': sell
}

Dans ce dictionnaire, les clés sont les noms des méthodes telles qu'elles seront utilisées dans la classe, et les valeurs sont les objets de fonction que nous avons définis précédemment.

Utilisation du constructeur type() pour créer une classe

En Python, la fonction type() est une fonction intégrée qui peut être utilisée pour créer des classes au niveau inférieur. La fonction type() prend trois arguments :

  1. Le nom de la classe : C'est une chaîne de caractères qui représente le nom de la classe que nous voulons créer.
  2. Un tuple de classes de base : En Python, les classes peuvent hériter d'autres classes. Ici, nous utilisons (object,) ce qui signifie que notre classe hérite de la classe de base object, qui est la classe de base de toutes les classes en Python.
  3. Un dictionnaire contenant les méthodes et les attributs : C'est le dictionnaire que nous avons créé précédemment qui contient toutes les méthodes de notre classe.
Stock = type('Stock', (object,), methods)

Cette ligne de code crée une nouvelle classe nommée Stock en utilisant la fonction type(). La classe hérite de la classe object et a les méthodes définies dans le dictionnaire methods.

Test de notre classe créée manuellement

Maintenant que nous avons créé notre classe manuellement, testons-la pour nous assurer qu'elle fonctionne comme prévu. Nous allons créer une instance de notre nouvelle classe et appeler ses méthodes.

s = Stock('GOOG', 100, 490.10)
print(s.name)
print(s.cost())
s.sell(25)
print(s.shares)

Dans la première ligne, nous créons une instance de la classe Stock avec le nom GOOG, 100 actions et un prix de 490,10. Ensuite, nous affichons le nom de l'action, calculons et affichons le coût, vendons 25 actions et, enfin, affichons le nombre restant d'actions.

Vous devriez voir la sortie suivante :

GOOG
49010.0
75

Cette sortie montre que notre classe créée manuellement fonctionne tout comme une classe créée en utilisant la syntaxe standard de Python. Cela démontre qu'une classe est fondamentalement juste un nom, un tuple de classes de base et un dictionnaire de méthodes et d'attributs. La fonction type() construit simplement un objet de classe à partir de ces composants.

Quittez le shell Python lorsque vous avez terminé :

exit()

Création d'un outil pour des structures typées

Dans cette étape, nous allons construire un exemple plus pratique. Nous allons implémenter une fonction qui crée des classes avec validation de type. La validation de type est cruciale car elle garantit que les données assignées aux attributs de classe répondent à des critères spécifiques, comme être d'un certain type de données ou se trouver dans une plage particulière. Cela permet de détecter les erreurs tôt et rend notre code plus robuste.

Compréhension de la classe Structure

Tout d'abord, nous devons ouvrir le fichier structure.py dans l'éditeur WebIDE. Ce fichier contient une classe Structure de base. Cette classe fournit la fonctionnalité fondamentale pour l'initialisation et la représentation d'objets structurés. L'initialisation consiste à configurer l'objet avec les données fournies, et la représentation concerne la façon dont l'objet est affiché lorsqu'on l'imprime.

Pour ouvrir le fichier, nous allons utiliser la commande suivante dans le terminal :

cd ~/project

Après avoir exécuté cette commande, vous vous trouverez dans le bon répertoire où se trouve le fichier structure.py. Lorsque vous ouvrez le fichier, vous remarquerez la classe Structure de base. Notre objectif est d'étendre cette classe pour prendre en charge la validation de type.

Implémentation de la fonction typed_structure

Maintenant, ajoutons la fonction typed_structure au fichier structure.py. Cette fonction créera une nouvelle classe qui hérite de la classe Structure et inclut les validateurs spécifiés. L'héritage signifie que la nouvelle classe aura toute la fonctionnalité de la classe Structure et peut également ajouter ses propres fonctionnalités. Les validateurs sont utilisés pour vérifier si les valeurs assignées aux attributs de classe sont valides.

Voici le code de la fonction typed_structure :

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

Le paramètre clsname est le nom que nous voulons donner à la nouvelle classe. Le paramètre validators est un dictionnaire où les clés sont les noms des attributs et les valeurs sont les objets validateurs. La fonction type() est utilisée pour créer dynamiquement une nouvelle classe. Elle prend trois arguments : le nom de la classe, un tuple de classes de base (dans ce cas, seulement la classe Structure), et un dictionnaire d'attributs de classe (les validateurs).

Après avoir ajouté cette fonction, votre fichier structure.py devrait ressembler à ceci :

## Structure class definition

class Structure:
    _fields = ()

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        ## Set all of the positional arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        ## Set the remaining keyword arguments
        for name, value in kwargs.items():
            setattr(self, name, value)

    def __repr__(self):
        attrs = ', '.join(f'{name}={getattr(self, name)!r}' for name in self._fields)
        return f'{type(self).__name__}({attrs})'

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

Test de la fonction typed_structure

Testons notre fonction typed_structure en utilisant les validateurs du fichier validate.py. Ces validateurs sont utilisés pour vérifier si les valeurs assignées aux attributs de classe sont du bon type et répondent à d'autres critères.

Tout d'abord, ouvrez un shell interactif Python. Nous allons utiliser les commandes suivantes dans le terminal :

cd ~/project
python3

La première commande nous amène dans le bon répertoire, et la deuxième commande lance le shell interactif Python.

Maintenant, importez les composants nécessaires et créez une structure typée :

from validate import String, PositiveInteger, PositiveFloat
from structure import typed_structure

## Create a Stock class with type validation
Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat())

## Create a stock instance
s = Stock('GOOG', 100, 490.1)

## Test the instance
print(s.name)
print(s)

## Test validation
try:
    invalid_stock = Stock('AAPL', -10, 150.25)  ## Should raise an error
except ValueError as e:
    print(f"Validation error: {e}")

Nous importons les validateurs String, PositiveInteger et PositiveFloat du fichier validate.py. Ensuite, nous utilisons la fonction typed_structure pour créer une classe Stock avec validation de type. Nous créons une instance de la classe Stock et la testons en imprimant ses attributs. Enfin, nous essayons de créer une instance de stock invalide pour tester la validation.

Vous devriez voir une sortie similaire à :

GOOG
Stock('GOOG', 100, 490.1)
Validation error: Expected a positive value

Lorsque vous avez terminé de tester, quittez le shell Python :

exit()

Cet exemple montre comment nous pouvons utiliser la fonction type() pour créer des classes personnalisées avec des règles de validation spécifiques. Cette approche est très puissante car elle nous permet de générer des classes de manière programmée, ce qui peut économiser beaucoup de temps et rendre notre code plus flexible.

✨ Vérifier la solution et pratiquer

Génération efficace de classes

Maintenant que vous savez comment créer des classes en utilisant la fonction type(), nous allons explorer une méthode plus efficace pour générer plusieurs classes similaires. Cette méthode vous fera gagner du temps et réduira la duplication de code, rendant votre processus de programmation plus fluide.

Compréhension des classes de validateurs actuelles

Tout d'abord, nous devons ouvrir le fichier validate.py dans WebIDE. Ce fichier contient déjà plusieurs classes de validateurs, qui sont utilisées pour vérifier si les valeurs répondent à certaines conditions. Ces classes incluent Validator, Positive, PositiveInteger et PositiveFloat. Nous allons ajouter une classe de base Typed et plusieurs validateurs spécifiques à un type à ce fichier.

Pour ouvrir le fichier, exécutez la commande suivante dans le terminal :

cd ~/project

Ajout de la classe de validateur Typed

Commençons par ajouter la classe de validateur Typed. Cette classe sera utilisée pour vérifier si une valeur est du type attendu.

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

Dans ce code, expected_type est défini sur object par défaut. Les sous - classes l'écraseront avec le type spécifique qu'elles vérifient. La méthode check utilise la fonction isinstance pour vérifier si la valeur est du type attendu. Sinon, elle lève une erreur TypeError.

Traditionnellement, nous créerions des validateurs spécifiques à un type comme ceci :

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

Cependant, cette approche est répétitive. Nous pouvons faire mieux en utilisant le constructeur type() pour générer ces classes dynamiquement.

Génération dynamique de validateurs de type

Nous allons remplacer les définitions de classes individuelles par une approche plus efficace.

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

Voici ce que ce code fait :

  1. Il définit une liste de tuples. Chaque tuple contient un nom de classe et le type Python correspondant.
  2. Il utilise une expression génératrice avec la fonction type() pour créer chaque classe. La fonction type() prend trois arguments : le nom de la classe, un tuple de classes de base et un dictionnaire d'attributs de classe.
  3. Il utilise globals().update() pour ajouter les classes nouvellement créées à l'espace de noms global. Cela rend les classes accessibles dans tout le module.

Votre fichier validate.py terminé devrait ressembler à ceci :

## Basic validator classes

class Validator:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        self.check(value)
        instance.__dict__[self.name] = value

    @classmethod
    def check(cls, value):
        pass

class Positive(Validator):
    @classmethod
    def check(cls, value):
        if value <= 0:
            raise ValueError('Expected a positive value')
        super().check(value)

class PositiveInteger(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, int):
            raise TypeError('Expected an integer')
        super().check(value)

class PositiveFloat(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, float):
            raise TypeError('Expected a float')
        super().check(value)

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

## Generate type validators dynamically
_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

Test des classes générées dynamiquement

Maintenant, testons nos classes de validateurs générées dynamiquement. Tout d'abord, ouvrez un shell interactif Python.

cd ~/project
python3

Une fois que vous êtes dans le shell Python, importez et testez nos validateurs.

from validate import Integer, Float, String

## Test the Integer validator
i = Integer()
i.__set_name__(None, 'test_int')
try:
    i.check("not an integer")
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"Integer validation: {e}")

## Test the String validator
s = String()
s.__set_name__(None, 'test_str')
try:
    s.check(123)
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"String validation: {e}")

## Add a new validator class to the list
import sys
print("Current validator classes:", [cls for cls in dir() if cls in ['Integer', 'Float', 'String']])

Vous devriez voir une sortie montrant les erreurs de validation de type. Cela indique que nos classes générées dynamiquement fonctionnent correctement.

Lorsque vous avez terminé de tester, quittez le shell Python :

exit()

Extension de la génération dynamique de classes

Si vous souhaitez ajouter plus de validateurs de type, vous pouvez simplement mettre à jour la liste _typed_classes dans validate.py.

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str),
    ('List', list),
    ('Dict', dict),
    ('Bool', bool)
]

Cette approche offre un moyen puissant et efficace de générer plusieurs classes similaires sans écrire de code répétitif. Elle vous permet de mettre à l'échelle facilement votre application à mesure que vos besoins augmentent.

✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire, vous avez appris les mécanismes de bas niveau de création de classes en Python. Tout d'abord, vous avez maîtrisé la création manuelle d'une classe en utilisant le constructeur type(), qui nécessite un nom de classe, un tuple de classes de base et un dictionnaire de méthodes. Deuxièmement, vous avez implémenté une fonction typed_structure pour créer dynamiquement des classes avec des capacités de validation.

De plus, vous avez utilisé le constructeur type() avec globals().update() pour générer efficacement plusieurs classes similaires, évitant ainsi le code répétitif. Ces techniques offrent des moyens puissants de créer et de personnaliser des classes de manière programmée, utiles dans les frameworks, les bibliothèques et la métaprogrammation. Comprendre ces mécanismes sous - jacents approfondit votre compréhension des fonctionnalités orientées objet de Python et permet une programmation plus avancée.