Tutoriel Python

← Retour aux tutoriels | Partie 2 : Environnements virtuels Python → | Partie 3 : Packaging et publication →

Comment utiliser ce tutoriel (stratégie novice vers expert)

Objectif: ne pas seulement lire la syntaxe, mais savoir l'utiliser dans un vrai projet.

Sortie attendue: à la fin de cette page, tu dois pouvoir coder sans copier-coller systématique.

Prochaine étape logique: passer à la Partie 2 pour rendre ton environnement de travail reproductible.

1. Structures conditionnelles

If / Elif / Else

age = 20

if age >= 18:
    print("Majeur")
elif age >= 16:
    print("Mineur avec permis")
else:
    print("Mineur")

# Expression conditionnelle (ternaire)
statut = "Majeur" if age >= 18 else "Mineur"

# Match-case (Python 3.10+)
match status:
    case 200 | 201:
        print("Success")
    case 404:
        print("Not Found")
    case 500:
        print("Server Error")
    case _:
        print("Unknown")

Opérateurs logiques

# and, or, not
if age > 18 and age < 65:
    print("Actif")

# Walrus operator (Python 3.8+)
if (n := len(data)) > 10:
    print(f"Data has {n} items")

# Truthiness
if user and user.is_active:
    print("User is active")

2. Boucles

For

# Itération simple
for i in range(10):
    print(i)

# Avec start, stop, step
for i in range(0, 10, 2):
    print(i)

# Itération sur liste
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    print(user)

# Avec index
for idx, user in enumerate(users):
    print(f"{idx}: {user}")

# Itération sur dictionnaire
config = {'host': 'localhost', 'port': 8080}
for key, value in config.items():
    print(f"{key}: {value}")

# Unpacking
points = [(1, 2), (3, 4), (5, 6)]
for x, y in points:
    print(f"x={x}, y={y}")

While

count = 0
while count < 10:
    print(count)
    count += 1

# While avec else
while count < 10:
    print(count)
    count += 1
else:
    print("Boucle terminée normalement")

# Break et continue
for i in range(100):
    if i == 10:
        break
    if i % 2 == 0:
        continue
    print(i)

3. Comprehensions

List Comprehension

# Basique
squares = [x**2 for x in range(10)]

# Avec condition
evens = [x for x in range(20) if x % 2 == 0]

# Nested
matrix = [[i*j for j in range(3)] for i in range(3)]

# Multiple conditions
filtered = [x for x in range(100) if x % 2 == 0 if x % 3 == 0]

Dict / Set Comprehension

# Dictionary comprehension
squares_dict = {x: x**2 for x in range(10)}

# Set comprehension
unique_lengths = {len(word) for word in ['hello', 'world', 'test']}

# Generator expression (lazy evaluation)
squares_gen = (x**2 for x in range(1000000))  # N'occupe pas de mémoire

4. Fonctions

Déclaration et types

# Fonction simple
def add(a, b):
    return a + b

# Avec type hints
def divide(a: int, b: int) -> float:
    if b == 0:
        raise ValueError("Division par zéro")
    return a / b

# Paramètres par défaut
def greet(name: str = "Guest") -> str:
    return f"Hello, {name}!"

# Paramètres nommés
def create_user(name: str, email: str, active: bool = True):
    pass

create_user(name="John", email="john@example.com")

# Arguments variables
def sum_all(*args: int) -> int:
    return sum(args)

# Keyword arguments
def config(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

config(host="localhost", port=8080, debug=True)

Lambda et fonctions de haut niveau

# Lambda
multiply = lambda a, b: a * b

# Map, filter, reduce
from functools import reduce

numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
evens = list(filter(lambda x: x % 2 == 0, numbers))
total = reduce(lambda acc, x: acc + x, numbers, 0)

# Sorted avec key
users = [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}]
sorted_users = sorted(users, key=lambda u: u['age'])

Décorateurs

# Décorateur simple
def timer(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f}s")
        return result
    return wrapper

@timer
def slow_function():
    import time
    time.sleep(1)

# Décorateur avec paramètres
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello!")

# Décorateur de classe
from functools import wraps

def singleton(cls):
    instances = {}
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class Database:
    pass

5. Programmation Orientée Objet

Classes et propriétés

# Classe basique
class User:
    # Variable de classe
    count = 0
    
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email
        User.count += 1
    
    # Méthode d'instance
    def greet(self) -> str:
        return f"Hello, {self.name}"
    
    # Méthode de classe
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['name'], data['email'])
    
    # Méthode statique
    @staticmethod
    def is_valid_email(email: str) -> bool:
        return '@' in email
    
    # Property (getter/setter)
    @property
    def display_name(self) -> str:
        return self.name.title()
    
    @display_name.setter
    def display_name(self, value: str):
        self.name = value.lower()
    
    # Représentation
    def __repr__(self) -> str:
        return f"User(name='{self.name}', email='{self.email}')"
    
    def __str__(self) -> str:
        return self.name

Héritage et composition

# Héritage simple
class Admin(User):
    def __init__(self, name: str, email: str, level: int):
        super().__init__(name, email)
        self.level = level
    
    def grant_access(self):
        return f"Admin {self.name} granted access"

# Héritage multiple
class Loggable:
    def log(self, message: str):
        print(f"[LOG] {message}")

class SecureAdmin(Admin, Loggable):
    pass

# Classe abstraite
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass
    
    @abstractmethod
    def perimeter(self) -> float:
        pass

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height
    
    def area(self) -> float:
        return self.width * self.height
    
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

Dataclasses (Python 3.7+)

from dataclasses import dataclass, field
from typing import List

@dataclass
class Product:
    name: str
    price: float
    stock: int = 0
    tags: List[str] = field(default_factory=list)
    
    def __post_init__(self):
        if self.price < 0:
            raise ValueError("Price cannot be negative")
    
    @property
    def total_value(self) -> float:
        return self.price * self.stock

# Utilisation
product = Product("Laptop", 999.99, 10, ["electronics", "computers"])
print(product.total_value)

6. Gestion des erreurs

Try / Except / Finally

# Basique
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Division par zéro!")

# Multiple exceptions
try:
    value = int(input())
    result = 100 / value
except ValueError:
    print("Valeur invalide")
except ZeroDivisionError:
    print("Division par zéro")
except Exception as e:
    print(f"Erreur inattendue: {e}")
finally:
    print("Nettoyage")

# Exception grouping (Python 3.11+)
try:
    risky_operation()
except (ValueError, TypeError) as e:
    handle_error(e)

# Relancer une exception
try:
    operation()
except ValueError:
    log_error()
    raise  # Relance la même exception

Exceptions personnalisées

class DatabaseError(Exception):
    """Exception personnalisée pour erreurs de base de données"""
    def __init__(self, message: str, query: str):
        super().__init__(message)
        self.query = query

class ValidationError(Exception):
    """Exception pour validation de données"""
    def __init__(self, field: str, message: str):
        self.field = field
        super().__init__(f"{field}: {message}")

# Utilisation
try:
    if not email:
        raise ValidationError("email", "Email requis")
except ValidationError as e:
    print(f"Erreur de validation: {e}")

Context Managers

# With statement
with open('file.txt', 'r') as f:
    content = f.read()
# Fichier automatiquement fermé

# Custom context manager
from contextlib import contextmanager

@contextmanager
def database_connection():
    conn = connect_to_db()
    try:
        yield conn
    finally:
        conn.close()

with database_connection() as conn:
    conn.execute("SELECT * FROM users")

# Classe context manager
class Transaction:
    def __enter__(self):
        self.begin()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None:
            self.commit()
        else:
            self.rollback()
        return False  # Propage l'exception

7. Modules et packages

Import

# Import simple
import math
print(math.pi)

# Import avec alias
import numpy as np
arr = np.array([1, 2, 3])

# Import spécifique
from collections import defaultdict, Counter
from pathlib import Path

# Import tout (déconseillé)
from math import *

# Import relatif
from .models import User  # Même package
from ..utils import helper  # Package parent

Structure de package

# mypackage/
#   __init__.py
#   module1.py
#   module2.py
#   subpackage/
#     __init__.py
#     module3.py

# mypackage/__init__.py
from .module1 import function1
from .module2 import Class2

__all__ = ['function1', 'Class2']
__version__ = '1.0.0'

8. Type Hints avancés

from typing import (
    List, Dict, Tuple, Set, Optional, Union,
    Callable, Any, TypeVar, Generic, Protocol
)

# Types de base
def process_items(items: List[str]) -> Dict[str, int]:
    return {item: len(item) for item in items}

# Optional (peut être None)
def find_user(user_id: int) -> Optional[User]:
    return users.get(user_id)

# Union (plusieurs types possibles)
def parse_value(value: Union[int, str]) -> int:
    return int(value)

# Callable
def apply_function(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# Generics
T = TypeVar('T')

def first(items: List[T]) -> Optional[T]:
    return items[0] if items else None

class Stack(Generic[T]):
    def __init__(self):
        self.items: List[T] = []
    
    def push(self, item: T) -> None:
        self.items.append(item)
    
    def pop(self) -> T:
        return self.items.pop()

# Protocol (structural subtyping - Python 3.8+)
class Drawable(Protocol):
    def draw(self) -> None:
        ...

def render(obj: Drawable) -> None:
    obj.draw()

9. Structures de données

Collections

from collections import (
    defaultdict, Counter, deque, 
    namedtuple, OrderedDict, ChainMap
)

# defaultdict
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1

# Counter
counter = Counter(['a', 'b', 'a', 'c', 'b', 'a'])
most_common = counter.most_common(2)  # [('a', 3), ('b', 2)]

# deque (double-ended queue)
queue = deque([1, 2, 3])
queue.append(4)      # Ajouter à droite
queue.appendleft(0)  # Ajouter à gauche
queue.pop()          # Retirer à droite
queue.popleft()      # Retirer à gauche

# namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y)

# ChainMap
user_config = {'theme': 'dark'}
default_config = {'theme': 'light', 'lang': 'en'}
config = ChainMap(user_config, default_config)
print(config['theme'])  # 'dark' (priorité au premier)

10. Programmation fonctionnelle

Itertools

from itertools import (
    chain, combinations, permutations,
    product, groupby, islice, cycle
)

# chain - Concaténer des itérables
combined = chain([1, 2], [3, 4], [5, 6])

# combinations
combos = list(combinations([1, 2, 3], 2))  # [(1,2), (1,3), (2,3)]

# permutations
perms = list(permutations([1, 2, 3]))

# product - Produit cartésien
prod = list(product([1, 2], ['a', 'b']))  # [(1,'a'), (1,'b'), (2,'a'), (2,'b')]

# groupby
data = [('a', 1), ('a', 2), ('b', 3), ('b', 4)]
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))

# islice - Slice d'itérable
first_10 = list(islice(infinite_sequence(), 10))

Functools

from functools import (
    reduce, partial, lru_cache,
    wraps, singledispatch
)

# reduce
total = reduce(lambda acc, x: acc + x, [1, 2, 3, 4], 0)

# partial - Application partielle
def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

# lru_cache - Mémoïsation
@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# singledispatch - Polymorphisme
@singledispatch
def process(arg):
    print(f"Default: {arg}")

@process.register(int)
def _(arg):
    print(f"Integer: {arg * 2}")

@process.register(str)
def _(arg):
    print(f"String: {arg.upper()}")

11. Bonnes pratiques

Idiomes Python (Pythonic)

# EAFP (Easier to Ask for Forgiveness than Permission)
# Plutôt que:
if key in dict:
    value = dict[key]
# Préférer:
try:
    value = dict[key]
except KeyError:
    value = default

# Unpacking
a, b = b, a  # Swap
first, *rest, last = [1, 2, 3, 4, 5]

# Enumerate au lieu de range(len())
for idx, item in enumerate(items):
    print(idx, item)

# Zip pour itérer en parallèle
for name, age in zip(names, ages):
    print(name, age)

# Any / All
if any(x > 10 for x in numbers):
    print("Au moins un nombre > 10")

if all(x > 0 for x in numbers):
    print("Tous les nombres sont positifs")

12. Mini labo syntaxe: progression en 3 niveaux

Ce mini labo transforme la theorie en pratique. Fais les niveaux dans l'ordre.

Niveau 1 (novice): script de filtrage

Objectif: manipuler conditions, boucles et comprehensions dans un meme exercice.

# fichier: mini_labo_n1.py
notes = [12, 7, 18, 9, 15, 20]

admis = [n for n in notes if n >= 10]
for note in admis:
    if note >= 16:
        print(f"{note}: mention bien")
    else:
        print(f"{note}: admis")

Sortie attendue: afficher uniquement les notes >= 10 avec un message adapté.

Niveau 2 (intermédiaire): fonctions + exceptions

Objectif: structurer le code et gérer les erreurs sans planter l'application.

# fichier: mini_labo_n2.py
def to_int(value: str) -> int:
    try:
        return int(value)
    except ValueError:
        return 0

values = ["10", "abc", "42", "-7"]
cleaned = [to_int(v) for v in values]
print(cleaned)

Sortie attendue: [10, 0, 42, -7]

Niveau 3 (avancé): POO + dataclass + tri

Objectif: modéliser des données métier et appliquer des opérations Pythonic.

# fichier: mini_labo_n3.py
from dataclasses import dataclass

@dataclass
class Produit:
    nom: str
    prix: float
    stock: int

produits = [
    Produit("Clavier", 59.9, 12),
    Produit("Souris", 29.9, 0),
    Produit("Ecran", 219.0, 4),
]

disponibles = [p for p in produits if p.stock > 0]
tries = sorted(disponibles, key=lambda p: p.prix)

for p in tries:
    print(f"{p.nom} - {p.prix} EUR ({p.stock} en stock)")

Sortie attendue: la liste des produits disponibles, tries par prix croissant.

Passage expert: transforme ce mini labo en package teste, puis publie-le en suivant les Parties 2 et 3.

← Retour aux tutoriels | Partie 2 : Environnements virtuels → | Partie 3 : Packaging et publication →