Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
💥 Du 22 au 24 novembre : Accès 100% GRATUIT
à la Bibliothèque Numérique ENI. Je m'inscris !
  1. Livres et vidéos
  2. Python
  3. Modularité
Extrait - Python Libérez le potentiel de votre Raspberry Pi
Extraits du livre
Python Libérez le potentiel de votre Raspberry Pi
2 avis
Revenir à la page d'achat du livre

Modularité

Fonctions

En informatique, le développeur débutant se retrouve parfois à écrire un code proche d’un code qu’il a déjà écrit précédemment. Dans ce cas de figure, plutôt que d’écrire une seconde fois une portion de code identique, on doit alors écrire une fonction pour encapsuler cette portion de code afin de le capitaliser.

Cette encapsulation est en fait une stratégie de modularité de code. En effet, en créant une fonction, le développeur crée un module qu’il pourra utiliser facilement et rapidement dans son application en cours de développement, mais également potentiellement dans de futurs projets.

Une fonction permet de regrouper un ensemble d’instructions afin de les exécuter au moment de l’appel de la fonction à l’endroit voulu dans le code source.

Une fonction est généralement créée pour réaliser une fonctionnalité particulière, par exemple :

  • tri d’une liste

  • recherche d’un maximum

  • extraction d’une sous-chaîne au sein d’une chaîne de caractères

La réalisation de la fonctionnalité contenue au sein d’une fonction peut nécessiter d’avoir des informations a priori pour réaliser la tâche. Ces informations sont données à l’aide des paramètres de la fonction.

Une fonction peut calculer un résultat à la suite de son exécution et le développeur peut vouloir récupérer ce résultat. Ceci se réalise à l’aide des valeurs retournées par la fonction.

1. Cas classique

En Python, la syntaxe d’une fonction est la suivante :


def ma_fonction(<param1>, <param2>, ..., <paramn>) : 
        <instruction 1> 
        <instruction 2> 
        ... 
        <instruction n> 
return <val1>, <val2>, ... , <val n>
 

Il apparaît, à partir de cette définition générique, que nous pouvons passer autant de paramètres que nécessaire et que nous pouvons retourner autant de valeurs que nécessaire. Notez qu’une fonction doit être définie...

Programmation orientée objet

La programmation orientée objet (POO) est un concept informatique. Les puristes emploient le terme de paradigme, qui consiste à représenter les informations d’un problème sous la forme d’objets.

Un objet peut être tout élément du problème qui peut être réduit à un ensemble de données (on parle d’attributs) et de fonctionnalités (qu’on nomme méthodes).

Vus sous cet angle, tous les éléments d’un problème quelconque peuvent être vus comme des objets. Afin d’illustrer ce que peut être un objet, citons quelques exemples :

  • un point (au sens mathématique)

  • un fichier

  • une voiture

  • un flux de données

  • un service web

On voit donc qu’un objet peut servir à représenter un objet physique, un élément informatique ou même un concept.

Un des intérêts des objets est que, de par leur modularité, il est possible de les réutiliser d’un projet informatique à l’autre.

Comme nous l’avons dit précédement, un objet contient des données appelées attributs. Ces attributs peuvent être de type simple comme des entiers, des flottants ou des chaînes de caractères.

Un objet est créé à partir d’un moule qu’on nomme classe....

Python, programmation objet et différences principales avec d’autres langages

Python est un langage tout objet. Vous avez déjà manipulé sans le savoir, dans les chapitres précédents, des objets !

En effet, les éléments les plus basiques comme les float et les int sont des objets qui possèdent des attributs et des méthodes. C’est également le cas des list, dont vous avez manipulé quelques méthodes, par exemple append().

Python possède sa propre implémentation du paradigme de POO. Elle est, en comparaison à celle de C++, beaucoup plus accessible au débutant. En termes de différences :

  • C++ permet la surcharge de méthodes, alors la surcharge n’existe pas en Python pour les fonctions. C’est également le cas pour les méthodes des classes.

  • C++ permet de réduire plus ou moins l’accès aux attributs et aux méthodes à l’aide de mots-clés : public, private et protected. Python n’offre pas explicitement cette possibilité.

Outre ces différences principales, Python permet l’usage des propriétés, décorateurs, qui n’existent pas en C++.

Enfin, pour conclure cette section, donnons la définition générique en langage Python :


class <nom_de_la_classe> (object): 
 ...

Constructeur et attributs

Le constructeur est la première fonction qui est appelée lors de l’instanciation d’une classe. L’instanciation consiste à la création d’un objet à partir d’un modèle, ici une classe.

Dans la définition générique de la classe, le constructeur correspond à la méthode spéciale : __init__(self, ...).

En Python, cette méthode contient toutes les définitions et les initialisations d’attributs. Pour illustrer le propos, nous allons modéliser un téléphone. Commençons par ses attributs :

  • marque

  • modele

  • couleur

  • annee

L’exemple qui suit correspond à la déclaration des attributs en question. marque, modele et couleur sont des chaînes de caractères initialisées à la valeur vide " ". Quant à annee, c’est un entier initialisé à 0.


class Telephone(object): 
 
    def __init__(self): 
        self.marque = "" 
        self.modele = "" 
        self.couleur = "" 
        self.annee = 0
 

Il est important que le premier argument du constructeur soit toujours self, ce qui correspond à l’instance en cours de la classe.

Par ailleurs, les attributs de la classe sont toujours précédés de self. Ce qui signifie que nous manipulons la classe instanciée.

Dans la plupart des langages, l’instance de la classe en cours n’utilise pas le mot-clé réservé self, mais this. De plus, dans de nombreux langages, on ne manipule pas le mot-clé this pour manipuler les attributs de la classe. C’est par exemple le cas en C++. De ce point...

Méthodes membres

Une classe est composée d’attributs, mais également de méthodes. Le terme méthode désigne les fonctions manipulant les données de la classe et pouvant être accédées depuis une instance de l’objet.

La définition d’une méthode est semblable à la définition d’une fonction classique, à la différence près qu’elle est faite au sein de la classe et qu’une méthode comporte toujours comme premier paramètre self.

Afin d’illustrer ceci, ajoutons à la classe Telephone une méthode pour afficher les valeurs des quatre attributs. Cette méthode se nomme aff_attribut.


class Telephone(object): 
 
    def __init__(self, val_modele, val_couleur, val_annee = 2002): 
        self.marque = "" 
        self.modele = val_modele 
        self.couleur = val_couleur 
        self.annee = val_annee 
 
    def aff_attribut(self): 
        print("Valeur pour l'attribut marque : ", self.marque) 
        print("Valeur pour l'attribut modele : ", self.modele) 
        print("Valeur pour l'attribut couleur : ", self.couleur) 
        print("Valeur pour l'attribut annee : ", self.annee)
 

Cette méthode réalise l’affichage successif des quatre attributs de la classe. Comme pour le constructeur, pour accéder aux attributs, il faut passer par self.<nom_attribut>. Si ce n’est pas le cas, l’interpréteur Python indique que la variable à laquelle nous voulons accéder n’est pas membre de la classe, comme dans le message suivant : « NameError: name ’marque’ is not defined » .

Grâce à cette fonction, nous pouvons réduire la taille du code qui manipule les instances de la classe...

Python et l’encapsulation

En informatique, l’encapsulation est un principe qui consiste à protéger ou à cacher des données de certains objets. En d’autres termes, cela consiste à ne pas rendre possible l’accès à certains attributs depuis une instance d’une classe.

En C++, cela s’effectue à l’aide du mot-clé private, qui permet de protéger l’accès aux données définies sous la portée de ce mot-clé.

Le langage Python ne propose pas de mot-clé private pour réaliser l’encapsulation. Les notions d’attribut et de méthode privés n’existent pas directement en Python.

Les développeurs pallient cette limite en appliquant un ensemble de règles de bon usage (voir par exemple : https://google.github.io/styleguide/pyguide.html).

En pratique, la syntaxe admise pour représenter ce qui est privé est très simple : tous les attributs ou méthodes qui possèdent un caractère _ au début de leur nom sont définis comme privés.

Il est donc de la responsabilité du développeur de ne pas utiliser directement un attribut privé lorsqu’il manipule une instance d’un objet.

Cette syntaxe (qui caractérise Python) doit être connue et respectée, notamment dans le cadre d’un projet collaboratif ou de l’utilisation de sources disponibles en open source.

Transformons maintenant la classe Telephone pour rendre les variables privées. Les seules transformations sont à réaliser dans le constructeur, ainsi que là où les attributs sont utilisés :


class Telephone(objec)): 
 
    def __init__(self, val_modele, val_couleur, val_annee = 2002): 
        self._marque = "" 
        self._modele = val_modele 
        self._couleur...

Méthodes spéciales

Python ne permet pas la surcharge de fonction et la stratégie pour pallier ce manque avec les paramètres par défaut a été présentée. Il existe encore un cas de figure où la surcharge est utile. Il s’agit de la surcharge d’opérateur.

La surcharge d’opérateur consiste à réécrire le comportement d’un opérateur (arithmétique par exemple) pour l’adapter à la structure d’une classe définie par l’utilisateur. À titre d’exemple, il y a peu de chance que l’opérateur + puisse être utilisé directement entre deux instances d’une classe définie par l’utilisateur. Il est donc nécessaire de définir le comportement de l’opérateur d’addition lorsqu’il est appelé sur de tels objets.

En Python, pour réaliser cette opération, il existe un ensemble de méthodes dites spéciales. Pour être tout à fait exact, les méthodes spéciales ne servent pas uniquement à surcharger des opérateurs, elles peuvent être utilisées pour redéfinir le comportement des fonctions.

Par exemple, lorsque vous tapez dans une console IPython (après l’exécution de votre script) :


In [2]: t1
 

vous obtenez en sortie :


Out[2]: <__main__.Telephone at 0x745fc630>
 

Cette manipulation est généralement faite à des fins de débogage. Or, les informations obtenues en sortie sont peu utiles pour détecter un bogue.

La fonction spéciale __repr__ permet de redéfinir le comportement de la fonction mise en œuvre ici et qui permet de contrôler comment est représenté un objet pour obtenir des informations plus intéressantes. Cette redéfinition se fait en ajoutant directement une méthode supplémentaire...

Héritage

L’héritage est une autre manière de réutiliser du code existant, c’est-à-dire de le capitaliser. Le principe est le suivant : une classe dite « fille » hérite des méthodes et attributs de la classe « mère » dont elle dérive.

Généralement, la classe fille est plus spécifique que la classe mère : elle récupère tous les attributs et méthodes de la classe mère et elle est enrichie de nouveaux attributs et de nouvelles méthodes.

Afin d’illustrer notre propos, créons une classe Smartphone qui dérive de la classe Telephone, mais qui a en plus un attribut _os, ainsi que les accesseurs et mutateurs associés.


class Smartphone(Telephone): 
 
    def __init__(self, val_modele, val_couleur, val_os = "Symbian", 
                 val_valeur=750, val_annee=2002): 
        super(Smartphone, self).__init__(val_modele, val_couleur, val_valeur, 
                                   val_annee) 
        self._os = val_os 
 
 
    @property 
    def os(self): 
        return self._os 
 
    @os.setter 
    def os(self, value): 
        self._os = value
 

Dans la définition de la classe Smartphone, la classe Telephone apparaît entre parenthèses. Cela signifie que la classe Smartphone dérive de la classe Telephone. Par conséquent, elle hérite de tous les attributs et méthodes de la classe Telephone.

Le constructeur de la classe Smartphone a en paramètre une valeur permettant d’initialiser le nouvel attribut de la classe ainsi que les valeurs permettant d’initialiser les attributs hérités de la classe Telephone.

Cette initialisation des attributs de la classe mère...

Créer ses propres modules et packages

1. Modules

La création de modules a déjà été abordée implicitement dans la section sur les fonctions. En fait, un module est simplement un regroupement de fonctions ou de classes dans un fichier.

L’utilisation des éléments formant le module à l’intérieur d’une autre source se fait à l’aide des instructions from et import.

Si nous consignons la définition des classes Telephone et Smartphone dans le fichier telephonie.py, il est nécessaire d’importer les deux classes en début de fichier, de la manière suivante :


from telephonie import Telephone 
from telephonie import Smartphone
 

Ce faisant, nous obtenons l’exemple suivant :


from telephonie import Telephone 
from telephonie import Smartphone 
 
 
print("Instance t1 avec tous les arguments") 
# Instanciation de la classe en précisant tous les paramètres 
t1 = Telephone("3310", "jaune", 35, 1997) 
 
print("Instance t2 avec tous les arguments") 
# Instanciation de la classe en précisant tous les paramètres 
t2 = Telephone("3310", "carbone", 45, 200) 
 
# utilisation de l'opérateur + redéfini pour la classe Telephone 
val_cumul = t1 + t2 
print("Valeur...

Travaux pratiques

1. Le problème

Nous proposons ici de coder deux méthodes de cryptage qui sont :

  • le cryptage de Vigenère

  • le cryptage par décalage

Avant toute chose, définissons comment fonctionne chaque méthode (source Wikipédia) :

  • Le cryptage de Vigenère : « Ce chiffrement introduit la notion de clé. Une clé se présente généralement sous la forme d’un mot ou d’une phrase. Pour pouvoir chiffrer notre texte, à chaque caractère nous utilisons une lettre de la clé pour effectuer la substitution. Évidemment, plus la clé sera longue et variée et mieux le texte sera chiffré. Il faut savoir qu’il y a eu une période où des passages entiers d’œuvres littéraires étaient utilisés pour chiffrer les plus grands secrets. Les deux correspondants n’avaient plus qu’à avoir en leurs mains un exemplaire du même livre pour s’assurer de la bonne compréhension des messages. ».

    Nous nous limiterons à utiliser une clé de la même taille que le texte à coder afin de réduire la difficulté, mais en conservant le principe.

  • Le cryptage par décalage : « En cryptographie, le chiffrement par décalage, aussi connu comme le chiffre de César ou le code de César [...], est une méthode de chiffrement très simple utilisée par Jules César dans ses correspondances secrètes (ce qui explique le nom "chiffre de César").

    Le texte chiffré s’obtient en remplaçant chaque lettre du texte clair original par une lettre à distance fixe, toujours du même côté, dans l’ordre de l’alphabet. Pour les dernières lettres (dans le cas d’un décalage à droite), on reprend au début. Par exemple, avec un décalage de 3 vers la droite, A est remplacé par D, B devient E, et ainsi jusqu’à W qui devient Z, puis X devient A, etc. Il s’agit d’une permutation circulaire de l’alphabet. La longueur du décalage, 3 dans l’exemple évoqué, constitue la clé du chiffrement qu’il suffit de transmettre au destinataire - s’il sait déjà...

Conclusion

Ce chapitre a permis d’introduire les éléments nécessaires à la création de code modulable et permettant une capitalisation d’un projet à l’autre. Tous les éléments permettant de traiter la programmation orientée objet ont été présentés dans leur globalité. Vous pouvez donc maintenant écrire des scripts complets et complexes, ce qui permet dans la suite de cet ouvrage de passer à des applications concrètes pour votre Raspberry Pi.