Licence CC BY-NC-SA

Découper son code

Nous allons aborder un niveau d'organisation à plus grande échelle.

Plusieurs fichiers

Au risque de casser l'ambiance, je vous annonce tout de suite qu'un module n'est rien d'autre qu'un fichier. Nous allons donc voir comment écrire notre programme dans plusieurs fichiers. En plus de fournir un nouveau moyen d'organisation, que vous apprécierez largement quand vos programmes grossiront, vous pourrez mettre en commun certaines de vos fonctions entre plusieurs programmes en les plaçant dans un module commun.

Vous savez créer un fichier, vous savez donc en créer plusieurs. :) Nous allons nous intéresser à l'utilisation de ces modules. En effet, malgré ses qualités, Python n'est pas devin : il va falloir lui dire où se cachent nos modules et lesquels nous souhaitons utiliser.

Créez deux fichiers, par exemple programme.py et commun.py, dans un même répertoire.

N'oubliez pas de spécifier l'encodage de tous vos fichiers, y compris ceux qui sont partagés, comme commun.py dans cet exemple.

Souvenez-vous du OU exclusif que nous avions codé dans le chapitre sur les conditions. Vous décidez (même si vous ne le savez pas encore) de le placer dans une fonction, appelée xor par exemple, dans le fichier commun.py afin de pouvoir la réutiliser rapidement dans vos divers programmes.

Notre programme se situera dans programme.py dans cet exemple. Voici donc comment utiliser notre fonction xor. Essayez donc d’écrire tout seul le code de commun.py.

1
2
3
4
# commun.py
# N'oubliez pas l'encodage
def xor(a, b):
    return (a and not(b)) or (b and not(a))

1
2
3
4
# programme.py
import commun

print(commun.xor(True, 2 != 2))

Nous avons commencé par indiquer à Python qu'il devait charger le module commun. De façon générale, import nom permet de charger le fichier nom.py situé dans le même dossier que votre programme. Pour importer plusieurs modules, vous pouvez, soit écrire plusieurs import, soit séparer le nom des modules par une virgule. Prenez l’habitude de placer vos import au début de vos fichiers, mais en dessous de l'encodage tout de même. Nous avons ensuite utilisé commun.xor au lieu de xor. Nous avons dit à Python d'aller chercher la fonction xor dans le module commun.

Cette notation peut vous sembler lourde, mais elle est bien utile. Supposez que vous récupériez un module sur internet pour réaliser une certaine tâche. Si ce module défini une fonction portant le même nom que l'une des vôtres, Python va écraser la première fonction du même nom qu'il a rencontrée par la deuxième. Cela est problématique si les deux fonctions ne sont pas identiques dans leurs paramètres et leurs valeurs renvoyées, ce qui est généralement le cas. L’utilisation du nom de module comme préfixe du nom des fonctions permet à Python de savoir quelle fonction nous souhaitons utiliser.

Rassurez-vous, il existe des moyens de faire plus court, mais vous devez toujours faire attention à ce que plusieurs fonctions portant le même nom n'entrent pas en conflit.

1
2
import commun as com
print(com.xor(True, 2 != 2))

L'ajout de as crée un alias, c'est à dire que le module commun est maintenant connu sous le nom de com. Il existe également un moyen d'appeler une fonction sans spécifier son module :

1
2
from commun import xor
print(xor(True, 2 != 2))

La syntaxe générale est, comme vous l'avez peut-être deviné, from module import fonction1, fonction2. Vous n'importez alors que les fonctions spécifiées. Si vous souhaitez tout importer, vous pouvez utiliser from module import *.

Pour des modules de taille conséquente, il n'est pas recommandé de tout importer, surtout si vous n'utilisez que quelques fonctions, ce qui est le cas dans la grande majorité des cas. En effet, vous allez vous retrouver avec un nombre conséquent de noms utilisés par ce module, et vous aurez du mal à déterminer rapidement si un nom donné n’est pas déjà pris.

Vous pouvez également combiner cette dernière syntaxe avec as afin de changer le nom des fonctions importées, ce qui peut permettre d'éviter un conflit de nommage. Par exemple from module import fonction1 as f1, fonction2 as f2 importera les deux fonctions sous le nom de f1 et f2. Il faudra donc utiliser le nom avec lequel la fonction a été importée, par exemple f1, et non le nom avec lequel elle a été définie, dans ce cas fonction1.

Vous pouvez également importer des variables depuis un module. Pour pouvoir importer une telle variable, il vous suffit de la définir en dehors de toute fonction dans votre module. En ce qui concerne son utilisation, nous allons nous intéresser au module math de Python qui définit une variable pi. Nous pouvons l'importer de manière similaire aux fonctions. Le commentaire correspond à la façon d’accéder à pi pour chaque import.

1
2
3
4
import math # print(math.pi)
import math as m # print(m.pi)
from math import pi # print(pi)
from math import * # print(pi)

Notez que la dernière ligne n'importe pas seulement la variable pi, mais l’intégralité du module.

Il se peut que vous souhaitiez écrire un fichier qui serait un programme à lui seul, mais que vous souhaitiez pouvoir importer les fonctions de ce dernier sans lancer l'exécution du programme. Pour ce faire, nous allons utiliser une variable spéciale de Python :

1
2
3
4
5
6
7
def fonction(a):
    return a + 1

if __name__ == "__main__":
    a = 1
    while a < 5:
        a = fonction(a)

