22. Organiser le code avec des modules et des packages
À mesure que vos programmes Python deviennent plus volumineux, garder tout votre code dans un seul fichier devient peu pratique. Vous aurez intérêt à organiser les fonctions, les classes et les variables liées dans des fichiers séparés, réutilisables dans différents programmes. Le système de modules (modules) et de packages (packages) de Python offre exactement cela : une manière d’organiser, de partager et de réutiliser le code efficacement.
Dans ce chapitre, nous explorerons le fonctionnement du système d’import (import) de Python, comment créer et utiliser vos propres modules, et comment organiser plusieurs modules en packages. Nous examinerons également la variable spéciale __name__ qui vous permet d’écrire des fichiers qui fonctionnent à la fois comme modules importables et comme scripts autonomes.
22.1) Ce que sont les modules et comment l’instruction import fonctionne
Comprendre les modules
Un module est simplement un fichier Python contenant des définitions et des instructions. Tout fichier .py que vous créez est un module. Lorsque vous écrivez une fonction dans un fichier appelé calculator.py, ce fichier devient un module nommé calculator que d’autres fichiers Python peuvent utiliser.
Les modules servent plusieurs objectifs importants :
- Réutilisabilité du code : écrire une fonction une fois et l’utiliser dans plusieurs programmes
- Organisation : regrouper des fonctionnalités liées
- Gestion d’espace de noms : garder des noms séparés pour éviter les conflits
- Maintenabilité : des fichiers plus petits et ciblés sont plus faciles à comprendre et à modifier
Créons un module simple pour voir comment cela fonctionne. Créez un fichier nommé greetings.py :
# greetings.py
def say_hello(name):
"""Return a friendly greeting."""
return f"Hello, {name}!"
def say_goodbye(name):
"""Return a farewell message."""
return f"Goodbye, {name}. See you soon!"
# Une variable au niveau du module
default_greeting = "Welcome"Ce fichier est maintenant un module. Il contient deux fonctions et une variable que d’autres fichiers Python peuvent utiliser.
L’instruction import
Pour utiliser du code provenant d’un module, vous l’importez. L’instruction import indique à Python de charger un module et de rendre son contenu disponible. Créez un autre fichier dans le même répertoire appelé main.py :
# main.py
import greetings
message = greetings.say_hello("Alice")
print(message) # Output: Hello, Alice!
farewell = greetings.say_goodbye("Bob")
print(farewell) # Output: Goodbye, Bob. See you soon!
print(greetings.default_greeting) # Output: WelcomeLorsque vous exécutez main.py, Python exécute l’instruction import greetings. Voici ce qui se passe en coulisses :
Important : Python n’exécute le code d’un module que la première fois qu’il est importé dans un programme. Les imports suivants dans le même programme réutilisent le module déjà chargé. Cela empêche des exécutions en double et fait gagner du temps.
Accéder au contenu d’un module
Après avoir importé un module, vous accédez à son contenu en utilisant la notation pointée : module_name.item_name. C’est similaire à la manière dont nous accédons aux méthodes de chaînes comme text.upper() ou aux méthodes de listes comme numbers.append(), comme nous l’avons appris aux chapitres 5 et 14.
import greetings
# Accéder aux fonctions
result = greetings.say_hello("Charlie")
# Accéder aux variables
greeting = greetings.default_greeting
# Vous pouvez même vérifier ce qui se trouve dans un module
print(dir(greetings)) # Liste tous les noms définis dans le moduleLa notation pointée indique clairement d’où provient chaque nom. Lorsque vous voyez greetings.say_hello(), vous savez immédiatement que cette fonction provient du module greetings.
Chemin de recherche des modules
Quand vous écrivez import greetings, comment Python trouve-t-il greetings.py ? Python recherche les modules dans un ordre précis :
- Répertoire courant : le répertoire contenant le script que vous exécutez
- PYTHONPATH : les répertoires listés dans la variable d’environnement
PYTHONPATH(si définie) - Bibliothèque standard : les répertoires de modules intégrés à Python
- Site-Packages : les packages tiers installés avec pip
Vous pouvez voir le chemin de recherche de Python en examinant sys.path :
import sys
for path in sys.path:
print(path)Sortie (exemple — vos chemins réels varieront selon votre système et votre installation de Python) :
/home/user/projects/myproject
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/usr/local/lib/python3.11/site-packagesLe premier chemin dans la sortie est le répertoire de travail actuel. Python recherche d’abord dans ce répertoire, afin de pouvoir trouver des modules dans le même répertoire.
Noms de modules et noms de fichiers
Le nom du module est le nom de fichier sans l’extension .py. Si votre fichier est string_utils.py, le nom du module est string_utils. Les noms de module doivent respecter les règles d’identifiant de Python (comme nous l’avons appris au chapitre 3) :
- Commencer par une lettre ou un underscore
- Contenir uniquement des lettres, des chiffres et des underscores
- Ne pas être des mots-clés Python
# Noms de modules valides (et noms de fichiers)
import data_processor # data_processor.py
import user_auth # user_auth.py
import _internal_helpers # _internal_helpers.py
# Invalide - provoquerait des erreurs
# import 2d_graphics # Can't start with digit
# import my-module # Hyphens not allowed
# import class # 'class' is a keywordPiège courant : masquer des modules de la bibliothèque standard
Faites attention à ne pas donner à vos modules le même nom que des modules de la bibliothèque standard. Si vous créez un fichier appelé random.py dans le répertoire de votre projet, Python importera votre fichier à la place du module random de la bibliothèque standard, provoquant des erreurs déroutantes :
# Votre fichier : random.py
def my_function():
return 42
# Un autre fichier dans votre projet
import random
print(random.randint(1, 6)) # ERROR! Your random.py doesn't have randint()Pour éviter cela, vérifiez si un nom est déjà utilisé par la bibliothèque standard avant de créer un module portant ce nom. Vous pouvez vérifier en essayant de l’importer dans le shell interactif Python. S’il s’importe sans erreur, ce nom est déjà pris.
Ce qui se passe pendant un import
Examinons ce qui se passe réellement lorsque vous importez un module. Créez un fichier appelé demo_module.py :
# demo_module.py
print("Module is being loaded!")
def greet():
print("Hello from demo_module")
print("Module loading complete!")Importez-le maintenant :
# test_import.py
print("Before import")
import demo_module
print("After import")
demo_module.greet()Output:
Before import
Module is being loaded!
Module loading complete!
After import
Hello from demo_moduleRemarquez que les instructions print() dans demo_module.py s’exécutent pendant l’import. Cela montre que l’import d’un module exécute tout son code de niveau supérieur. Les définitions de fonctions sont stockées pour une utilisation ultérieure, mais tout code en dehors des fonctions s’exécute immédiatement.
Si vous importez à nouveau le même module dans le même programme, les messages de chargement ne réapparaîtront pas :
import demo_module # Premier import - exécute le code du module
import demo_module # Second import - utilise le module mis en cache
import demo_module # Troisième import - utilise toujours le module mis en cacheOutput:
Module is being loaded!
Module loading complete!Le code du module ne s’exécute qu’une seule fois, quel que soit le nombre de fois où vous l’importez.
22.2) Différentes façons d’importer : import, from et as
Python propose plusieurs façons d’importer des modules et leur contenu. Chaque approche a des implications différentes sur la manière dont vous accédez aux noms importés et sur la manière dont ils affectent votre espace de noms (namespace).
Instruction import de base
L’instruction import de base que nous avons déjà vue charge le module entier :
import math
result = math.sqrt(16)
print(result) # Output: 4.0
pi_value = math.pi
print(pi_value) # Output: 3.141592653589793Avec cette approche, vous utilisez toujours le nom du module comme préfixe. Cela rend le code très clair — vous pouvez toujours dire d’où provient un nom.
Importer des noms spécifiques avec from
Parfois, vous n’avez besoin que d’un ou deux éléments d’un module. L’instruction from vous permet d’importer des noms spécifiques directement dans votre espace de noms :
from math import sqrt, pi
result = sqrt(25) # Aucun préfixe 'math.' nécessaire
print(result) # Output: 5.0
print(pi) # Output: 3.141592653589793Vous pouvez maintenant utiliser sqrt et pi directement sans le préfixe math.. C’est pratique lorsque vous utilisez fréquemment ces noms.
Voyons un autre exemple avec notre module greetings :
# Utilisation de from import
from greetings import say_hello
message = say_hello("Diana") # Accès direct
print(message) # Output: Hello, Diana!
# Cependant, say_goodbye n’est pas disponible puisque nous ne l’avons pas importé
# say_goodbye("Diana") # NameError: name 'say_goodbye' is not definedVous pouvez importer plusieurs noms dans une seule instruction :
from greetings import say_hello, say_goodbye, default_greeting
print(say_hello("Eve")) # Output: Hello, Eve!
print(say_goodbye("Frank")) # Output: Goodbye, Frank!
print(default_greeting) # Output: WelcomeL’import avec wildcard (et pourquoi l’éviter)
Python permet d’importer tout ce qui se trouve dans un module en utilisant * :
from math import *
print(sqrt(9)) # Output: 3.0
print(cos(0)) # Output: 1.0
print(pi) # Output: 3.141592653589793Cela importe tous les noms publics du module (les noms qui ne commencent pas par un underscore). Bien que cela semble pratique, c’est généralement considéré comme une mauvaise pratique parce que :
- Pollution de l’espace de noms : vous ne savez pas exactement quels noms vous importez
- Conflits de noms : les noms importés peuvent écraser vos propres variables
- Lisibilité : les lecteurs du code ne peuvent pas dire d’où viennent les noms
# Exemple problématique
from math import *
# Plus tard dans votre code...
def sqrt(x):
"""Your own square root function."""
return x ** 0.5
# Quel sqrt utilisez-vous ? Le vôtre ou celui de math ?
result = sqrt(16) # Confusing!Bonne pratique : importez des noms spécifiques ou utilisez l’instruction import de base. Évitez from module import * sauf dans des sessions interactives où vous expérimentez.
Renommer des imports avec as
Parfois, les noms de modules ou de fonctions sont longs, ou vous voulez éviter des conflits de noms. Le mot-clé as vous permet de créer un alias :
import math as m
result = m.sqrt(36)
print(result) # Output: 6.0C’est particulièrement utile pour les modules aux noms longs ou lorsque vous suivez des conventions courantes :
import datetime as dt
today = dt.date.today()
print(today) # Output: 2025-12-19 (or current date)Vous pouvez aussi renommer des imports spécifiques :
from math import sqrt as square_root
result = square_root(49)
print(result) # Output: 7.0C’est utile lorsque vous avez des conflits de noms :
from math import sqrt as math_sqrt
def sqrt(x):
"""Custom square root with input validation."""
if x < 0:
return None
return math_sqrt(x)
print(sqrt(25)) # Output: 5.0 (your function)
print(sqrt(-4)) # Output: None (your function)Combiner des styles d’import
Vous pouvez mélanger différents styles d’import dans le même fichier :
import math
from datetime import date, time
from random import randint as random_int
# Utiliser math avec préfixe
radius = 5
area = math.pi * radius ** 2
# Utiliser date et time directement
today = date.today()
current_time = time(14, 30)
# Utiliser la fonction renommée
dice_roll = random_int(1, 6)Choisir le bon style d’import
Voici un guide de décision :
Utilisez import module quand :
- Vous avez besoin de plusieurs éléments du module
- Vous voulez une clarté maximale sur l’origine des noms
- Le nom du module est court et explicite
Utilisez from module import name quand :
- Vous n’avez besoin que d’un ou deux éléments spécifiques
- Les noms sont distinctifs et ont peu de chances d’entrer en conflit
- Vous utiliserez les noms fréquemment
Utilisez import module as alias quand :
- Le nom du module est très long
- Vous suivez une convention courante (comme
import numpy as np) - Vous devez éviter des conflits avec d’autres modules
Évitez from module import * dans le code de production :
- Utilisez-le uniquement pour des expériences rapides dans le shell interactif
- Ne l’utilisez jamais dans des modules que d’autres vont importer
Voyons un exemple complet démontrant de bonnes pratiques d’import :
# data_processor.py
import math
from statistics import mean, median
from datetime import datetime as dt
def calculate_statistics(numbers):
"""Calculate various statistics for a list of numbers."""
if not numbers:
return None
avg = mean(numbers)
mid = median(numbers)
std_dev = math.sqrt(sum((x - avg) ** 2 for x in numbers) / len(numbers))
return {
'mean': avg,
'median': mid,
'std_dev': std_dev,
'timestamp': dt.now()
}
# Test de la fonction
data = [10, 20, 30, 40, 50]
stats = calculate_statistics(data)
print(f"Mean: {stats['mean']}") # Output: Mean: 30.0
print(f"Median: {stats['median']}") # Output: Median: 30
print(f"Std Dev: {stats['std_dev']:.2f}") # Output: Std Dev: 14.14Cet exemple montre :
import mathpour le module entier (nous pourrions utiliser d’autres fonctions de math plus tard)from statistics import mean, medianpour des fonctions spécifiques que nous utilisons souventfrom datetime import datetime as dtpour un module couramment aliasé
22.3) Aperçu de modules courants de la bibliothèque standard Python
Python est livré avec une riche bibliothèque standard (standard library) — une collection de modules qui fournissent des solutions à des tâches de programmation courantes. Ces modules sont toujours disponibles ; vous n’avez rien à installer en plus. Comprendre ce qui est disponible dans la bibliothèque standard vous aide à éviter de « réinventer la roue ».
Le module math
Le module math fournit des fonctions mathématiques au-delà de l’arithmétique de base :
import math
# Fonctions trigonométriques
angle_rad = math.radians(45) # Convertir des degrés en radians
print(math.sin(angle_rad)) # Output: 0.7071067811865476
print(math.cos(angle_rad)) # Output: 0.7071067811865475
# Arrondi et valeur absolue
print(math.ceil(4.2)) # Output: 5 (arrondir vers le haut)
print(math.floor(4.8)) # Output: 4 (arrondir vers le bas)
print(math.fabs(-7.5)) # Output: 7.5 (valeur absolue sous forme de float)
# Exponentielle et logarithmique
print(math.exp(2)) # Output: 7.38905609893065 (e^2)
print(math.log(100)) # Output: 4.605170185988092 (log naturel)
print(math.log10(100)) # Output: 2.0 (log en base 10)
# Constantes
print(math.pi) # Output: 3.141592653589793
print(math.e) # Output: 2.718281828459045Comme nous l’avons appris au chapitre 4, le module math est essentiel pour les opérations mathématiques avancées.
Le module random
Le module random génère des nombres pseudo-aléatoires et effectue des sélections aléatoires :
import random
# Entiers aléatoires
dice = random.randint(1, 6) # Entier aléatoire de 1 à 6 (inclus)
print(f"Dice roll: {dice}")
# Floats aléatoires
probability = random.random() # Float aléatoire de 0.0 à 1.0
print(f"Probability: {probability:.4f}")
# Choix aléatoire dans une séquence
colors = ['red', 'blue', 'green', 'yellow']
chosen_color = random.choice(colors)
print(f"Chosen color: {chosen_color}")
# Mélanger une liste sur place
deck = ['A', 'K', 'Q', 'J', '10']
random.shuffle(deck)
print(f"Shuffled deck: {deck}")
# Échantillon aléatoire sans remise
lottery_numbers = random.sample(range(1, 50), 6)
print(f"Lottery numbers: {sorted(lottery_numbers)}")Output (example - will vary due to randomness):
Dice roll: 4
Probability: 0.7382
Chosen color: green
Shuffled deck: ['Q', 'A', '10', 'K', 'J']
Lottery numbers: [7, 15, 23, 31, 38, 42]Le module datetime
Le module datetime gère les dates et les heures :
from datetime import date, time, datetime, timedelta
# Date et heure actuelles
today = date.today()
now = datetime.now()
print(f"Today: {today}") # Output: Today: 2025-12-19
print(f"Now: {now}") # Output: Now: 2025-12-19 14:30:45.123456
# Créer des dates et heures spécifiques
birthday = date(1990, 5, 15)
meeting_time = time(14, 30)
appointment = datetime(2025, 12, 25, 10, 0)
print(f"Birthday: {birthday}") # Output: Birthday: 1990-05-15
print(f"Meeting: {meeting_time}") # Output: Meeting: 14:30:00
print(f"Appointment: {appointment}") # Output: Appointment: 2025-12-25 10:00:00
# Arithmétique des dates avec timedelta
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
print(f"Tomorrow: {tomorrow}") # Output: Tomorrow: 2025-12-20
print(f"Next week: {next_week}") # Output: Next week: 2025-12-26
# Extraire des composants
print(f"Year: {today.year}") # Output: Year: 2025
print(f"Month: {today.month}") # Output: Month: 12
print(f"Day: {today.day}") # Output: Day: 19Le module os
Le module os fournit des fonctionnalités du système d’exploitation. Nous explorerons cela en détail au chapitre 26, mais voici un aperçu :
import os
# Répertoire de travail actuel
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")
# Lister les fichiers d’un répertoire
files = os.listdir('.')
print(f"Files: {files[:3]}") # Afficher les 3 premiers fichiers
# Vérifier si un chemin existe
exists = os.path.exists('myfile.txt')
print(f"File exists: {exists}")
# Joindre des composants de chemin (fonctionne sur tous les systèmes)
file_path = os.path.join('data', 'users', 'profile.txt')
print(f"Path: {file_path}") # Output: data/users/profile.txt (or data\users\profile.txt on Windows)Le module sys
Le module sys fournit des paramètres et des fonctions spécifiques au système :
import sys
# Informations sur la version de Python
print(f"Python version: {sys.version}")
print(f"Version info: {sys.version_info}")
# Informations sur la plateforme
print(f"Platform: {sys.platform}") # Output: linux, darwin, win32, etc.
# Taille maximale d’un entier
print(f"Max int: {sys.maxsize}")Le module statistics
Le module statistics fournit des fonctions pour des calculs statistiques :
import statistics
grades = [85, 92, 78, 90, 88, 95, 82]
# Tendance centrale
avg = statistics.mean(grades)
mid = statistics.median(grades)
mode_val = statistics.mode([1, 2, 2, 3, 3, 3, 4])
print(f"Mean: {avg}") # Output: Mean: 87.14285714285714
print(f"Median: {mid}") # Output: Median: 88
print(f"Mode: {mode_val}") # Output: Mode: 3
# Dispersion
std_dev = statistics.stdev(grades)
variance = statistics.variance(grades)
print(f"Standard deviation: {std_dev:.2f}") # Output: Standard deviation: 5.90
print(f"Variance: {variance:.2f}") # Output: Variance: 34.81Le module collections
Le module collections fournit des types de conteneurs spécialisés. Nous explorerons cela davantage au chapitre 39, mais en voici un avant-goût :
from collections import Counter, defaultdict
# Counter - compter les occurrences
text = "hello world"
letter_counts = Counter(text)
print(letter_counts) # Output: Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
print(letter_counts['l']) # Output: 3
# defaultdict - dictionnaire avec des valeurs par défaut
word_lists = defaultdict(list)
word_lists['fruits'].append('apple')
word_lists['fruits'].append('banana')
word_lists['vegetables'].append('carrot')
print(dict(word_lists)) # Output: {'fruits': ['apple', 'banana'], 'vegetables': ['carrot']}Trouver davantage de modules de la bibliothèque standard
La bibliothèque standard de Python contient plus de 200 modules. Vous pouvez les explorer de plusieurs façons :
# Voir tous les modules disponibles (cela prend un moment)
help('modules')
# Obtenir de l’aide sur un module spécifique
import math
help(math)
# Voir ce qu’il y a dans un module
import random
print(dir(random))La documentation Python (https://docs.python.org/3/library/) fournit des informations complètes sur chaque module de la bibliothèque standard. À mesure que vous gagnez en expérience, vous découvrirez quels modules sont les plus utiles pour votre travail.
22.4) Créer et utiliser vos propres modules
Créer vos propres modules est simple — n’importe quel fichier Python peut être un module. La clé est d’organiser votre code de manière réfléchie afin que les modules soient ciblés, réutilisables et faciles à comprendre.
Créer un module simple
Créons un module pour travailler avec les notes des étudiants. Créez un fichier appelé grade_calculator.py :
# grade_calculator.py
"""Module for calculating and analyzing student grades."""
def calculate_average(grades):
"""Calculate the average of a list of grades."""
if not grades:
return 0
return sum(grades) / len(grades)
def get_letter_grade(numeric_grade):
"""Convert a numeric grade to a letter grade."""
if numeric_grade >= 90:
return 'A'
elif numeric_grade >= 80:
return 'B'
elif numeric_grade >= 70:
return 'C'
elif numeric_grade >= 60:
return 'D'
else:
return 'F'
def find_highest(grades):
"""Find the highest grade in a list."""
if not grades:
return None
return max(grades)
def find_lowest(grades):
"""Find the lowest grade in a list."""
if not grades:
return None
return min(grades)
# Constantes au niveau du module
PASSING_GRADE = 60
HONOR_ROLL_THRESHOLD = 90Créez maintenant un autre fichier pour utiliser ce module :
# student_report.py
import grade_calculator
# Notes de test de l’étudiant
test_scores = [85, 92, 78, 88, 95]
# Calculer les statistiques
average = grade_calculator.calculate_average(test_scores)
letter = grade_calculator.get_letter_grade(average)
highest = grade_calculator.find_highest(test_scores)
lowest = grade_calculator.find_lowest(test_scores)
# Générer le rapport
print("Student Grade Report")
print("=" * 40)
print(f"Test Scores: {test_scores}")
print(f"Average: {average:.1f}")
print(f"Letter Grade: {letter}")
print(f"Highest Score: {highest}")
print(f"Lowest Score: {lowest}")
# Vérifier l’honor roll
if average >= grade_calculator.HONOR_ROLL_THRESHOLD:
print("Status: HONOR ROLL!")
elif average >= grade_calculator.PASSING_GRADE:
print("Status: Passing")
else:
print("Status: Needs Improvement")Output:
Student Grade Report
========================================
Test Scores: [85, 92, 78, 88, 95]
Average: 87.6
Letter Grade: B
Highest Score: 95
Lowest Score: 78
Status: PassingDocumentation de module
Remarquez la docstring en haut de grade_calculator.py. Cette docstring au niveau du module décrit ce que fait le module. Elle apparaît quand quelqu’un utilise help() :
import grade_calculator
help(grade_calculator)Cela affiche la documentation du module, y compris la docstring du module et toutes les docstrings des fonctions. Une bonne documentation rend vos modules plus faciles à utiliser.
Variables et constantes au niveau du module
Les modules peuvent contenir des variables qui sont partagées pour toutes les utilisations du module. Elles sont souvent utilisées pour la configuration ou des constantes :
# config.py
"""Application configuration settings."""
# Paramètres de base de données
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "myapp"
# Paramètres de l’application
MAX_LOGIN_ATTEMPTS = 3
SESSION_TIMEOUT = 1800 # seconds
DEBUG_MODE = False
# Chemins de fichiers
DATA_DIR = "/var/data"
LOG_DIR = "/var/log"
# Feature flags
ENABLE_CACHING = True
ENABLE_LOGGING = TrueUtiliser la configuration depuis un module :
# app.py
import config
def connect_database():
"""Connect to the database using config settings."""
print(f"Connecting to {config.DB_HOST}:{config.DB_PORT}")
print(f"Database: {config.DB_NAME}")
if config.DEBUG_MODE:
print("DEBUG: Connection details logged")
def check_login_attempts(attempts):
"""Check if login attempts exceed the limit."""
if attempts >= config.MAX_LOGIN_ATTEMPTS:
print(f"Too many attempts! Maximum is {config.MAX_LOGIN_ATTEMPTS}")
return False
return True
connect_database()
print(check_login_attempts(2)) # Output: True
print(check_login_attempts(4)) # Output: Too many attempts! Maximum is 3Output:
Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3Important : les variables au niveau du module sont partagées entre tous les imports. Si vous modifiez une variable de module, le changement affecte tout le code utilisant ce module :
# file1.py
import config
config.DEBUG_MODE = True
print(f"File1 - Debug mode: {config.DEBUG_MODE}")
# file2.py
import config
print(f"File2 - Debug mode: {config.DEBUG_MODE}") # Will be True!Ce comportement peut être utile mais aussi surprenant. Soyez prudent lorsque vous modifiez des variables au niveau du module.
Noms privés dans les modules
Par convention, les noms commençant par un underscore sont considérés comme privés ou internes au module :
# user_manager.py
"""Module for managing user accounts."""
# Fonction utilitaire privée
def _validate_email(email):
"""Internal function to validate email format."""
return '@' in email and '.' in email
# Fonction publique
def create_user(username, email):
"""Create a new user account."""
if not _validate_email(email):
return None
user = {
'username': username,
'email': email,
'active': True
}
return user
# Constante privée
_MAX_USERNAME_LENGTH = 20
# Constante publique
MIN_PASSWORD_LENGTH = 8Quand vous utilisez from user_manager import *, les noms privés (ceux qui commencent par un underscore) ne sont pas importés. Cependant, vous pouvez toujours y accéder explicitement si nécessaire :
import user_manager
# Fonction publique - destinée à être utilisée
user = user_manager.create_user("alice", "alice@example.com")
# Fonction privée - accessible mais il ne faut pas en dépendre
# (elle pourrait changer dans les versions futures)
is_valid = user_manager._validate_email("test@test.com")Le préfixe underscore est un signal aux autres programmeurs : « C’est un détail d’implémentation. Ne comptez pas sur le fait qu’il reste identique. »
22.5) Comprendre les packages et __init__.py
À mesure que les projets grandissent, vous voudrez organiser plusieurs modules liés dans un package. Un package est un répertoire contenant des modules Python et un fichier spécial __init__.py.
Qu’est-ce qu’un package ?
Un package est une façon d’organiser plusieurs modules dans une structure hiérarchique. Voyez-le comme un dossier qui contient des fichiers Python, où le dossier lui-même peut être importé.
Voici une structure de package simple :
myproject/
main.py
utilities/
__init__.py
text.py
math.py
file.pyDans cette structure, utilities est un package contenant trois modules : text, math et file. Le fichier __init__.py (qui peut être vide) indique à Python que utilities est un package.
Créer un package simple
Créons un package pour le traitement de données. D’abord, créez cette structure de répertoires :
data_tools/
__init__.py
validators.py
formatters.pyCréez validators.py :
# data_tools/validators.py
"""Data validation functions."""
def is_valid_email(email):
"""Check if email has basic valid format."""
return '@' in email and '.' in email.split('@')[1]
def is_valid_phone(phone):
"""Check if phone number has valid format (simple check)."""
digits = ''.join(c for c in phone if c.isdigit())
return len(digits) == 10
def is_positive_number(value):
"""Check if value is a positive number."""
try:
return float(value) > 0
except (ValueError, TypeError):
return FalseCréez formatters.py :
# data_tools/formatters.py
"""Data formatting functions."""
def format_phone(phone):
"""Format phone number as (XXX) XXX-XXXX."""
digits = ''.join(c for c in phone if c.isdigit())
if len(digits) != 10:
return phone
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
def format_currency(amount):
"""Format number as currency."""
return f"${amount:,.2f}"
def format_percentage(value, decimals=1):
"""Format number as percentage."""
return f"{value * 100:.{decimals}f}%"Créez un __init__.py vide :
# data_tools/__init__.py
"""Data processing tools package."""Importer depuis des packages
Vous pouvez maintenant importer depuis le package de plusieurs façons :
# Méthode 1 : importer le module depuis le package
import data_tools.validators
email = "user@example.com"
is_valid = data_tools.validators.is_valid_email(email)
print(f"Email valid: {is_valid}") # Output: Email valid: True
# Méthode 2 : importer un module spécifique avec from
from data_tools import formatters
phone = "1234567890"
formatted = formatters.format_phone(phone)
print(f"Formatted phone: {formatted}") # Output: Formatted phone: (123) 456-7890
# Méthode 3 : importer des fonctions spécifiques
from data_tools.validators import is_valid_phone
from data_tools.formatters import format_currency
print(is_valid_phone("555-1234")) # Output: False (not 10 digits)
print(format_currency(1234.56)) # Output: $1,234.56Le fichier __init__.py
Le fichier __init__.py a deux objectifs :
- Marquer le répertoire comme un package : Python reconnaît les répertoires avec
__init__.pycomme des packages - Code d’initialisation du package : le code dans
__init__.pys’exécute lorsque le package est importé pour la première fois
Le fichier __init__.py peut être vide, mais il peut aussi contenir du code pour rendre le package plus simple à utiliser :
# data_tools/__init__.py
"""Data processing tools package."""
# Importer des fonctions couramment utilisées dans l’espace de noms du package
from data_tools.validators import is_valid_email, is_valid_phone
from data_tools.formatters import format_phone, format_currency
# Version du package
__version__ = '1.0.0'
# Constante au niveau du package
DEFAULT_CURRENCY_SYMBOL = '$'Désormais, les utilisateurs peuvent importer directement depuis le package :
# Au lieu de : from data_tools.validators import is_valid_email
# Vous pouvez écrire :
from data_tools import is_valid_email, format_currency
print(is_valid_email("test@test.com")) # Output: True
print(format_currency(99.99)) # Output: $99.9922.6) La variable __name__ et if __name__ == "__main__": : exécuter un fichier comme un script
Les fichiers Python peuvent servir à deux objectifs : ils peuvent être importés comme modules ou exécutés comme scripts autonomes. La variable spéciale __name__ vous aide à écrire du code qui fonctionne bien dans les deux situations.
Comprendre __name__
Chaque module Python possède une variable intégrée appelée __name__. Python définit cette variable différemment selon la manière dont le fichier est utilisé :
- Lorsqu’il est importé :
__name__est défini sur le nom du module - Lorsqu’il est exécuté directement :
__name__est défini sur"__main__"
Voyons cela en action. Créez un fichier appelé demo_name.py :
# demo_name.py
print(f"The __name__ variable is: {__name__}")Exécutez-le maintenant directement :
python demo_name.pyOutput:
The __name__ variable is: __main__Importez-le maintenant depuis un autre fichier :
# test_import.py
import demo_nameOutput:
The __name__ variable is: demo_nameLorsque vous exécutez demo_name.py directement, Python définit __name__ sur "__main__". Lorsque vous l’importez, Python définit __name__ sur le nom du module ("demo_name").
Le motif if __name__ == "__main__":
Ce comportement vous permet d’écrire du code qui ne s’exécute que lorsque le fichier est exécuté directement, pas lorsqu’il est importé. On fait cela avec le motif :
if __name__ == "__main__":
# Le code ici ne s’exécute que lorsque le fichier est exécuté directement
passVoici pourquoi c’est utile. Créez math_utils.py :
# math_utils.py
"""Utility functions for mathematical operations."""
def calculate_area(radius):
"""Calculate the area of a circle."""
return 3.14159 * radius ** 2
def calculate_circumference(radius):
"""Calculate the circumference of a circle."""
return 2 * 3.14159 * radius
# Code de test - ne s’exécute que lorsque le fichier est exécuté directement
if __name__ == "__main__":
print("Testing math_utils functions...")
test_radius = 5
area = calculate_area(test_radius)
circumference = calculate_circumference(test_radius)
print(f"Radius: {test_radius}")
print(f"Area: {area:.2f}")
print(f"Circumference: {circumference:.2f}")Quand vous exécutez ce fichier directement :
python math_utils.pyOutput:
Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42Mais quand vous l’importez :
# use_math_utils.py
import math_utils
# Le code de test ne s’exécute pas !
area = math_utils.calculate_area(10)
print(f"Area of circle: {area:.2f}") # Output: Area of circle: 314.16Le code de test dans le bloc if __name__ == "__main__": ne s’exécute pas pendant l’import. Cela vous permet d’inclure du code de test, des exemples ou des démonstrations dans vos modules sans affecter le code qui les importe.
Usages courants de if __name__ == "__main__":
Tests et démonstrations
Inclure des exemples montrant comment utiliser votre module :
# string_tools.py
def reverse_string(text):
"""Reverse a string."""
return text[::-1]
def count_vowels(text):
"""Count vowels in text."""
vowels = 'aeiouAEIOU'
return sum(1 for char in text if char in vowels)
if __name__ == "__main__":
# Code de démonstration
sample = "Hello, World!"
print(f"Original: {sample}")
print(f"Reversed: {reverse_string(sample)}")
print(f"Vowels: {count_vowels(sample)}")Dans ce chapitre, nous avons appris à organiser du code Python en utilisant des modules et des packages. Nous avons exploré le fonctionnement du système d’import, différentes façons d’importer du code, et comment créer nos propres modules et packages. Nous avons également appris la variable __name__ et le motif if __name__ == "__main__": qui permet à des fichiers de fonctionner à la fois comme modules importables et comme scripts autonomes.
Ces outils d’organisation deviennent de plus en plus importants à mesure que vos programmes grandissent. Dans le prochain chapitre, nous explorerons comment utiliser des fonctions comme des données et appliquer des techniques simples de programmation fonctionnelle, en nous appuyant sur la base solide d’organisation du code que nous avons établie ici.