Introduction
Dans ce laboratoire (lab), vous allez apprendre des concepts importants liés à l'organisation des packages Python. Tout d'abord, vous allez découvrir comment contrôler les symboles exportés à l'aide de __all__ dans les modules Python. Cette compétence est essentielle pour gérer ce qui est exposé par vos modules.
Deuxièmement, vous comprendrez comment combiner des sous-modules pour faciliter les importations et maîtriserez la technique de division des modules pour une meilleure organisation du code. Ces pratiques amélioreront la lisibilité et la maintenabilité de votre code Python.
Comprendre la complexité des importations de packages
Lorsque vous commencez à travailler avec des packages Python, vous réaliserez rapidement que l'importation de modules peut devenir assez compliquée et verbeuse. Cette complexité peut rendre votre code plus difficile à lire et à écrire. Dans ce laboratoire (lab), nous allons examiner de près ce problème et apprendre à simplifier le processus d'importation.
Structure d'importation actuelle
Tout d'abord, ouvrons le terminal. Le terminal est un outil puissant qui vous permet d'interagir avec le système d'exploitation de votre ordinateur. Une fois le terminal ouvert, nous devons nous déplacer vers le répertoire du projet. Le répertoire du projet est l'endroit où tous les fichiers liés à notre projet Python sont stockés. Pour ce faire, nous allons utiliser la commande cd, qui signifie "change directory" (changer de répertoire).
cd ~/project
Maintenant que nous sommes dans le répertoire du projet, examinons la structure actuelle du package structly. Un package en Python est un moyen d'organiser des modules liés. Nous pouvons utiliser la commande ls -la pour lister tous les fichiers et répertoires à l'intérieur du package structly, y compris les fichiers cachés.
ls -la structly
Vous remarquerez qu'il y a plusieurs modules Python à l'intérieur du package structly. Ces modules contiennent des fonctions et des classes que nous pouvons utiliser dans notre code. Cependant, si nous voulons utiliser la fonctionnalité de ces modules, nous devons actuellement utiliser de longues instructions d'importation. Par exemple :
from structly.structure import Structure
from structly.reader import read_csv_as_instances
from structly.tableformat import create_formatter, print_table
Ces longs chemins d'importation peuvent être fastidieux à écrire, surtout si vous devez les utiliser plusieurs fois dans votre code. Ils rendent également votre code moins lisible, ce qui peut être un problème lorsque vous essayez de comprendre ou de déboguer votre code. Dans ce laboratoire (lab), nous allons apprendre à organiser le package de manière à simplifier ces importations.
Commençons par regarder le contenu du fichier __init__.py du package. Le fichier __init__.py est un fichier spécial dans les packages Python. Il est exécuté lorsque le package est importé, et il peut être utilisé pour initialiser le package et configurer les importations nécessaires.
cat structly/__init__.py
Il est probable que vous constatiez que le fichier __init__.py est soit vide, soit contient très peu de code. Dans les prochaines étapes, nous allons modifier ce fichier pour simplifier nos instructions d'importation.
L'objectif
À la fin de ce laboratoire (lab), notre objectif est d'être en mesure d'utiliser des instructions d'importation beaucoup plus simples. Au lieu des longs chemins d'importation que nous avons vus précédemment, nous serons en mesure d'utiliser des instructions telles que :
from structly import Structure, read_csv_as_instances, create_formatter, print_table
Ou même :
from structly import *
L'utilisation de ces instructions d'importation plus simples rendra notre code plus propre et plus facile à manipuler. Cela nous fera également gagner du temps et des efforts lors de l'écriture et de la maintenance de notre code.
Contrôler les symboles exportés avec __all__
En Python, lorsque vous utilisez l'instruction from module import *, vous pouvez vouloir contrôler quels symboles (fonctions, classes, variables) sont importés depuis un module. C'est là que la variable __all__ s'avère utile. L'instruction from module import * est un moyen d'importer tous les symboles d'un module dans l'espace de noms actuel. Cependant, parfois, vous ne voulez pas importer chaque symbole, surtout s'il y en a beaucoup ou si certains sont destinés à être internes au module. La variable __all__ vous permet de spécifier exactement quels symboles doivent être importés lorsque vous utilisez cette instruction.
Qu'est-ce que __all__?
La variable __all__ est une liste de chaînes de caractères. Chaque chaîne de caractères dans cette liste représente un symbole (fonction, classe ou variable) que le module exporte lorsque quelqu'un utilise l'instruction from module import *. Si la variable __all__ n'est pas définie dans un module, l'instruction import * importera tous les symboles qui ne commencent pas par un tiret bas. Les symboles commençant par un tiret bas sont généralement considérés comme privés ou internes au module et ne sont pas destinés à être importés directement.
Modifier chaque sous-module
Maintenant, ajoutons la variable __all__ à chaque sous-module du package structly. Cela nous aidera à contrôler quels symboles sont exportés depuis chaque sous-module lorsque quelqu'un utilise l'instruction from module import *.
- Tout d'abord, modifions
structure.py:
touch ~/project/structly/structure.py
Cette commande crée un nouveau fichier nommé structure.py dans le répertoire structly de votre projet. Après avoir créé le fichier, nous devons ajouter la variable __all__. Ajoutez cette ligne tout en haut du fichier, juste après les instructions d'importation :
__all__ = ['Structure']
Cette ligne indique à Python que lorsque quelqu'un utilise from structure import *, seul le symbole Structure sera importé. Enregistrez le fichier et quittez l'éditeur.
- Ensuite, modifions
reader.py:
touch ~/project/structly/reader.py
Cette commande crée un nouveau fichier nommé reader.py dans le répertoire structly. Maintenant, parcourez le fichier pour trouver toutes les fonctions qui commencent par read_csv_as_. Ce sont ces fonctions que nous voulons exporter. Ensuite, ajoutez une liste __all__ avec tous les noms de ces fonctions. Elle devrait ressembler à ceci :
__all__ = ['read_csv_as_instances', 'read_csv_as_dicts', 'read_csv_as_columns']
Notez que les noms réels des fonctions peuvent varier en fonction de ce que vous trouvez dans le fichier. Assurez-vous d'inclure toutes les fonctions read_csv_as_* que vous trouvez. Enregistrez le fichier et quittez l'éditeur.
- Maintenant, modifions
tableformat.py:
touch ~/project/structly/tableformat.py
Cette commande crée un nouveau fichier nommé tableformat.py dans le répertoire structly. Ajoutez cette ligne tout en haut du fichier :
__all__ = ['create_formatter', 'print_table']
Cette ligne spécifie que lorsque quelqu'un utilise from tableformat import *, seuls les symboles create_formatter et print_table seront importés. Enregistrez le fichier et quittez l'éditeur.
Importations unifiées dans __init__.py
Maintenant que chaque module définit ce qu'il exporte, nous pouvons mettre à jour le fichier __init__.py pour importer tous ces symboles. Le fichier __init__.py est un fichier spécial dans les packages Python. Il est exécuté lorsque le package est importé, et il peut être utilisé pour initialiser le package et importer des symboles depuis les sous-modules.
touch ~/project/structly/__init__.py
Cette commande crée un nouveau fichier __init__.py dans le répertoire structly. Modifiez le contenu du fichier comme suit :
## structly/__init__.py
from .structure import *
from .reader import *
from .tableformat import *
Ces lignes importent tous les symboles exportés depuis les sous-modules structure, reader et tableformat. Le point (.) avant les noms des modules indique que ce sont des importations relatives, c'est-à-dire des importations depuis le même package. Enregistrez le fichier et quittez l'éditeur.
Tester nos modifications
Créons un fichier de test simple pour vérifier que nos modifications fonctionnent. Ce fichier de test tentera d'importer les symboles que nous avons spécifiés dans les variables __all__ et affichera un message de réussite si les importations sont réussies.
touch ~/project/test_structly.py
Cette commande crée un nouveau fichier nommé test_structly.py dans le répertoire du projet. Ajoutez ce contenu au fichier :
## A simple test to verify our imports work correctly
from structly import Structure
from structly import read_csv_as_instances
from structly import create_formatter, print_table
print("Successfully imported all required symbols!")
Ces lignes tentent d'importer la classe Structure, la fonction read_csv_as_instances et les fonctions create_formatter et print_table depuis le package structly. Si les importations sont réussies, le programme affichera le message "Successfully imported all required symbols!". Enregistrez le fichier et quittez l'éditeur. Maintenant, exécutons ce test :
cd ~/project
python test_structly.py
La commande cd ~/project change le répertoire de travail actuel pour le répertoire du projet. La commande python test_structly.py exécute le script test_structly.py. Si tout fonctionne correctement, vous devriez voir le message "Successfully imported all required symbols!" affiché à l'écran.
Exporter tout depuis le package
En Python, l'organisation des packages est essentielle pour gérer efficacement le code. Maintenant, nous allons pousser notre organisation de package plus loin. Nous allons définir quels symboles doivent être exportés au niveau du package. Exporter des symboles signifie rendre certaines fonctions, classes ou variables disponibles pour d'autres parties de votre code ou pour d'autres développeurs qui pourraient utiliser votre package.
Ajouter __all__ au package
Lorsque vous travaillez avec des packages Python, vous pouvez vouloir contrôler quels symboles sont accessibles lorsque quelqu'un utilise l'instruction from structly import *. C'est là que la liste __all__ s'avère utile. En ajoutant une liste __all__ au fichier __init__.py du package, vous pouvez précisément contrôler quels symboles sont disponibles lorsque quelqu'un utilise l'instruction from structly import *.
Tout d'abord, créons ou mettons à jour le fichier __init__.py. Nous allons utiliser la commande touch pour créer le fichier s'il n'existe pas.
touch ~/project/structly/__init__.py
Maintenant, ouvrez le fichier __init__.py et ajoutez une liste __all__. Cette liste doit inclure tous les symboles que nous voulons exporter. Les symboles sont regroupés en fonction de leur origine, par exemple les modules structure, reader et tableformat.
## structly/__init__.py
from .structure import *
from .reader import *
from .tableformat import *
## Define what symbols are exported when using "from structly import *"
__all__ = ['Structure', ## from structure
'read_csv_as_instances', 'read_csv_as_dicts', 'read_csv_as_columns', ## from reader
'create_formatter', 'print_table'] ## from tableformat
Après avoir ajouté le code, enregistrez le fichier et quittez l'éditeur.
Comprendre import *
Le modèle from module import * n'est généralement pas recommandé dans la plupart des codes Python. Voici plusieurs raisons à cela :
- Il peut polluer votre espace de noms avec des symboles inattendus. Cela signifie que vous pourriez avoir des variables ou des fonctions dans votre espace de noms actuel que vous n'attendiez pas, ce qui peut entraîner des conflits de noms.
- Il rend difficile de savoir d'où viennent les symboles particuliers. Lorsque vous utilisez
import *, il est difficile de savoir de quel module un symbole provient, ce qui peut rendre votre code plus difficile à comprendre et à maintenir. - Il peut entraîner des problèmes d'ombre (shadowing). L'ombre se produit lorsqu'une variable ou une fonction locale a le même nom qu'une variable ou une fonction d'un autre module, ce qui peut entraîner un comportement inattendu.
Cependant, il y a des cas spécifiques où l'utilisation de import * est appropriée :
- Pour les packages conçus pour être utilisés comme un tout cohérent. Si un package est destiné à être utilisé comme une unité unique, alors l'utilisation de
import *peut faciliter l'accès à tous les symboles nécessaires. - Lorsqu'un package définit une interface claire via
__all__. En utilisant la liste__all__, vous pouvez contrôler quels symboles sont exportés, ce qui rend plus sûr l'utilisation deimport *. - Pour une utilisation interactive, comme dans un REPL Python (Read-Eval-Print Loop). Dans un environnement interactif, il peut être pratique d'importer tous les symboles d'un coup.
Tester avec Import *
Pour vérifier que nous pouvons importer tous les symboles d'un coup, créons un autre fichier de test. Nous allons utiliser la commande touch pour créer le fichier.
touch ~/project/test_import_all.py
Maintenant, ouvrez le fichier test_import_all.py et ajoutez le contenu suivant. Ce code importe tous les symboles du package structly puis teste si certains des symboles importants sont disponibles.
## Test importing everything at once
from structly import *
## Try using the imported symbols
print(f"Structure symbol is available: {Structure is not None}")
print(f"read_csv_as_instances symbol is available: {read_csv_as_instances is not None}")
print(f"create_formatter symbol is available: {create_formatter is not None}")
print(f"print_table symbol is available: {print_table is not None}")
print("All symbols successfully imported!")
Enregistrez le fichier et quittez l'éditeur. Maintenant, exécutons le test. Tout d'abord, naviguez jusqu'au répertoire du projet en utilisant la commande cd, puis exécutez le script Python.
cd ~/project
python test_import_all.py
Si tout est configuré correctement, vous devriez voir une confirmation que tous les symboles ont été importés avec succès.
Division de modules pour une meilleure organisation du code
Au fur et à mesure que vos projets Python grandissent, vous pourriez constater qu'un seul fichier de module devient assez volumineux et contient plusieurs composants liés mais distincts. Lorsque cela se produit, il est recommandé de diviser le module en un package avec des sous-modules. Cette approche rend votre code plus organisé, plus facile à maintenir et plus évolutif.
Comprendre la structure actuelle
Le module tableformat.py est un bon exemple d'un module volumineux. Il contient plusieurs classes de formateurs, chacune étant responsable du formatage des données d'une manière différente :
TableFormatter(classe de base) : C'est la classe de base pour toutes les autres classes de formateurs. Elle définit la structure de base et les méthodes que les autres classes hériteront et implémenteront.TextTableFormatter: Cette classe formate les données en texte brut.CSVTableFormatter: Cette classe formate les données au format CSV (Comma-Separated Values, valeurs séparées par des virgules).HTMLTableFormatter: Cette classe formate les données au format HTML (Hypertext Markup Language, langage de balisage hypertexte).
Nous allons réorganiser ce module en une structure de package avec des fichiers séparés pour chaque type de formateur. Cela rendra le code plus modulaire et plus facile à gérer.
Étape 1 : Nettoyer les fichiers de cache
Avant de commencer à réorganiser le code, il est préférable de nettoyer tous les fichiers de cache Python. Ces fichiers sont créés par Python pour accélérer l'exécution de votre code, mais ils peuvent parfois causer des problèmes lorsque vous apportez des modifications à votre code.
cd ~/project/structly
rm -rf __pycache__
Dans les commandes ci-dessus, cd ~/project/structly change le répertoire actuel pour le répertoire structly de votre projet. rm -rf __pycache__ supprime le répertoire __pycache__ et tout son contenu. L'option -r signifie récursif, ce qui signifie qu'elle supprimera tous les fichiers et sous-répertoires à l'intérieur du répertoire __pycache__. L'option -f signifie forcé, ce qui signifie qu'elle supprimera les fichiers sans demander de confirmation.
Étape 2 : Créer la nouvelle structure de package
Maintenant, créons une nouvelle structure de répertoire pour notre package. Nous allons créer un répertoire nommé tableformat et un sous-répertoire nommé formats à l'intérieur.
mkdir -p tableformat/formats
La commande mkdir est utilisée pour créer des répertoires. L'option -p signifie parents, ce qui signifie qu'elle créera tous les répertoires parents nécessaires s'ils n'existent pas. Donc, si le répertoire tableformat n'existe pas, il sera créé en premier, puis le répertoire formats sera créé à l'intérieur.
Étape 3 : Déplacer et renommer le fichier original
Ensuite, nous allons déplacer le fichier original tableformat.py dans la nouvelle structure et le renommer en formatter.py.
mv tableformat.py tableformat/formatter.py
La commande mv est utilisée pour déplacer ou renommer des fichiers. Dans ce cas, nous déplaçons le fichier tableformat.py dans le répertoire tableformat et le renommons en formatter.py.
Étape 4 : Diviser le code en fichiers séparés
Maintenant, nous devons créer des fichiers pour chaque formateur et déplacer le code pertinent dans ceux-ci.
1. Créer le fichier du formateur de base
touch tableformat/formatter.py
La commande touch est utilisée pour créer un fichier vide. Dans ce cas, nous créons un fichier nommé formatter.py dans le répertoire tableformat.
Nous allons conserver la classe de base TableFormatter et toutes les fonctions utilitaires générales telles que print_table et create_formatter dans ce fichier. Le fichier devrait ressembler à ceci :
## Base TableFormatter class and utility functions
__all__ = ['TableFormatter', 'print_table', 'create_formatter']
class TableFormatter:
def headings(self, headers):
'''
Emit table headings.
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data.
'''
raise NotImplementedError()
def print_table(objects, columns, formatter):
'''
Make a nicely formatted table from a list of objects and attribute names.
'''
formatter.headings(columns)
for obj in objects:
rowdata = [getattr(obj, name) for name in columns]
formatter.row(rowdata)
def create_formatter(fmt):
'''
Create an appropriate formatter given an output format name.
'''
if fmt == 'text':
from .formats.text import TextTableFormatter
return TextTableFormatter()
elif fmt == 'csv':
from .formats.csv import CSVTableFormatter
return CSVTableFormatter()
elif fmt == 'html':
from .formats.html import HTMLTableFormatter
return HTMLTableFormatter()
else:
raise ValueError(f'Unknown format {fmt}')
La variable __all__ est utilisée pour spécifier quels symboles doivent être importés lorsque vous utilisez from module import *. Dans ce cas, nous spécifions que seuls les symboles TableFormatter, print_table et create_formatter doivent être importés.
La classe TableFormatter est la classe de base pour toutes les autres classes de formateurs. Elle définit deux méthodes, headings et row, qui sont destinées à être implémentées par les sous-classes.
La fonction print_table est une fonction utilitaire qui prend une liste d'objets, une liste de noms de colonnes et un objet formateur, et affiche les données dans un tableau formaté.
La fonction create_formatter est une fonction usine qui prend un nom de format en argument et renvoie un objet formateur approprié.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications.
2. Créer le formateur de texte
touch tableformat/formats/text.py
Nous allons ajouter seulement la classe TextTableFormatter à ce fichier.
## Text formatter implementation
__all__ = ['TextTableFormatter']
from ..formatter import TableFormatter
class TextTableFormatter(TableFormatter):
'''
Emit a table in plain-text format
'''
def headings(self, headers):
print(' '.join('%10s' % h for h in headers))
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
print(' '.join('%10s' % d for d in rowdata))
La variable __all__ spécifie que seul le symbole TextTableFormatter doit être importé lorsque vous utilisez from module import *.
L'instruction from ..formatter import TableFormatter importe la classe TableFormatter depuis le fichier formatter.py dans le répertoire parent.
La classe TextTableFormatter hérite de la classe TableFormatter et implémente les méthodes headings et row pour formater les données en texte brut.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications.
3. Créer le formateur CSV
touch tableformat/formats/csv.py
Nous allons ajouter seulement la classe CSVTableFormatter à ce fichier.
## CSV formatter implementation
__all__ = ['CSVTableFormatter']
from ..formatter import TableFormatter
class CSVTableFormatter(TableFormatter):
'''
Output data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(str(d) for d in rowdata))
De même que dans les étapes précédentes, nous spécifions la variable __all__, importons la classe TableFormatter et implémentons les méthodes headings et row pour formater les données au format CSV.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications.
4. Créer le formateur HTML
touch tableformat/formats/html.py
Nous allons ajouter seulement la classe HTMLTableFormatter à ce fichier.
## HTML formatter implementation
__all__ = ['HTMLTableFormatter']
from ..formatter import TableFormatter
class HTMLTableFormatter(TableFormatter):
'''
Output data in HTML format.
'''
def headings(self, headers):
print('<tr>', end='')
for h in headers:
print(f'<th>{h}</th>', end='')
print('</tr>')
def row(self, rowdata):
print('<tr>', end='')
for d in rowdata:
print(f'<td>{d}</td>', end='')
print('</tr>')
Encore une fois, nous spécifions la variable __all__, importons la classe TableFormatter et implémentons les méthodes headings et row pour formater les données au format HTML.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications.
Étape 5 : Créer les fichiers d'initialisation de package
En Python, les fichiers __init__.py sont utilisés pour marquer les répertoires comme des packages Python. Nous devons créer des fichiers __init__.py dans les répertoires tableformat et formats.
1. Créer un fichier pour le package tableformat
touch tableformat/__init__.py
Ajoutez ce contenu au fichier :
## Re-export the original symbols from tableformat.py
from .formatter import *
Cette instruction importe tous les symboles depuis le fichier formatter.py et les rend disponibles lorsque vous importez le package tableformat.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications.
2. Créer un fichier pour le package formats
touch tableformat/formats/__init__.py
Vous pouvez laisser ce fichier vide ou ajouter une simple chaîne de documentation (docstring) :
'''
Format implementations for different output formats.
'''
La chaîne de documentation fournit une brève description de ce que fait le package formats.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications.
Étape 6 : Tester la nouvelle structure
Créons un test simple pour vérifier que nos modifications fonctionnent correctement.
cd ~/project
touch test_tableformat.py
Ajoutez ce contenu au fichier test_tableformat.py :
## Test the tableformat package restructuring
from structly import *
## Create formatters of each type
text_fmt = create_formatter('text')
csv_fmt = create_formatter('csv')
html_fmt = create_formatter('html')
## Define some test data
class TestData:
def __init__(self, name, value):
self.name = name
self.value = value
## Create a list of test objects
data = [
TestData('apple', 10),
TestData('banana', 20),
TestData('cherry', 30)
]
## Test text formatter
print("\nText Format:")
print_table(data, ['name', 'value'], text_fmt)
## Test CSV formatter
print("\nCSV Format:")
print_table(data, ['name', 'value'], csv_fmt)
## Test HTML formatter
print("\nHTML Format:")
print_table(data, ['name', 'value'], html_fmt)
Ce code de test importe les fonctions et classes nécessaires depuis le package structly, crée des formateurs de chaque type, définit des données de test et teste chaque formateur en affichant les données dans le format correspondant.
Enregistrez le fichier et quittez l'éditeur après avoir apporté ces modifications. Maintenant, exécutez le test :
python test_tableformat.py
Vous devriez voir les mêmes données formatées de trois manières différentes (texte, CSV et HTML). Si vous voyez la sortie attendue, cela signifie que votre réorganisation de code a réussi.
Résumé
Dans ce laboratoire, vous avez appris plusieurs techniques essentielles d'organisation de packages Python. Tout d'abord, vous avez maîtrisé l'utilisation de la variable __all__ pour définir explicitement les symboles exportés par un module. Ensuite, vous avez créé une interface de package plus conviviale en réexportant les symboles des sous - modules depuis le package de niveau supérieur.
Ces techniques sont essentielles pour créer des packages Python propres, faciles à maintenir et conviviaux. Elles vous permettent de contrôler la vue de l'utilisateur, de simplifier le processus d'importation et d'organiser logiquement le code au fur et à mesure que votre projet se développe.