Python & AI Tutorials Logo
Programmation Python

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 :

python
# 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 :

python
# 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: Welcome

Lorsque vous exécutez main.py, Python exécute l’instruction import greetings. Voici ce qui se passe en coulisses :

Non

Oui

import greetings

greetings
déjà importé ?

Rechercher greetings.py

Exécuter greetings.py
de haut en bas

Créer un objet module
nommé 'greetings'

Stocker fonctions et variables
comme attributs

Rendre 'greetings' disponible
dans l’espace de noms courant

Utiliser l’objet module
existant

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.

python
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 module

La 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 :

  1. Répertoire courant : le répertoire contenant le script que vous exécutez
  2. PYTHONPATH : les répertoires listés dans la variable d’environnement PYTHONPATH (si définie)
  3. Bibliothèque standard : les répertoires de modules intégrés à Python
  4. Site-Packages : les packages tiers installés avec pip

Vous pouvez voir le chemin de recherche de Python en examinant sys.path :

python
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-packages

Le 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
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 keyword

Piè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 :

python
# 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 :

python
# demo_module.py
print("Module is being loaded!")
 
def greet():
    print("Hello from demo_module")
 
print("Module loading complete!")

Importez-le maintenant :

python
# 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_module

Remarquez 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 :

python
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 cache

Output:

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 :

python
import math
 
result = math.sqrt(16)
print(result)  # Output: 4.0
 
pi_value = math.pi
print(pi_value)  # Output: 3.141592653589793

Avec 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 :

python
from math import sqrt, pi
 
result = sqrt(25)  # Aucun préfixe 'math.' nécessaire
print(result)  # Output: 5.0
 
print(pi)  # Output: 3.141592653589793

Vous 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 :

python
# 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 defined

Vous pouvez importer plusieurs noms dans une seule instruction :

python
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: Welcome

L’import avec wildcard (et pourquoi l’éviter)

Python permet d’importer tout ce qui se trouve dans un module en utilisant * :

python
from math import *
 
print(sqrt(9))   # Output: 3.0
print(cos(0))    # Output: 1.0
print(pi)        # Output: 3.141592653589793

Cela 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 :

  1. Pollution de l’espace de noms : vous ne savez pas exactement quels noms vous importez
  2. Conflits de noms : les noms importés peuvent écraser vos propres variables
  3. Lisibilité : les lecteurs du code ne peuvent pas dire d’où viennent les noms
python
# 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 :

python
import math as m
 
result = m.sqrt(36)
print(result)  # Output: 6.0

C’est particulièrement utile pour les modules aux noms longs ou lorsque vous suivez des conventions courantes :

python
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 :

python
from math import sqrt as square_root
 
result = square_root(49)
print(result)  # Output: 7.0

C’est utile lorsque vous avez des conflits de noms :

python
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 :

python
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 :

python
# 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.14

Cet exemple montre :

  • import math pour le module entier (nous pourrions utiliser d’autres fonctions de math plus tard)
  • from statistics import mean, median pour des fonctions spécifiques que nous utilisons souvent
  • from datetime import datetime as dt pour 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 :

python
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.718281828459045

Comme 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 :

python
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 :

python
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: 19

Le 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 :

python
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 :

python
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 :

python
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.81

Le 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 :

python
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 :

python
# 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 :

python
# 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 = 90

Créez maintenant un autre fichier pour utiliser ce module :

python
# 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: Passing

Documentation 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() :

python
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 :

python
# 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 = True

Utiliser la configuration depuis un module :

python
# 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 3

Output:

Connecting to localhost:5432
Database: myapp
True
Too many attempts! Maximum is 3

Important : 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 :

python
# 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 :

python
# 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 = 8

Quand 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 :

python
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.py

Dans 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.py

Créez validators.py :

python
# 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 False

Créez formatters.py :

python
# 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 :

python
# data_tools/__init__.py
"""Data processing tools package."""

Importer depuis des packages

Vous pouvez maintenant importer depuis le package de plusieurs façons :

python
# 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.56

Le fichier __init__.py

Le fichier __init__.py a deux objectifs :

  1. Marquer le répertoire comme un package : Python reconnaît les répertoires avec __init__.py comme des packages
  2. Code d’initialisation du package : le code dans __init__.py s’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 :

python
# 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 :

python
# 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.99

22.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 :

python
# demo_name.py
print(f"The __name__ variable is: {__name__}")

Exécutez-le maintenant directement :

bash
python demo_name.py

Output:

The __name__ variable is: __main__

Importez-le maintenant depuis un autre fichier :

python
# test_import.py
import demo_name

Output:

The __name__ variable is: demo_name

Lorsque 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 :

python
if __name__ == "__main__":
    # Le code ici ne s’exécute que lorsque le fichier est exécuté directement
    pass

Voici pourquoi c’est utile. Créez math_utils.py :

python
# 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 :

bash
python math_utils.py

Output:

Testing math_utils functions...
Radius: 5
Area: 78.54
Circumference: 31.42

Mais quand vous l’importez :

python
# 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.16

Le 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 :

python
# 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.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai