← Retour aux tutoriels | Partie 2 : Environnements virtuels Python → | Partie 3 : Packaging et publication →
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.
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")
# 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")
# 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}")
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)
# 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]
# 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
# 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
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é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
# 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 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)
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)
# 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
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}")
# 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
# 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
# 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'
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()
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)
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))
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()}")
with pour les ressourcesexec(), eval()# 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")
Ce mini labo transforme la theorie en pratique. Fais les niveaux dans l'ordre.
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é.
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]
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.