Comportement de l'héritage

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 le comportement de l'héritage en Python. Plus précisément, vous vous concentrerez sur le fonctionnement de la fonction super() et sur la mise en œuvre de l'héritage coopératif. L'héritage est un concept fondamental en programmation orientée objet, permettant aux classes d'hériter d'attributs et de méthodes des classes parentes pour une réutilisation du code et la création de structures de classes hiérarchiques.

Dans cette expérience pratique, vous comprendrez les différents types d'héritage en Python, y compris l'héritage simple et multiple. Vous apprendrez également à utiliser la fonction super() pour naviguer dans les hiérarchies d'héritage, à implémenter un exemple pratique d'héritage multiple coopératif et à appliquer ces concepts pour construire un système de validation. Le fichier principal créé lors de ce laboratoire est validate.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("Encapsulation") subgraph Lab Skills python/conditional_statements -.-> lab-132500{{"Comportement de l'héritage"}} python/classes_objects -.-> lab-132500{{"Comportement de l'héritage"}} python/inheritance -.-> lab-132500{{"Comportement de l'héritage"}} python/encapsulation -.-> lab-132500{{"Comportement de l'héritage"}} end

Comprendre l'héritage simple et multiple

Dans cette étape, nous allons apprendre les deux principaux types d'héritage en Python : l'héritage simple et l'héritage multiple. L'héritage est un concept fondamental en programmation orientée objet qui permet à une classe d'hériter d'attributs et de méthodes d'autres classes. Nous allons également examiner comment Python détermine quelle méthode appeler lorsqu'il y a plusieurs candidats, un processus connu sous le nom de résolution de méthode.

Héritage simple

L'héritage simple se produit lorsque les classes forment une seule lignée d'ascendance. Imaginez - le comme un arbre généalogique où chaque classe n'a qu'un seul parent direct. Créons un exemple pour comprendre son fonctionnement.

Tout d'abord, ouvrez un nouveau terminal dans le WebIDE. Une fois le terminal ouvert, lancez l'interpréteur Python en tapant la commande suivante puis en appuyant sur Entrée :

python3

Maintenant que vous êtes dans l'interpréteur Python, nous allons créer trois classes qui forment une chaîne d'héritage simple. Entrez le code suivant :

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()

class C(B):
    def spam(self):
        print('C.spam')
        super().spam()

Dans ce code, la classe B hérite de la classe A, et la classe C hérite de la classe B. La fonction super() est utilisée pour appeler la méthode de la classe parente.

Après avoir défini ces classes, nous pouvons découvrir l'ordre dans lequel Python recherche les méthodes. Cet ordre est appelé l'ordre de résolution de méthode (Method Resolution Order - MRO). Pour voir le MRO de la classe C, tapez le code suivant :

C.__mro__

Vous devriez voir une sortie similaire à celle - ci :