La variable __name__ contient le nom du module courant, c'est à dire le nom du module de ce fichier. Néanmoins, __name__ peut prendre une valeur spéciale, __main__, qui signifie « principal », pour indiquer que ce module a été exécuté depuis la console. Ainsi dans notre exemple, la boucle while ne sera prise en compte que si le fichier est exécuté et non importer.

De façon générale, les variables ou fonctions de la forme __abc__ correspondent à des fonctions ou variables ayant une signification spéciale pour Python.

Des répertoires

Nous allons maintenant encore étendre notre organisation en conquérant les répertoires. Bien entendu, on va leur donner un petit nom, celui de package, soit « paquet » en français. Tout comme un répertoire peut contenir plusieurs fichiers et plusieurs sous-dossiers, un package peut contenir plusieurs modules ainsi que d'autres packages.

Il s'agit d'un niveau de découpage encore plus grand que celui des modules. Nous allons illustrer ceci par un exemple. Imaginez que vous soyez en train de programmer un logiciel de gestion de la maison du futur pas si lointain. Vous seriez par exemple amené à écrire un programme gestion_courses. Celui-ci utiliserait les modules frigo et placard afin de savoir ce qu'il vous manque. Continuons avec l’arrosage. Vous auriez alors un programme arrosage_automatique qui utiliserait le module arrosage. Et ainsi de suite. Nous allons nous retrouver sous une montagne de modules et de programmes mélangés, même s'ils visent le but commun de gérer une maison. On pourrait alors utiliser des packages. La structure suivante représente une organisation possible de votre répertoire de travail :

  • arrosage_automatique.py, gestion_courses.py, nos programmes
  • exterieur/, un grand package
    • jardin/, un premier sous-package
      • arrosage.py, piscine.py, et bien d'autres modules
  • interieur/, un autre grand package avec plusieurs sous-packages
    • cuisine/
      • frigo.py, placard.py, …
    • salon/
      • tv.py, …

Les / marquent les dossiers, et donc les packages, le .py les modules. Le nom du package est le nom du répertoire, sans /. Remarquez que, tout comme pour les fichiers, préférez des noms évocateurs et n'utilisez ni d'espaces ni de caractère spéciaux, accents compris.

Je dois vous informer d'un petit mensonge d'une omission de ma part : un package est en réalité un dossier comportant un fichier __init__.py. Nous nous contenterons d'un fichier vide, mais sachez que ce fichier peut contenir du code qui sera exécuté à l'initialisation du package. Sans ce fichier, Python ne reconnaitra pas votre dossier comme un package.

Ceci est d'autant plus vrai si vous utilisez une version de Python inférieure à la 3.3. Les versions supérieures ne requièrent plus ce fichier, mais vous pouvez continuer à le mettre, même s'il reste vide, afin d'assurer une plus grande compatibilité.

Importer ses packages

Supposons que nous avons placé notre fichier commun.py dans un dossier nommé conditions. Je vous invite d'ailleurs à le faire pour tester les exemples suivants. Pour importer un module se trouvant dans un package, il faut utiliser import package.module. Les différentes syntaxes vues au-dessus sont également applicables dans ce contexte. En reprenant notre exemple, voici ce que cela donne, avec en commentaire l'appel associé :

1
2
3
import condition.commun # condition.commun.xor(a, b)
import condition.commun as com # com.xor(a, b)
from condition.commun import xor # xor(a, b)

Si vous souhaitez importer un sous-package, il faut alors séparer les noms des packages par un . et suivre la hiérarchie de vos dossiers, par exemple from interieur.cuisine import frigo.

Les importations relatives

Vos modules peuvent avoir besoin d'importer d'autres modules, se situant ou non dans le même package. Supposons que vous souhaitiez importer le module placard depuis le module frigo. On utilisera alors :

1
2
from . import placard # ex: placard.ingredients()
from .placard import ingredients # ex: ingredients()

Le . placé au début indique à Python qu’il doit chercher le module en partant du package dans lequel il se trouve et non à partir de l’emplacement de votre programme. En effet, votre programme, devra utiliser from interieur.cuisine.placard import ingredients, ce qui n’est pas le cas de frigo qui se trouve dans le même package. Vous pouvez appeler des sous-packages relativement, en séparant les différents niveaux par un point comme précédemment.

Essayons maintenant d'importer un package qui ne se situe pas sous le module actuel. Par exemple, essayons d'importer le module tv depuis frigo :

1
from ..salon.tv import volume # ex: volume()

Cette fois ci, le .. au début informe Python qu'il doit chercher le module en se positionnant d'abord dans le package au-dessus de celui dans lequel il est actuellement. Dans notre exemple, les .. font référence au package interieur. Si vous souhaitez remonter de plus d'un niveau, il faut alors rajouter un point supplémentaire par niveau à remonter. Par exemple, pour remonter de trois niveaux, utilisez from ....

Vous ne pouvez néanmoins pas remonter d'un niveau si le dossier parent n’est pas lui-même un package. Par exemple, vous ne pouvez pas utiliser la syntaxe relative pour accéder au package exterieur depuis le package interieur. Vous devrez alors utiliser from exterieur.jardin.piscine import temperature. En effet, les packages exterieur et interieur ne font pas partie d'un plus grand package commun. Naturellement, si ces deux derniers faisaient partie d'un plus grand package, propriete par exemple, la syntaxe relative pourrait être utilisée. La syntaxe absolue, avec le chemin en entier, est toujours disponible : frigo pourrait importer placard comme suit : from interieur.cuisine.placard import ingredients.


Il est maintenant temps de vérifier vos connaissances avant de s'avancer plus profondément. :diable: