Outils pour utilisateurs

Outils du site


cours:informatique:fun_mooc:python3_uca_inria:380_introduction_aux_classes

Python: Introduction aux classes

Les types built-in, bien qu'étant de nature différente, se manipulent de manière extrêmement proche. Connaître la longueur d'une séquence, la longueur d'un set ou d'un dictionnaire, c'est fait avec *len*. Afficher le contenu de ces objets, c'est fait avec *print*. Faire le test d'appartenance, c'est fait avec *in*.

Cette uniformité dans le comportement des objets est une caractéristique majeure de Python. Ce qui explique la faible courbe d'apprentissage que l'on a en Python ; lorsque vous savez faire quelque chose avec un type d'objet, vous savez le faire avec quasiment tous les autres types.

Python va bien au-delà de cette puissance des types built-in. En fait, en Python, vous pouvez écrire vos propres objets, définir vos propres types d'objets, qui vont se comporter exactement comme des types built-in. Donc vous pouvez créer des objets qui vont supporter le test d'appartenance avec *in*, qui vont supporter la fonction built-in *print* ou la fonction built-in *len*, ou qui vont supporter l'affectation avec les crochets.

On présente ici le mécanisme qui permet d'implémenter vos propres objets. C'est ce qu'on appelle des classes. Avec une classe, vous pouvez créer des objets et définir des méthodes pour que vos objets se comportent exactement comme des types built-in.

Définir une classe c'est définir un nouveau type, c'est la même chose. La classe est le modèle, la matrice qui va créer vos instances, et les instances, ce sont les objets qui vont être produits, présents en mémoire dans l'espace objet et manipulables. Si vous voulez prendre une analogie avec les types built-in, list, c'est le type qui va créer les objets listes, et tous les objets listes que l'on produit sont des instances du type list.

Définir une classe

On crée une classe avec l'instruction class suivie du nom de la classe, et je vais faire une classe qui ne fait absolument rien. En fait, comme il faut une instruction dans le bloc de code de la classe, je mets simplement pass qui ne fait absolument rien.

# Définition d'une classe nommée C
class C:
  pass

Une classe C existe à présent dans le *main* de l'interpréteur que l'on peut appeler avec des parenthèses. Lorsque j'appelle la classe C avec des parenthèses, on crée des instances.

# creation d'une instance de la classe C et affectation
# de la référence à la variable mon_obj
>>> mon_obj = C()                                                           
 
# la variable mon_obj référence un objet avec un identifiant
>>> id(mon_obj)                                                             
139855861134192
 
# Le type de l'objet référencé par mon_obj est bien C
>>> type(mon_obj)                                                           
__main__.C

Pour le moment comme cette classe n'a défini absolument aucune méthode, on n'a aucun comportement spécifique avec notre classe, on a essentiellement une classe qui ne sert à rien, qui ne fait rien. Maintenant, on veut ajouter des méthodes (fonctions ou comportements) dans nos classes. La première méthode qu'on peut ajouter, c'est ce qu'on appelle l'initialisateur ou le constructeur de nos instances. Pour l' exemple on va créer une nouvelle classe que l'on nommera *Phrase*.

Par convention pour nommer les classe on utilise le CamelCase
class Phrase:
def __init__(self, phrase):
      self.mots = phrase.split()

Pour définir une méthode on utilise le mot clé *def*. Ici la méthode pour être un constructeur et être automatiquement appelée lors de la création d'une instance doit avoir:

  • un nom précis: __init__
  • prendre un ou plusieurs arguments selon les besoins mais le premier doit toujours être self (cette règle s'applique a toute méthode d'une classe);

Dans cet exemple le constructeur prend deux arguments *self* et *phrase*. Lors de l'appel du constructeur *init* le paramètre *self* va correspondre à l'instance qui a été créée. C'est via cette référence qu' on va pouvoir travailler sur l'instance et mettre à jours ses attributs et meme son comportement en ajoutant des méthodes.

Maintenant, on pourrait vouloir ajouter des méthodes à l'instance, par exemple, comme on le fait sur les listes. Sur les listes, on peut prendre une *liste.append* pour rajouter des éléments dans ma liste. On aimerait bien définir une méthode qui va mettre tous les mots en majuscule. Pour cela, on va définir une méthode qui s'appelle upper, qui prend obligatoirement comme premier argument mon instance (*self*), c'est comme ça que l'on peut travailler sur les attributs de l' instance, et qui va simplement transformer la liste mots en liste mise en majuscule.

class Phrase:
def __init__(self, phrase):
      # définition d'un attribut mots
      # de type liste retournée par la méthode split
      self.mots = phrase.split()
def upper(self):
    # Passe chaque mot en majuscules
    self.mots = [m.upper() for m in self.mots]
 
# création d'une instance du type Phrase   
>>> p = Phrase('ceci est une phrase')                                                     
 
# après initialisation, l'objet référencé par p
# a un attribut mots
>>> p.mots                                                                                
['ceci', 'est', 'une', 'phrase']
 
# La méthode upper permet de changer la
# casse de chaque mot
>>> p.upper()                                                                             
 
>>> p.mots                                                                               
['CECI', 'EST', 'UNE', 'PHRASE']

*p* est une variable de type Phrase, *p.upper()* appelle la méthode upper sur l'instance. Après l'exécution des instructions de la méthode on voit que l'attribut mots contient tous les mots mis en majuscule. La méthode appliquée à l'instance a été capable de modifier un attribut.

Pour afficher une variable buil-tin, on invoque pas une méthode, on utilise un *print* de l' instance. Or, qu'est-ce qu'il se passe on fait un print de la variable *p*?

>>> print(p)                                                                             
<__main__.Phrase object at 0x7f8cd70b9cd0>

On obtient l'adresse de l'objet. Or, ce n'est pas ce véritablement ce qu'on souhaite, on préfère en général afficher les attributs de l'objet. Redéfinir le comportement de l'affichage d'un objet est très facile à faire en Python. Il faut redéfinir la méthode qui s'appelle __str__:

  • qui prend comme argument self;
  • qui retourne une chaîne de caractères;

La chaîne de caractères que retournée est celle qui sera affichée, pour l'exemple ça va simplement afficher les mots les uns sous les autres:

class Phrase:
def __init__(self, phrase):
      # définition d'un attribut mots
      # de type liste retournée par la méthode split
      self.mots = phrase.split()
def upper(self):
    # Passe chaque mot en majuscules
    self.mots = [m.upper() for m in self.mots]
def __str__(self): 
    return "\n".join(self.mots)

Lorsque on fait un *print* d'une instance avec cette définition de classe, la méthode __str__ sera automatiquement appelée sur cette instance et va donc afficher les mots dans une colonne.

on aurait tout à fait pu implémenter également:

  • Le test d'appartenance avec la méthode __contains__; j
  • Le nombre d'éléments, le nombre de mots dans ma phrase, avec la méthode __len__ ;

Tous ces comportements que disponibles avec les types built-in, peuvent être définis pour la classe (ou le type) *Phrase*.

cours/informatique/fun_mooc/python3_uca_inria/380_introduction_aux_classes.txt · Dernière modification : 2021/04/28 16:08 de yoann