(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

Cette sortie montre que Python cherche d'abord une méthode dans la classe C, puis dans la classe B, ensuite dans la classe A, et enfin dans la classe de base object.

Maintenant, créons une instance de la classe C et appelons sa méthode spam(). Tapez le code suivant :

c = C()
c.spam()

Vous devriez voir la sortie suivante :

C.spam
B.spam
A.spam

Cette sortie démontre le fonctionnement de super() dans une chaîne d'héritage simple. Lorsque C.spam() appelle super().spam(), il appelle B.spam(). Ensuite, lorsque B.spam() appelle super().spam(), il appelle A.spam().

Héritage multiple

L'héritage multiple permet à une classe d'hériter de plusieurs classes parentes. Cela donne à une classe accès aux attributs et méthodes de toutes ses classes parentes. Voyons comment la résolution de méthode fonctionne dans ce cas.

Entrez le code suivant dans votre interpréteur Python :

class Base:
    def spam(self):
        print('Base.spam')

class X(Base):
    def spam(self):
        print('X.spam')
        super().spam()

class Y(Base):
    def spam(self):
        print('Y.spam')
        super().spam()

class Z(Base):
    def spam(self):
        print('Z.spam')
        super().spam()

Maintenant, nous allons créer une classe M qui hérite de plusieurs classes parentes X, Y et Z. Entrez le code suivant :

class M(X, Y, Z):
    pass

M.__mro__

Vous devriez voir la sortie suivante :

(<class '__main__.M'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <class '__main__.Base'>, <class 'object'>)

Cette sortie montre l'ordre de résolution de méthode pour la classe M. Python recherchera les méthodes dans cet ordre.

Créons une instance de la classe M et appelons sa méthode spam() :

m = M()
m.spam()

Vous devriez voir la sortie suivante :

X.spam
Y.spam
Z.spam
Base.spam

Notez que super() ne fait pas simplement appel à la méthode de la classe parente immédiate. Au lieu de cela, il suit l'ordre de résolution de méthode (MRO) défini par la classe enfant.

Créons une autre classe N avec les classes parentes dans un ordre différent :

class N(Z, Y, X):
    pass

N.__mro__

Vous devriez voir la sortie suivante :

(<class '__main__.N'>, <class '__main__.Z'>, <class '__main__.Y'>, <class '__main__.X'>, <class '__main__.Base'>, <class 'object'>)

Maintenant, créez une instance de la classe N et appelez sa méthode spam() :

n = N()
n.spam()

Vous devriez voir la sortie suivante :

Z.spam
Y.spam
X.spam
Base.spam

Cela montre un concept important : dans l'héritage multiple de Python, l'ordre des classes parentes dans la définition de la classe détermine l'ordre de résolution de méthode. La fonction super() suit cet ordre, peu importe depuis quelle classe elle est appelée.

Lorsque vous avez terminé d'explorer ces concepts, vous pouvez quitter l'interpréteur Python en tapant le code suivant :

exit()

Construction d'un système de validation avec l'héritage

Dans cette étape, nous allons construire un système de validation pratique en utilisant l'héritage. L'héritage est un concept puissant en programmation qui vous permet de créer de nouvelles classes basées sur des classes existantes. De cette manière, vous pouvez réutiliser le code et créer des programmes plus organisés et modulaires. En construisant ce système de validation, vous verrez comment l'héritage peut être utilisé pour créer des composants de code réutilisables qui peuvent être combinés de différentes manières.

Création de la classe de base des validateurs

Tout d'abord, nous devons créer une classe de base pour nos validateurs. Pour ce faire, nous allons créer un nouveau fichier dans le WebIDE. Voici comment vous pouvez le faire : cliquez sur "File" > "New File", ou vous pouvez utiliser le raccourci clavier. Une fois le nouveau fichier ouvert, nommez - le validate.py.

Maintenant, ajoutons du code à ce fichier pour créer une classe de base Validator. Cette classe servira de fondation pour tous nos autres validateurs.

## validate.py
class Validator:
    @classmethod
    def check(cls, value):
        return value

Dans ce code, nous avons défini une classe Validator avec une méthode check. La méthode check prend une valeur en argument et la renvoie simplement inchangée. Le décorateur @classmethod est utilisé pour transformer cette méthode en une méthode de classe. Cela signifie que nous pouvons appeler cette méthode sur la classe elle - même, sans avoir à créer une instance de la classe.

Ajout de validateurs de type

Ensuite, nous allons ajouter des validateurs qui vérifient le type d'une valeur. Ces validateurs hériteront de la classe Validator que nous venons de créer. Revenez au fichier validate.py et ajoutez le code suivant :

class Typed(Validator):
    expected_type = object
    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        return super().check(value)

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

La classe Typed est une sous - classe de Validator. Elle a un attribut expected_type, qui est initialement défini sur object. La méthode check dans la classe Typed vérifie si la valeur donnée est du type attendu. Si ce n'est pas le cas, elle lève une TypeError. Si le type est correct, elle appelle la méthode check de la classe parente en utilisant super().check(value).

Les classes Integer, Float et String héritent de Typed et spécifient le type exact qu'elles sont censées vérifier. Par exemple, la classe Integer vérifie si une valeur est un entier.

Test des validateurs de type

Maintenant que nous avons créé nos validateurs de type, testons - les. Ouvrez un nouveau terminal et lancez l'interpréteur Python en exécutant la commande suivante :

python3

Une fois que l'interpréteur Python est en cours d'exécution, nous pouvons importer et tester nos validateurs. Voici du code pour les tester :

from validate import Integer, String

Integer.check(10)  ## Should return 10

try:
    Integer.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

String.check('10')  ## Should return '10'

Lorsque vous exécutez ce code, vous devriez voir quelque chose comme ceci :

10
Error: Expected <class 'int'>
'10'

Nous pouvons également utiliser ces validateurs dans une fonction. Essayons cela :

def add(x, y):
    Integer.check(x)
    Integer.check(y)
    return x + y

add(2, 2)  ## Should return 4

try:
    add('2', '3')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

Lorsque vous exécutez ce code, vous devriez voir :

4
Error: Expected <class 'int'>

Ajout de validateurs de valeur

Jusqu'à présent, nous avons créé des validateurs qui vérifient le type d'une valeur. Maintenant, ajoutons des validateurs qui vérifient la valeur elle - même plutôt que le type. Revenez au fichier validate.py et ajoutez le code suivant :

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

class NonEmpty(Validator):
    @classmethod
    def check(cls, value):
        if len(value) == 0:
            raise ValueError('Must be non-empty')
        return super().check(value)

Le validateur Positive vérifie si une valeur est non négative. Si la valeur est inférieure à 0, il lève une ValueError. Le validateur NonEmpty vérifie si une valeur a une longueur non nulle. Si la longueur est 0, il lève une ValueError.

Composition de validateurs avec l'héritage multiple

Maintenant, nous allons combiner nos validateurs en utilisant l'héritage multiple. L'héritage multiple permet à une classe d'hériter de plusieurs classes parentes. Revenez au fichier validate.py et ajoutez le code suivant :

class PositiveInteger(Integer, Positive):
    pass

class PositiveFloat(Float, Positive):
    pass

class NonEmptyString(String, NonEmpty):
    pass

Ces nouvelles classes combinent la vérification de type et la vérification de valeur. Par exemple, la classe PositiveInteger vérifie qu'une valeur est à la fois un entier et non négative. L'ordre d'héritage est important ici. Les validateurs sont vérifiés dans l'ordre spécifié dans la définition de la classe.

Test des validateurs composés

Testons nos validateurs composés. Dans l'interpréteur Python, exécutez le code suivant :

from validate import PositiveInteger, PositiveFloat, NonEmptyString

PositiveInteger.check(10)  ## Should return 10

try:
    PositiveInteger.check('10')  ## Should raise TypeError
except TypeError as e:
    print(f"Error: {e}")

try:
    PositiveInteger.check(-10)  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

NonEmptyString.check('hello')  ## Should return 'hello'

try:
    NonEmptyString.check('')  ## Should raise ValueError
except ValueError as e:
    print(f"Error: {e}")

Lorsque vous exécutez ce code, vous devriez voir :

10
Error: Expected <class 'int'>
Error: Expected >= 0
'hello'
Error: Must be non-empty

Cela montre comment nous pouvons combiner des validateurs pour créer des règles de validation plus complexes.

Lorsque vous avez terminé de tester, vous pouvez quitter l'interpréteur Python en exécutant la commande suivante :

exit()
✨ Vérifier la solution et pratiquer

Application des validateurs à une classe Stock

Dans cette étape, nous allons voir comment nos validateurs fonctionnent dans une situation réelle. Les validateurs sont comme de petits vérificateurs qui s'assurent que les données que nous utilisons respectent certaines règles. Nous allons créer une classe Stock. Une classe est comme un modèle pour créer des objets. Dans ce cas, la classe Stock représentera une action boursière, et nous utiliserons nos validateurs pour nous assurer que les valeurs de ses attributs (comme le nombre de parts et le prix) sont valides.

Création de la classe Stock

Tout d'abord, nous devons créer un nouveau fichier. Dans le WebIDE, créez un nouveau fichier appelé stock.py. Ce fichier contiendra le code de notre classe Stock. Maintenant, ajoutez le code suivant au fichier stock.py :

## stock.py
from validate import PositiveInteger, PositiveFloat

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

    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        self._shares = PositiveInteger.check(value)

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        self._price = PositiveFloat.check(value)

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

Analysons ce que fait ce code :

  1. Nous commençons par importer les validateurs PositiveInteger et PositiveFloat depuis notre module validate. Ces validateurs nous aideront à nous assurer que le nombre de parts est un entier positif et que le prix est un nombre à virgule flottante positif.
  2. Ensuite, nous définissons une classe Stock. À l'intérieur de la classe, nous avons une méthode __init__. Cette méthode est appelée lorsque nous créons un nouvel objet Stock. Elle prend trois paramètres : name, shares et price, et les assigne aux attributs de l'objet.
  3. Nous utilisons des propriétés (properties) et des mutateurs (setters) pour valider les valeurs de shares et price. Une propriété est un moyen de contrôler l'accès à un attribut, et un mutateur est une méthode qui est appelée lorsque nous essayons de définir la valeur de cet attribut. Lorsque nous définissons l'attribut shares, la méthode PositiveInteger.check est appelée pour nous assurer que la valeur est un entier positif. De même, lorsque nous définissons l'attribut price, la méthode PositiveFloat.check est appelée pour nous assurer que la valeur est un nombre à virgule flottante positif.
  4. Enfin, nous avons une méthode cost. Cette méthode calcule le coût total de l'action en multipliant le nombre de parts par le prix.

Test de la classe Stock

Maintenant que nous avons créé notre classe Stock, nous devons la tester pour voir si les validateurs fonctionnent correctement. Ouvrez un nouveau terminal et lancez l'interpréteur Python. Vous pouvez le faire en exécutant la commande suivante :

python3

Une fois que l'interpréteur Python est en cours d'exécution, nous pouvons importer et tester notre classe Stock. Entrez le code suivant dans l'interpréteur Python :

from stock import Stock

## Create a valid stock
s = Stock('GOOG', 100, 490.10)
print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}")
print(f"Cost: {s.cost()}")

## Try setting an invalid shares value
try:
    s.shares = -10
except ValueError as e:
    print(f"Error setting shares: {e}")

## Try setting an invalid price value
try:
    s.price = "not a price"
except TypeError as e:
    print(f"Error setting price: {e}")

Lorsque vous exécutez ce code, vous devriez voir une sortie similaire à ceci :

Name: GOOG, Shares: 100, Price: 490.1
Cost: 49010.0
Error setting shares: Expected >= 0
Error setting price: Expected <class 'float'>

Cette sortie montre que nos validateurs fonctionnent comme prévu. La classe Stock ne nous permet pas de définir des valeurs invalides pour shares et price. Lorsque nous essayons de définir une valeur invalide, une erreur est levée, et nous pouvons capturer et afficher cette erreur.

Comprendre l'aide de l'héritage

L'un des grands avantages de l'utilisation de nos validateurs est que nous pouvons facilement combiner différentes règles de validation. L'héritage est un concept puissant en Python qui nous permet de créer de nouvelles classes basées sur des classes existantes. Avec l'héritage multiple, nous pouvons utiliser la fonction super() pour appeler des méthodes de plusieurs classes parentes.

Par exemple, si nous voulons nous assurer que le nom de l'action n'est pas vide, nous pouvons suivre ces étapes :

  1. Importer le validateur NonEmptyString depuis le module validate. Ce validateur nous aidera à vérifier si le nom de l'action n'est pas une chaîne de caractères vide.
  2. Ajouter un mutateur de propriété pour l'attribut name dans la classe Stock. Ce mutateur utilisera la méthode NonEmptyString.check() pour valider le nom de l'action.

Cela montre comment l'héritage, en particulier l'héritage multiple avec la fonction super(), nous permet de construire des composants flexibles et réutilisables dans différentes combinaisons.

Lorsque vous avez terminé de tester, vous pouvez quitter l'interpréteur Python en exécutant la commande suivante :

exit()
✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire, vous avez appris le comportement de l'héritage en Python et maîtrisé plusieurs concepts clés. Vous avez exploré la différence entre l'héritage simple et l'héritage multiple, compris comment la fonction super() navigue dans l'ordre de résolution des méthodes (Method Resolution Order - MRO), appris à implémenter un héritage multiple coopératif et appliqué l'héritage pour construire un système de validation pratique.

Vous avez également créé un cadre de validation flexible en utilisant l'héritage et l'avez appliqué à un exemple concret avec la classe Stock, ce qui montre comment l'héritage peut créer des composants réutilisables et composables. Les points clés à retenir incluent le fonctionnement de super() dans l'héritage simple et multiple, la capacité de l'héritage multiple à composer des fonctionnalités et l'utilisation de mutateurs de propriétés avec des validateurs. Ces concepts sont fondamentaux pour la programmation orientée objet en Python et sont largement utilisés dans les applications réelles.