Premiers pas
Hello World & Commentaires
Python utilise # pour les commentaires et les triples guillemets pour les docstrings. La fonction print() prend en charge les paramètres sep et end pour personnaliser le formatage de sortie. Les docstrings servent de documentation accessible via help() et __doc__.
# This is a single-line comment
"""
This is a
multi-line comment (docstring)
"""
print("Hello, World!") # print to stdout
print("A", "B", "C", sep="-") # A-B-C
print("No newline", end="") # suppress newlineIndentation & Blocs de code
Contrairement à la plupart des langages, Python utilise l'indentation plutôt que les accolades pour définir les blocs de code. La cohérence est essentielle — mélanger des tabulations et des espaces provoque une SyntaxError. PEP 8 recommande 4 espaces par niveau.
# Python uses indentation (4 spaces) to define blocks
if True:
print("inside if")
if True:
print("nested block")
print("outside block")
# No braces! Indentation IS the syntax
def func():
x = 1
return x + 1Entrée & Sortie
input() lit depuis stdin sous forme de chaîne — convertissez toujours lorsque vous avez besoin d'un nombre. Utilisez int(), float(), etc. pour la conversion. Les f-strings (Python 3.6+) sont la méthode préférée pour formater les chaînes.
# input() always returns a string
name = input("Enter your name: ")
age = int(input("Enter your age: ")) # convert to int
print(f"Hello {name}, you are {age} years old")
# formatted output
print("Pi is approximately {:.2f}".format(3.14159))
print(f"{1000000:,}") # 1,000,000 with thousands separatorInstructions multiples & Continuation de ligne
Utilisez des points-virgules pour séparer les instructions sur une ligne (rare en Python idiomatique). Les longues lignes peuvent être continuées avec une barre oblique inversée, ou automatiquement à l'intérieur de (), [], {}. Préférez la continuation implicite pour la lisibilité.
# multiple statements on one line (discouraged)
a = 1; b = 2; c = 3
# explicit line continuation
total = 1 + 2 + 3 + \
4 + 5 + 6
# implicit continuation inside brackets
nums = [
1, 2, 3,
4, 5, 6
]
result = (1 + 2
+ 3 + 4)Exécution Python
Les scripts Python s'exécutent avec 'python script.py'. Le REPL permet l'expérimentation interactive. Utilisez toujours python3 sur les systèmes où python pointe vers Python 2. La ligne shebang rend les scripts exécutables sur Unix.
# Run a script
# $ python script.py
# Run interactively (REPL)
# $ python
# >>> 2 + 2
# 4
# Shebang line for Unix scripts
#!/usr/bin/env python3
# Check Python version
import sys
print(sys.version)
print(sys.version_info.major) # 3Variables & Types de données
Variables & Typage dynamique
Python utilise le typage dynamique — les variables peuvent changer de type à l'exécution. Utilisez type() pour vérifier, isinstance() pour vérifier. Python 3.6+ prend en charge les indications de type (name: str = 'Alice') pour le support IDE sans application à l'exécution.
# Python is dynamically typed - no declaration needed
name = "Alice" # str
age = 30 # int
height = 5.7 # float
is_active = True # bool
items = [1, 2, 3] # list
# Type checking
print(type(name)) # <class 'str'>
print(isinstance(age, int)) # True
# Multiple assignment
x, y, z = 1, 2, 3
a = b = 0 # chain assignmentIndications de type (Python 3.6+)
Les indications de type améliorent la lisibilité du code et activent l'autocomplétion IDE et l'analyse statique avec mypy. Elles ne sont PAS appliquées à l'exécution — Python reste dynamiquement typé. Utilisez Optional[X] pour les valeurs qui peuvent être None.
# Variable annotations
name: str = "Alice"
age: int = 30
scores: list[float] = [90.5, 85.0]
# Function annotations
def greet(name: str, times: int = 1) -> str:
return (f"Hi {name}! " * times).strip()
# Optional and Union
from typing import Optional, Union
def find(id: int) -> Optional[str]:
return "Alice" if id == 1 else None
# mypy for static type checking
# $ mypy script.pyConversion de type
Python possède des fonctions de conversion intégrées : int(), float(), str(), bool(), list(), tuple(), set(), dict(). Les valeurs fausses incluent 0, '', [], {}, None, False. int() tronque vers zéro, tandis que round() utilise l'arrondi du banquier.
# String to number
num_str = str(42) # "42"
num = int("42") # 42
float_num = float("3.14") # 3.14
# Number conversions
print(int(3.99)) # 3 (truncates toward zero)
print(int(-3.99)) # -3
print(round(3.14159, 2)) # 3.14
# Boolean conversion
print(bool(0)) # False
print(bool("")) # False
print(bool([])) # False
print(bool("anything")) # True
# Collection conversions
print(list("abc")) # ['a', 'b', 'c']
print(tuple([1, 2, 3])) # (1, 2, 3)
print(set([1, 1, 2])) # {1, 2}Types numériques
Les entiers Python ont une précision arbitraire (pas de débordement). Les flottants sont des doubles IEEE 754 avec les problèmes de précision habituels. Les nombres complexes sont intégrés. Les booléens sont une sous-classe de int (True==1, False==0). Utilisez des tirets bas dans les littéraux numériques pour la lisibilité.
# Integers (arbitrary precision)
big = 10 ** 100 # no overflow
print(type(big)) # <class 'int'>
# Floats (IEEE 754 double)
pi = 3.14159
print(0.1 + 0.2) # 0.30000000000000004
# Complex numbers
z = 3 + 4j
print(z.real, z.imag) # 3.0 4.0
print(abs(z)) # 5.0
# Boolean is subclass of int
print(isinstance(True, int)) # True
print(True + True) # 2
# Underscores in numbers (3.6+)
million = 1_000_000
binary = 0b_1010_1010Constantes & Conventions de nommage
Python n'a pas de mot-clé const — les noms en MAJUSCULES sont des constantes par convention uniquement (rien n'empêche la réaffectation). PEP 8 définit le nommage : snake_case pour les variables/fonctions, PascalCase pour les classes, MAJUSCULES pour les constantes. Un tiret bas de tête signifie 'privé' par convention ; le double tiret bas déclenche la modification de nom.
# Python has no true constants - convention only
MAX_SIZE = 100 # ALL_CAPS for constants
PI = 3.14159
# Naming conventions (PEP 8)
variable_name = "snake_case" # variables, functions
ClassName = "PascalCase" # classes
CONSTANT_VALUE = 100 # constants
_private_var = "underscore prefix" # private (convention)
__name_mangled = "double underscore" # name mangling
# dunder names (reserved)
__name__, __main__, __init__Chaînes
Méthodes de chaîne
Les chaînes sont immuables — les méthodes renvoient de nouvelles chaînes. find() renvoie -1 si non trouvé, tandis que index() lève une ValueError. Utilisez isalpha()/isdigit()/isalnum() pour la validation. La classe str possède plus de 40 méthodes — explorez avec dir(str).
s = "Hello, World"
# Case operations
print(s.upper()) # HELLO, WORLD
print(s.lower()) # hello, world
print(s.title()) # Hello, World
print(s.capitalize()) # Hello, world
print(s.swapcase()) # hELLO, wORLD
# Search & replace
print(s.find("World")) # 7 (index, -1 if not found)
print(s.index("World")) # 7 (raises ValueError if not found)
print(s.replace("o", "0")) # Hell0, W0rld
print(s.count("l")) # 3
# Validation
print("abc".isalpha()) # True
print("123".isdigit()) # True
print(" ".isspace()) # TrueFormatage de chaîne
Les f-strings sont la méthode moderne, la plus rapide et la plus lisible pour formater les chaînes. Elles prennent en charge les spécifications de format après deux-points : :.2f pour 2 décimales, :>10 pour aligner à droite largeur 10, :, pour le séparateur de milliers. Évitez le formatage %- dans le nouveau code.
name = "Alice"
age = 30
# f-strings (Python 3.6+) - PREFERRED
print(f"Hello, {name}! You are {age}.")
print(f"{name.upper()} is {age * 365} days old")
print(f"{3.14159:.2f}") # 3.14
print(f"{42:>10}") # right-align
print(f"{42:<10}") # left-align
print(f"{42:^10}") # center
print(f"{1000000:,}") # 1,000,000
# str.format() method
print("Hello, {}!".format(name))
print("{name} is {age}".format(name="Bob", age=25))
# Old style (avoid in new code)
print("Hello, %s!" % name)Découpage & Indexation
La syntaxe de découpage Python [start:stop:step] est puissante — stop est exclusif. Les indices négatifs comptent depuis la fin. s[::-1] est la méthode idiomatique pour inverser une chaîne. Le découpage est sûr : les indices hors plage renvoient des chaînes vides plutôt que de lever des erreurs.
s = "Hello, World"
# Indexing (0-based, negative from end)
print(s[0]) # H
print(s[-1]) # d
print(s[7]) # W
# Slicing [start:stop:step]
print(s[0:5]) # Hello
print(s[7:]) # World
print(s[:5]) # Hello
print(s[::2]) # HloWrd (every 2nd char)
print(s[::-1]) # dlroW ,olleH (reverse!)
# Length
print(len(s)) # 12
# Slicing never raises IndexError
print(s[100:200]) # '' (empty string)Division & Jointure
split() divise une chaîne en liste, join() combine une liste en chaîne. Utilisez toujours join() pour la concaténation efficace de nombreuses chaînes — l'opérateur + crée des chaînes intermédiaires. partition() divise en exactement 3 parties (avant, séparateur, après).
# Split
csv = "a,b,c,d"
print(csv.split(",")) # ['a', 'b', 'c', 'd']
print(csv.split(",", 2)) # ['a', 'b', 'c,d'] (max 2 splits)
# Splitlines
text = "line1\nline2\nline3"
print(text.splitlines()) # ['line1', 'line2', 'line3']
# Partition (splits on first occurrence)
print("[email protected]".partition("@"))
# ('user', '@', 'domain.com')
# Join
words = ["Hello", "World"]
print(" ".join(words)) # Hello World
print("-".join(["2024", "01", "15"])) # 2024-01-15
print("".join(["a", "b", "c"])) # abc
# String concatenation
s = "Hello" + " " + "World"
parts = ["a"]
parts += "b" # NOT string concat - adds chars to list!Suppression & Remplissage
strip() supprime les espaces de tête/de fin par défaut, ou les caractères spécifiés. zfill() remplit avec des zéros en tête (utile pour les ID). rjust/ljust/center remplissent à une largeur spécifiée avec un caractère de remplissage optionnel.
# Strip whitespace (or specified chars)
s = " hello "
print(s.strip()) # "hello"
print(s.lstrip()) # "hello "
print(s.rstrip()) # " hello"
# Strip specific characters
print("xxxhelloxxx".strip("x")) # hello
# Padding / centering
print("42".zfill(5)) # 00042
print("hi".rjust(10)) # " hi"
print("hi".ljust(10, "-")) # "hi--------"
print("hi".center(10, "*")) # "****hi****"
# expandtabs
print("a\tb".expandtabs(4)) # "a b"Chaînes brutes & Échappements
Les chaînes brutes (r'...') traitent les barres obliques inversées littéralement — essentielles pour les motifs regex et les chemins de fichiers Windows. Les chaînes entre triples guillemets préservent les nouvelles lignes. Les chaînes prennent en charge l'opérateur * pour la répétition et + pour la concaténation.
# Escape sequences
print("Line1\nLine2") # newline
print("Tab\there") # tab
print("Quote: \"hi\"") # escaped quotes
print("Backslash: \\") # literal backslash
# Raw strings (ignore escapes) - great for regex
path = r"C:\Users\name\file.txt"
regex = r"\d{3}-\d{4}"
print(path) # C:\Users\name\file.txt
# Triple-quoted strings
multi = """
Multiple
lines
"""
# String multiplication
print("ab" * 3) # abababNombres & Mathématiques
Opérateurs arithmétiques
Python possède 7 opérateurs arithmétiques. / renvoie toujours un flottant, // est la division entière (arrondit vers moins l'infini). ** est l'exponentiation (pas ^ — c'est XOR). Le résultat de l'opérateur % prend le signe du diviseur, contrairement à C/Java.
# Basic operators
print(7 + 3) # 10 addition
print(7 - 3) # 4 subtraction
print(7 * 3) # 21 multiplication
print(7 / 3) # 2.333... true division (always float)
print(7 // 3) # 2 floor division
print(7 % 3) # 1 modulo (remainder)
print(7 ** 3) # 343 exponentiation
# Floor division with negatives
print(-7 // 3) # -3 (rounds toward negative infinity)
print(-7 % 3) # 2 (result has same sign as divisor)
# Augmented assignment
x = 10
x += 5 # x = x + 5
x **= 2 # x = x ** 2Module math
Le module math fournit des fonctions et constantes mathématiques. Toutes les fonctions trigonométriques utilisent des radians — convertissez avec math.radians()/degrees(). math.gcd() trouve le plus grand commun diviseur. Pour les nombres complexes, utilisez le module cmath.
import math
# Constants
print(math.pi) # 3.141592653589793
print(math.e) # 2.718281828459045
print(math.inf) # inf
print(math.nan) # nan
# Functions
print(math.sqrt(16)) # 4.0
print(math.pow(2, 10)) # 1024.0
print(math.log(100, 10)) # 2.0 (log base 10)
print(math.log(math.e)) # 1.0 (natural log)
print(math.factorial(5)) # 120
print(math.gcd(12, 8)) # 4
# Rounding
print(math.floor(3.7)) # 3
print(math.ceil(3.2)) # 4
print(math.trunc(-3.7)) # -3 (toward zero)
# Trigonometry (radians)
print(math.sin(math.pi / 2)) # 1.0
print(math.degrees(math.pi)) # 180.0Module random
Le module random utilise le PRNG Mersenne Twister — PAS cryptographiquement sûr. Utilisez le module secrets pour la sécurité. random.sample() choisit des éléments uniques, random.choices() autorise les doublons. Définissez une graine pour des résultats reproductibles dans les tests.
import random
# Random integers
print(random.randint(1, 100)) # 1 to 100 inclusive
print(random.randrange(0, 10, 2)) # even number 0,2,4,6,8
# Random floats
print(random.random()) # 0.0 to 1.0
print(random.uniform(1.0, 10.0)) # random float in range
# Choice & sampling
colors = ["red", "green", "blue"]
print(random.choice(colors)) # one random item
print(random.sample(colors, 2)) # 2 unique items
print(random.choices(colors, k=5)) # 5 items (with replacement)
# Shuffle (in-place)
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)
print(nums)
# Reproducible randomness
random.seed(42) # same seed = same sequenceDecimal & Fractions
Utilisez Decimal pour les calculs financiers où les erreurs de précision flottante sont inacceptables (par ex. l'argent). Utilisez Fraction pour l'arithmétique rationnelle exacte. Les deux sont plus lents que float mais évitent les erreurs d'arrondi. Construisez toujours Decimal à partir de chaînes, pas de flottants.
from decimal import Decimal, getcontext
from fractions import Fraction
# Float precision issues
print(0.1 + 0.2) # 0.30000000000000004
# Decimal for exact decimal arithmetic
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b) # 0.3 (exact!)
# Set precision
getcontext().prec = 6
print(Decimal(1) / Decimal(7)) # 0.142857
# Fractions for exact rational arithmetic
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1 + f2) # 1/2
print(float(f1)) # 0.3333...
# Fraction from string
print(Fraction("3/4")) # 3/4Opérateurs bit à bit
Les opérateurs bit à bit manipulent les bits individuels des entiers. Les entiers Python ont une précision arbitraire, donc les décalages fonctionnent différemment des langages à largeur fixe. Utilisations courantes : indicateurs, masques, analyse de protocoles de bas niveau. x & (x-1) == 0 vérifie si x est une puissance de 2.
# Bitwise operators work on integers
a = 0b1010 # 10
b = 0b1100 # 12
print(a & b) # 8 (0b1000) AND
print(a | b) # 14 (0b1110) OR
print(a ^ b) # 6 (0b0110) XOR
print(~a) # -11 (NOT, two's complement)
print(a << 2) # 40 (left shift, multiply by 4)
print(a >> 1) # 5 (right shift, divide by 2)
# Binary representation
print(bin(10)) # 0b1010
print(hex(255)) # 0xff
print(oct(8)) # 0o10
print(int("1010", 2)) # 10 (parse binary)
# Common tricks
print(5 & 1) # 1 (check odd: nonzero = odd)
print(8 & (8-1)) # 0 (check power of 2)Structures de données
Listes
Les listes sont la structure de données la plus polyvalente de Python — ordonnées, mutables et hétérogènes. append() est O(1), insert(0, x) est O(n). Utilisez collections.deque pour des opérations rapides aux deux extrémités. sort() est sur place, sorted() renvoie une nouvelle liste.
# Lists are ordered, mutable sequences
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", True, 3.14]
# Adding elements
nums.append(6) # [1,2,3,4,5,6]
nums.insert(0, 0) # [0,1,2,3,4,5,6]
nums.extend([7, 8]) # extend with another list
# Removing elements
nums.remove(0) # remove by value
popped = nums.pop() # remove & return last
popped = nums.pop(0) # remove & return by index
del nums[0] # delete by index
nums.clear() # remove all
# Slicing (same as strings)
nums = [1, 2, 3, 4, 5]
print(nums[1:3]) # [2, 3]
print(nums[::-1]) # [5, 4, 3, 2, 1] reverse
# Sorting
nums.sort() # in-place sort
nums.sort(reverse=True) # descending
sorted_nums = sorted(nums) # returns new listTuples
Les tuples sont immuables et plus rapides que les listes. Utilisez-les pour les collections fixes, les valeurs de retour multiples et les clés de dictionnaire (les listes ne peuvent pas être des clés). Les tuples nommés fournissent des noms de champs pour la lisibilité. Les tuples à élément unique nécessitent une virgule finale.
# Tuples are ordered, IMMUTABLE sequences
point = (3, 4)
single = (42,) # note the comma for single-element tuple
empty = ()
# Packing & unpacking
coordinates = 10, 20, 30 # packing
x, y, z = coordinates # unpacking
x, y = y, x # swap values!
# Multiple return values
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 1, 4, 1, 5])
# Named tuples (readable)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4
# Tuples are immutable but can contain mutable objects
t = (1, [2, 3])
t[1].append(4) # OK: (1, [2, 3, 4])Dictionnaires
Les dictionnaires sont des tables de hachage — O(1) en moyenne pour recherche/insertion/suppression. Les clés doivent être hachables (immuables). Depuis Python 3.7, les dicts maintiennent l'ordre d'insertion. Utilisez get() pour éviter KeyError. La compréhension de dict crée des dicts élégamment.
# Dicts are key-value mappings (insertion-ordered since 3.7)
user = {"name": "Alice", "age": 30}
# Access
print(user["name"]) # Alice
print(user.get("email")) # None (no KeyError)
print(user.get("email", "N/A")) # N/A (default)
# Add/update
user["email"] = "[email protected]" # add
user["age"] = 31 # update
user.setdefault("role", "user") # set if missing
# Delete
del user["email"]
val = user.pop("age") # remove & return
# user.clear() # remove all
# Iteration
for key in user: # keys
print(key)
for k, v in user.items(): # key-value pairs
print(k, v)
for v in user.values(): # values
print(v)
# Dict comprehension
squares = {x: x**2 for x in range(5)}
# Merge dicts (3.9+)
merged = {"a": 1} | {"b": 2}Ensembles
Les ensembles sont des collections non ordonnées d'éléments uniques et hachables. Ils excellent dans les tests d'appartenance (O(1) contre O(n) pour les listes) et l'algèbre d'ensembles (union, intersection, différence). frozenset est immuable et hachable. L'ordre n'est pas garanti.
# Sets are unordered collections of unique elements
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# Set operations
print(a | b) # union: {1, 2, 3, 4, 5, 6}
print(a & b) # intersection: {3, 4}
print(a - b) # difference: {1, 2}
print(a ^ b) # symmetric difference: {1, 2, 5, 6}
# Methods
a.add(5) # add element
a.discard(10) # remove if present (no error)
a.remove(1) # remove (KeyError if missing)
a.update([6, 7]) # add multiple
# Membership test (O(1) - faster than list)
print(3 in a) # True
# Frozen set (immutable)
fs = frozenset([1, 2, 3])
# Common use: deduplicate
unique = list(set([1, 1, 2, 2, 3])) # [1, 2, 3]Compréhensions
Les compréhensions sont une manière pythonique de créer des collections de façon concise. Les expressions génératrices (parenthèses au lieu de crochets) sont paresseuses — elles produisent des valeurs à la demande, économisant de la mémoire. Préférez les compréhensions à map()/filter() pour la lisibilité.
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(3)]
# Dict comprehension
square_map = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Set comprehension
unique_lens = {len(w) for w in ["a", "ab", "abc", "ab"]}
# Generator expression (lazy, memory-efficient)
gen = (x**2 for x in range(1000000))
print(next(gen)) # 0
print(next(gen)) # 1
total = sum(x**2 for x in range(100)) # no extra list
# Nested comprehension (matrix)
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]Module collections
Le module collections fournit des conteneurs spécialisés. Counter compte les éléments hachables. defaultdict crée automatiquement les clés manquantes. deque offre append/pop en O(1) aux deux extrémités (contre O(n) pour les listes). Ils sont essentiels pour un code propre et efficace.
from collections import Counter, defaultdict, deque, OrderedDict
# Counter - counting
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
cnt = Counter(words)
print(cnt) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(cnt.most_common(2)) # [('apple', 3), ('banana', 2)]
# defaultdict - no KeyError
dd = defaultdict(list)
dd["fruits"].append("apple")
dd["fruits"].append("banana")
# dd["vegs"] automatically creates empty list
# deque - fast double-ended queue
dq = deque([1, 2, 3])
dq.appendleft(0) # [0, 1, 2, 3]
dq.append(4) # [0, 1, 2, 3, 4]
dq.popleft() # 0, deque is now [1, 2, 3, 4]
dq.rotate(1) # rotate right
# OrderedDict (less needed since 3.7, dicts are ordered)
od = OrderedDict([("a", 1), ("b", 2)])Flux de contrôle
If / Elif / Else
Python utilise if/elif/else — notez 'elif' et non 'elseif'. L'indentation définit les blocs. Le ternaire 'x if cond else y' est une expression. Python traite les collections vides, 0, None et False comme faux — utile pour des conditions concises.
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(f"Grade: {grade}") # Grade: B
# Conditional expression (ternary)
status = "pass" if score >= 60 else "fail"
# Truthy/falsy values
# Falsy: False, 0, 0.0, "", [], {}, (), None
# Everything else is truthy
if []: # False
print("never")
if [0]: # True (non-empty list)
print("always")Boucles for & Itération
La boucle for de Python itère sur tout itérable. range() génère des nombres (stop exclusif). enumerate() associe les éléments à des indices. zip() itère plusieurs séquences en parallèle. Utilisez .items() pour itérer les paires clé-valeur d'un dict.
# range(start, stop, step)
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8
print(i)
for i in range(10, 0, -1): # countdown
print(i)
# Iterate over collections
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# enumerate for index + value
for idx, fruit in enumerate(fruits):
print(f"{idx}: {fruit}")
# zip to iterate multiple sequences
names = ["Alice", "Bob"]
ages = [30, 25]
for name, age in zip(names, ages):
print(f"{name}: {age}")
# Iterate dict
user = {"name": "Alice", "age": 30}
for key, value in user.items():
print(f"{key} = {value}")Boucles while & Break/Continue
Les boucles while répètent tant qu'une condition est vraie. break sort de la boucle immédiatement, continue passe à l'itération suivante. La construction for/else exécute le bloc else uniquement si la boucle n'a pas été interrompue. pass est un placeholder no-op pour les blocs vides.
# Basic while
count = 0
while count < 5:
print(count)
count += 1
# break - exit loop
while True:
cmd = input("> ")
if cmd == "quit":
break
print(f"You said: {cmd}")
# continue - skip to next iteration
for i in range(10):
if i % 2 == 0:
continue # skip even numbers
print(i) # prints 1, 3, 5, 7, 9
# else clause (runs if no break)
for i in range(5):
if i == 10:
break
else:
print("Loop completed without break")
# pass - do nothing (placeholder)
for i in range(5):
pass # TODO: implementInstruction match (Python 3.10+)
L'instruction match (Python 3.10+) est un filtrage par motif structurel puissant, bien au-delà du switch de C. Elle peut filtrer des séquences, des mappings, des instances de classe et lier des variables. Le motif _ est un joker (par défaut). Les gardes avec 'if' ajoutent des conditions.
# Structural pattern matching (like switch)
def handle_command(cmd):
match cmd.split():
case ["quit"]:
return "Goodbye"
case ["hello", name]:
return f"Hello, {name}!"
case ["move", direction] if direction in "NSEW":
return f"Moving {direction}"
case ["add", x, y]:
return int(x) + int(y)
case _:
return "Unknown command"
print(handle_command("hello Alice")) # Hello, Alice!
# Matching data structures
match point:
case (0, 0):
print("origin")
case (0, y):
print(f"on y-axis at {y}")
case (x, 0):
print(f"on x-axis at {x}")
case (x, y):
print(f"at ({x}, {y})")Itérateurs & Générateurs
Les générateurs produisent des valeurs paresseusement avec yield — ils ne calculent pas toutes les valeurs à l'avance, économisant de la mémoire. Ils implémentent le protocole itérateur (iter() et next()). Une fois épuisés, ils sont terminés. Utilisez les générateurs pour les séquences grandes/infinies, les pipelines et les flux de données.
# Iterator protocol
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) # StopIteration
# Generator function (uses yield)
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
for num in count_up_to(5):
print(num) # 1, 2, 3, 4, 5
# Infinite generator
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
print(next(fib)) # 0
print(next(fib)) # 1
print(next(fib)) # 1
print(next(fib)) # 2
# Generator expression
squares = (x**2 for x in range(10))Fonctions
Définir & Appeler des fonctions
Les fonctions sont définies avec def. Les arguments par défaut utilisent =. Python prend en charge les arguments nommés pour la clarté. Les fonctions peuvent renvoyer plusieurs valeurs (sous forme de tuple). Les docstrings (triples guillemets) documentent les fonctions et sont accessibles via help().
# Basic function
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Hello, Alice!
# Default arguments
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Bob")) # Hello, Bob!
print(greet("Bob", "Hi")) # Hi, Bob!
# Keyword arguments
print(greet(name="Carol", greeting="Hey"))
# Return multiple values (tuple)
def stats(nums):
return min(nums), max(nums), sum(nums) / len(nums)
lo, hi, avg = stats([1, 2, 3, 4, 5])
# Docstrings
def add(a, b):
"""Add two numbers and return the result.
Args:
a: First number
b: Second number
Returns:
Sum of a and b
"""
return a + bArguments : *args & **kwargs
*args collecte les arguments positionnels supplémentaires dans un tuple, **kwargs collecte les arguments nommés supplémentaires dans un dict. Les noms args/kwargs sont conventionnels. Vous pouvez déballer des séquences avec * et des dicts avec ** lors de l'appel de fonctions. Ordre : positionnels, *args, nommés, **kwargs.
# *args - variable positional arguments (tuple)
def sum_all(*args):
return sum(args)
print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
# **kwargs - variable keyword arguments (dict)
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, role="admin")
# Combining all
def func(a, b, *args, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"kwargs={kwargs}")
func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2, args=(3, 4), kwargs={'x': 5, 'y': 6}
# Unpacking arguments
nums = [1, 2, 3]
print(sum_all(*nums)) # unpack list as args
opts = {"name": "Alice", "age": 30}
print_info(**opts) # unpack dict as kwargsLambda & Fonctions d'ordre supérieur
Les lambdas sont limités à une seule expression — utilisez def pour une logique complexe. Ils brillent comme arguments de fonctions d'ordre supérieur comme sorted(), map(), filter(). Cependant, les compréhensions de liste sont souvent plus lisibles que map/filter. reduce() se trouve dans functools.
# Lambda - anonymous function (single expression)
square = lambda x: x ** 2
print(square(5)) # 25
# Common with sorted, map, filter, reduce
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]
# Sort by score (key function)
sorted_by_score = sorted(students, key=lambda s: s[1])
# [('Carol', 78), ('Alice', 85), ('Bob', 92)]
# map - apply function to each item
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
# [2, 4, 6, 8, 10]
# filter - keep items where function returns True
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]
# reduce - accumulate to single value
from functools import reduce
product = reduce(lambda a, b: a * b, nums)
# 120 (1*2*3*4*5)
# Prefer comprehensions over map/filter
doubled = [x * 2 for x in nums] # more Pythonic
evens = [x for x in nums if x % 2 == 0]Décorateurs
Les décorateurs enveloppent des fonctions pour ajouter du comportement sans modifier le code original. La syntaxe @ est du sucre syntaxique. Les décorateurs avec arguments nécessitent un niveau d'imbrication supplémentaire. Utilisez toujours functools.wraps pour préserver les métadonnées de la fonction originale (nom, docstring).
# A decorator modifies a function's behavior
def uppercase_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@uppercase_result
def greet(name):
return f"hello, {name}"
print(greet("alice")) # HELLO, ALICE
# Decorator with arguments
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_hi():
print("Hi!")
say_hi() # prints "Hi!" three times
# Practical: timing decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time() - start:.4f}s")
return result
return wrapper
# Use functools.wraps to preserve metadata
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapperPortée & Fermetures
Python résout les noms en utilisant l'ordre de portée LEGB : Local, Englobante, Globale, Intégrée. Utilisez 'global' pour rebinder une variable globale dans une fonction. Utilisez 'nonlocal' (Python 3) pour modifier une variable dans une portée englobante. Les fermetures se souviennent de leur portée englobante.
# LEGB rule: Local, Enclosing, Global, Built-in
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
# global keyword - modify global variable
count = 0
def increment():
global count
count += 1
# nonlocal keyword - modify enclosing variable
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3POO & Classes
Classes & Objets
Les classes regroupent des données (attributs) et du comportement (méthodes). __init__ est le constructeur. self fait référence à l'instance (comme 'this' dans d'autres langages). Les variables de classe sont partagées ; les variables d'instance sont par objet. __str__ est pour les utilisateurs, __repr__ est pour les développeurs.
class Dog:
# Class variable (shared by all instances)
species = "Canis familiaris"
# Constructor
def __init__(self, name, age):
# Instance variables
self.name = name
self.age = age
# Instance method
def bark(self):
return f"{self.name} says Woof!"
# String representation
def __str__(self):
return f"Dog({self.name}, {self.age})"
# Official representation (for debugging)
def __repr__(self):
return f"Dog(name='{self.name}', age={self.age})"
# Create instances
buddy = Dog("Buddy", 3)
lucy = Dog("Lucy", 5)
print(buddy.bark()) # Buddy says Woof!
print(buddy.name) # Buddy
print(buddy.species) # Canis familiaris
print(str(buddy)) # Dog(Buddy, 3)Héritage & Polymorphisme
L'héritage permet aux classes de réutiliser et d'étendre le comportement. Python prend en charge l'héritage multiple avec MRO (Method Resolution Order) pour résoudre les conflits. Le polymorphisme permet de traiter différents types uniformément. Utilisez isinstance() pour la vérification de type, pas type().
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement")
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# Polymorphism - same interface, different behavior
def animal_sound(animal):
print(animal.speak())
animals = [Dog("Buddy"), Cat("Whiskers")]
for a in animals:
animal_sound(a)
# Buddy says Woof!
# Whiskers says Meow!
# Multiple inheritance
class Swimmer:
def swim(self):
return "swimming"
class Flyer:
def fly(self):
return "flying"
class Duck(Animal, Swimmer, Flyer):
pass
duck = Duck("Donald")
print(duck.swim()) # swimming
print(duck.fly()) # flying
# Check inheritance
print(isinstance(duck, Animal)) # True
print(issubclass(Dog, Animal)) # TruePropriétés & Encapsulation
Python n'a pas de véritable privé/protégé — il utilise des conventions. Un seul tiret bas _ signifie 'interne'. Le double tiret bas __ déclenche la modification de nom (pas une véritable confidentialité). @property transforme les méthodes en attributs avec getters/setters, permettant la validation et les propriétés calculées.
class Temperature:
def __init__(self, celsius=0):
self.celsius = celsius # uses setter below
# Getter
@property
def celsius(self):
return self._celsius
# Setter
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
# Computed property
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
temp = Temperature(25)
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 100
print(temp.celsius) # 37.78...
# Name conventions:
# _name - protected (convention, not enforced)
# __name - private (name mangling: _ClassName__name)
# __name__ - dunder (reserved by Python)Méthodes de classe & statiques
@staticmethod est juste une fonction dans l'espace de noms d'une classe — aucun premier argument implicite. @classmethod reçoit la classe (cls) comme premier argument, utile pour les constructeurs alternatifs (méthodes de fabrique) et le comportement sensible à l'héritage. Utilisez classmethod pour les constructeurs, staticmethod pour les fonctions utilitaires.
class MathUtils:
pi = 3.14159
# Static method - no self/cls, lives in class namespace
@staticmethod
def add(a, b):
return a + b
# Class method - receives the class as first argument
@classmethod
def circle_area(cls, radius):
return cls.pi * radius ** 2
# Alternative constructor (common classmethod use)
@classmethod
def from_diameter(cls, diameter):
return cls() # would configure instance
# Static: called on class or instance, no special first arg
print(MathUtils.add(2, 3)) # 5
# Class: often used for alternative constructors
print(MathUtils.circle_area(5)) # 78.54...
# Factory pattern with classmethod
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def origin(cls):
return cls(0, 0)
@classmethod
def from_tuple(cls, coords):
return cls(*coords)
p1 = Point.origin()
p2 = Point.from_tuple((3, 4))Méthodes magiques (Dunder)
Les méthodes magiques (méthodes dunder) implémentent la surcharge d'opérateurs et le comportement de protocole. __add__ pour +, __eq__ pour ==, __len__ pour len(), __iter__ pour l'itération/le déballage. Elles permettent à vos objets de fonctionner naturellement avec la syntaxe et les fonctions intégrées de Python.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# String representation
def __str__(self):
return f"Vector({self.x}, {self.y})"
# Operator overloading
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# Equality
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Length
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
# Make it iterable
def __iter__(self):
yield self.x
yield self.y
# Index access
def __getitem__(self, index):
return (self.x, self.y)[index]
v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2) # Vector(6, 8)
print(v1 * 3) # Vector(6, 9)
print(v1 == Vector(2, 3)) # True
print(len(v1)) # 3
x, y = v1 # unpacking via __iter__Gestion des erreurs
Try / Except / Finally
try/except/else/finally : try exécute le code risqué, except capture les erreurs, else s'exécute si aucune exception, finally s'exécute toujours (nettoyage). Capturez des exceptions spécifiques, pas un 'except:' nu. Le bloc else est utile lorsque le nettoyage ne doit se produire qu'en cas de succès. Exception est la base de la plupart des erreurs capturables.
# Basic exception handling
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}") # division by zero
finally:
print("This always runs")
# Multiple exception types
try:
value = int("abc")
except (ValueError, TypeError) as e:
print(f"Conversion error: {e}")
# Different handlers for different exceptions
try:
f = open("nonexistent.txt")
data = f.read()
except FileNotFoundError:
print("File not found")
except PermissionError:
print("No permission")
except Exception as e:
print(f"Unexpected: {e}")
else:
print("No exception occurred")
f.close()
finally:
print("Cleanup (always runs)")
# Exception hierarchy
# BaseException
# ├── SystemExit
# ├── KeyboardInterrupt
# └── Exception
# ├── ValueError
# ├── TypeError
# ├── KeyError
# └── ...Lever des exceptions
Utilisez raise pour lancer des exceptions. 'raise' seul relève l'exception courante (dans un bloc except). 'raise X from Y' chaîne les exceptions, préservant la cause originale. Levez toujours des types d'exception spécifiques. Évitez d'utiliser des exceptions pour le flux de contrôle normal.
# Raise an exception
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero!")
return a / b
# Re-raise the current exception
def process(data):
try:
return parse(data)
except ValueError:
print("Logging parse error...")
raise # re-raises the same exception
# Raise with context (from)
try:
int("abc")
except ValueError as e:
raise RuntimeError("Failed to process input") from e
# Common built-in exceptions
raise ValueError("invalid value")
raise TypeError("wrong type")
raise KeyError("missing key")
raise IndexError("out of range")
raise RuntimeError("something went wrong")
raise NotImplementedError("override this")
raise FileNotFoundError("no such file")
# Exception with custom args
class ValidationError(Exception):
pass
raise ValidationError("field is required", "email")Exceptions personnalisées
Créez des exceptions personnalisées en sous-classant Exception (ou un intégré plus spécifique). Concevez une hiérarchie pour que les appelants puissent capturer au bon niveau. Ajoutez des attributs personnalisés pour transporter du contexte. Héritez d'Exception, pas de BaseException (qui inclut SystemExit/KeyboardInterrupt).
# Custom exception hierarchy
class AppError(Exception):
"""Base exception for the application."""
pass
class DatabaseError(AppError):
def __init__(self, message, query=None):
super().__init__(message)
self.query = query
class ValidationError(AppError):
def __init__(self, field, message):
super().__init__(f"{field}: {message}")
self.field = field
self.message = message
class AuthenticationError(AppError):
pass
# Usage
def login(username, password):
if not username:
raise ValidationError("username", "is required")
if password != "secret":
raise AuthenticationError("Invalid credentials")
# Catching by hierarchy
try:
login("", "x")
except ValidationError as e:
print(f"Validation failed: {e.field}")
except AppError as e:
print(f"App error: {e}")
# Access exception info
import traceback
try:
1 / 0
except:
traceback.print_exc()
print(repr(sys.exc_info()[1]))Gestionnaires de contexte (instruction with)
Les gestionnaires de contexte (instruction 'with') garantissent le nettoyage via __enter__ et __exit__. Ils sont essentiels pour les ressources comme les fichiers, les verrous et les connexions de base de données. contextlib.contextmanager simplifie la création en utilisant un générateur. __exit__ peut supprimer les exceptions en renvoyant True.
# Context managers handle setup and cleanup
with open("file.txt") as f:
content = f.read()
# file is automatically closed, even if an error occurs
# Multiple context managers
with open("input.txt") as fin, open("output.txt", "w") as fout:
fout.write(fin.read())
# Creating a context manager (class-based)
class Timer:
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.elapsed = time.time() - self.start
print(f"Elapsed: {self.elapsed:.4f}s")
return False # don't suppress exceptions
with Timer() as t:
# code to time
sum(range(1000000))
# contextlib for simpler context managers
from contextlib import contextmanager
@contextmanager
def open_db(url):
db = connect(url)
try:
yield db
finally:
db.close()
with open_db("localhost") as db:
db.query("SELECT 1")Assertions & Journalisation
Les instructions assert servent à déboguer les invariants — elles sont supprimées lorsque Python s'exécute avec -O (optimisation). N'utilisez jamais d'assertions pour la validation d'entrée. Utilisez le module logging au lieu de print() pour le code de production — il prend en charge les niveaux, le formatage et les destinations de sortie.
# Assertions - for debugging (removed with -O flag)
def divide(a, b):
assert b != 0, "Divisor cannot be zero"
return a / b
# Never use assertions for data validation (they can be disabled)
# Use them for invariant checks during development
# Logging (better than print for production)
import logging
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(message)s"
)
logger = logging.getLogger(__name__)
logger.debug("Detailed info for debugging")
logger.info("Confirmation things are working")
logger.warning("Something unexpected happened")
logger.error("A serious problem")
logger.critical("A fatal error")
# Log exceptions with traceback
try:
1 / 0
except:
logger.exception("Failed to divide") # includes tracebackE/S de fichiers
Lire des fichiers
Utilisez toujours 'with' pour ouvrir des fichiers — il les ferme automatiquement même si une erreur se produit. Spécifiez encoding='utf-8' pour éviter les problèmes d'encodage dépendants de la plateforme. Pour les gros fichiers, itérez ligne par ligne au lieu de read() pour économiser de la mémoire. readlines() charge tout le fichier en mémoire.
# Read entire file
with open("file.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
# Read line by line (memory-efficient for large files)
with open("file.txt", "r") as f:
for line in f:
print(line.strip()) # strip removes trailing newline
# Read all lines into a list
with open("file.txt") as f:
lines = f.readlines() # ['line1\n', 'line2\n', ...]
# Read specific number of characters
with open("file.txt") as f:
chunk = f.read(100) # first 100 chars
# File modes:
# "r" read (default)
# "w" write (truncate)
# "a" append
# "x" exclusive create (fails if exists)
# "b" binary mode (e.g., "rb", "wb")
# "+" read and write (e.g., "r+")Écrire des fichiers
Le mode 'w' tronque le fichier (supprime le contenu) ; utilisez 'a' pour ajouter. writelines() n'ajoute pas de nouvelles lignes — ajoutez-les manuellement. Utilisez 'rb'/'wb' pour les fichiers binaires (images, etc.). seek() déplace le curseur ; tell() renvoie sa position. Spécifiez toujours l'encodage pour les fichiers texte.
# Write text (overwrites existing)
with open("output.txt", "w") as f:
f.write("First line\n")
f.write("Second line\n")
# writelines doesn't add newlines
f.writelines(["line3\n", "line4\n"])
# Append to a file
with open("log.txt", "a") as f:
f.write("New log entry\n")
# Write binary data
with open("data.bin", "wb") as f:
f.write(b"\x00\x01\x02\x03")
# Read and write simultaneously
with open("file.txt", "r+") as f:
content = f.read()
f.seek(0) # move to beginning
f.write("Updated") # overwrite
f.truncate() # cut off remaining
# Check if file exists
import os
if os.path.exists("file.txt"):
print("File exists")Gestion des chemins (pathlib)
pathlib (Python 3.4+) est la méthode moderne et orientée objet pour gérer les chemins — préférez-le à os.path. L'opérateur / joint les chemins de manière indépendante de la plateforme. Les objets Path ont des méthodes read_text()/write_text() qui gèrent l'ouverture/fermeture pour vous. rglob() recherche récursivement.
from pathlib import Path
# Create Path objects (preferred over os.path)
p = Path("src/main.py")
home = Path.home() # /home/user or C:\Users\user
cwd = Path.cwd() # current working directory
# Path components
print(p.name) # main.py
print(p.stem) # main
print(p.suffix) # .py
print(p.parent) # src
print(p.parts) # ('src', 'main.py')
# Joining paths (use / operator)
config = home / ".config" / "app" / "config.json"
# Existence and type
print(p.exists()) # True/False
print(p.is_file())
print(p.is_dir())
# Listing directories
for f in Path(".").iterdir():
print(f)
# Glob patterns
for py_file in Path(".").rglob("*.py"):
print(py_file)
# Create directories
Path("new/dir").mkdir(parents=True, exist_ok=True)
# Read/write (Path methods)
content = Path("file.txt").read_text()
Path("output.txt").write_text("Hello!")JSON
json.dumps() sérialise en chaîne, json.loads() désérialise. dump()/load() travaillent avec des fichiers. Utilisez indent pour un affichage joli. Les objets personnalisés nécessitent un sérialiseur par défaut. JSON ne prend en charge que les types de base — utilisez default= pour datetime et autres objets complexes.
import json
# Python dict to JSON string
data = {"name": "Alice", "age": 30, "skills": ["Python", "SQL"]}
json_str = json.dumps(data, indent=2)
print(json_str)
# JSON string to Python dict
parsed = json.loads('{"name": "Bob", "active": true}')
print(parsed["name"]) # Bob
print(parsed["active"]) # True (Python bool)
# Write JSON to file
with open("data.json", "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Read JSON from file
with open("data.json") as f:
loaded = json.load(f)
# Custom serialization (e.g., datetime)
from datetime import datetime
def json_default(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError
json.dumps({"time": datetime.now()}, default=json_default)
# Type mapping:
# JSON object <-> Python dict
# JSON array <-> Python list
# JSON string <-> Python str
# JSON number <-> Python int/float
# JSON boolean <-> Python bool
# JSON null <-> Python NoneCSV & Autres formats
Le module csv gère CSV avec des guillemets corrects. Utilisez newline='' lors de l'ouverture de fichiers CSV sur Windows. DictReader/DictWriter travaillent avec des noms de colonnes. pickle peut sérialiser tout objet Python mais est spécifique à Python et non sécurisé — ne dépicklez jamais de données provenant de sources non fiables.
import csv
# Write CSV
with open("data.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["name", "age", "city"])
writer.writerows([
["Alice", 30, "NYC"],
["Bob", 25, "LA"],
])
# Read CSV
with open("data.csv") as f:
reader = csv.reader(f)
header = next(reader) # first row
for row in reader:
print(row) # ['Alice', '30', 'NYC']
# DictReader/DictWriter (column access by name)
with open("data.csv") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"])
# Pickle (Python-specific, can store any object)
import pickle
with open("data.pkl", "wb") as f:
pickle.dump({"complex": [1, 2, {"a": 3}]}, f)
with open("data.pkl", "rb") as f:
obj = pickle.load(f)
# WARNING: pickle is insecure - never unpickle untrusted data!Modules & Packages
Importer des modules
Les imports apportent des modules. 'import X' garde l'espace de noms propre. 'from X import Y' est pratique mais peut causer des collisions de noms. Les alias (import X as Y) sont courants pour les bibliothèques avec des conventions (np, pd). Évitez 'from X import *' — cela pollue l'espace de noms.
# Import entire module
import math
print(math.sqrt(16))
# Import specific names
from datetime import datetime, timedelta
now = datetime.now()
# Import with alias
import numpy as np
import pandas as pd
# Import all names (discouraged - pollutes namespace)
# from os import *
# Conditional import (try/except)
try:
import cjson as json
except ImportError:
import json
# Check what's in a module
import os
print(dir(os)) # list all attributes
print(os.__file__) # module location
print(os.__name__) # module name
# Reload a module (during development)
import importlib
importlib.reload(my_module)Créer des modules & packages
Un module est un fichier .py ; un package est un répertoire avec __init__.py. Le fichier __init__.py peut être vide ou configurer le package. __all__ dans __init__.py contrôle ce que 'from package import *' exporte. Python moderne (3.3+) prend en charge les packages d'espace de noms sans __init__.py.
# A module is just a .py file
# mymath.py
def add(a, b):
return a + b
PI = 3.14159
# A package is a directory with __init__.py
# mypackage/
# __init__.py
# module1.py
# module2.py
# subpackage/
# __init__.py
# module3.py
# __init__.py can be empty or contain package initialization
# mypackage/__init__.py
from .module1 import ClassA
from .module2 import func_b
__version__ = "1.0.0"
__all__ = ["ClassA", "func_b"]
# Using the package
from mypackage import ClassA
from mypackage.subpackage import module3
# __all__ controls 'from package import *'
# Without __all__, * imports only what's in __init__.py__name__ == '__main__'
L'idiome if __name__ == '__main__' permet à un fichier de servir à la fois de script et de module. Lorsqu'il est exécuté directement, __name__ est '__main__' ; lorsqu'il est importé, c'est le nom du module. Ce motif est essentiel pour créer des modules réutilisables qui peuvent aussi être exécutés seuls.
# script.py
def main():
print("Running main")
def helper():
print("Helper function")
if __name__ == "__main__":
# This code only runs when the file is executed directly
# NOT when imported as a module
main()
# When you run: python script.py
# __name__ is "__main__" -> main() runs
# When you: import script
# __name__ is "script" -> main() does NOT run
# This lets the module be both a script and an importable library
# Common pattern for CLI tools
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--name", required=True)
args = parser.parse_args()
print(f"Hello, {args.name}!")
if __name__ == "__main__":
main()Points forts de la bibliothèque standard
La bibliothèque standard de Python est énorme et complète. os/sys pour l'interaction système, datetime pour les dates, collections pour les conteneurs spécialisés, itertools pour les outils d'itérateur, functools pour la programmation fonctionnelle. Explorez la documentation sur docs.python.org/3/library/.
# os - operating system interface
import os
os.getcwd() # current directory
os.listdir(".") # list files
os.environ.get("HOME") # environment variables
# sys - system-specific
import sys
sys.argv # command-line arguments
sys.exit(0) # exit with status code
sys.path # module search path
# datetime - date and time
from datetime import datetime, timedelta
now = datetime.now()
# collections - specialized containers
from collections import Counter, defaultdict, deque
# itertools - iterator tools
from itertools import chain, cycle, repeat, product
# functools - higher-order functions
from functools import lru_cache, reduce, partial
# typing - type hints
from typing import List, Dict, Optional, Union, Any
# pathlib - path handling
from pathlib import Path
# subprocess - run external commands
import subprocess
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)Pip & Environnements virtuels
Utilisez toujours des environnements virtuels pour isoler les dépendances du projet. venv est intégré ; les alternatives incluent virtualenv, conda et uv. Épinglez les versions dans requirements.txt pour la reproductibilité. N'installez jamais de packages globalement avec --user ou en tant que root — utilisez un venv.
# Create a virtual environment
# $ python -m venv venv
# Activate it
# Windows: venv\Scripts\activate
# Unix: source venv/bin/activate
# Install packages
# $ pip install requests
# $ pip install requests==2.28.0
# $ pip install "requests>=2.25,<3.0"
# Install from requirements file
# $ pip install -r requirements.txt
# requirements.txt example:
# requests==2.31.0
# numpy>=1.21.0
# pandas~=2.0.0 # compatible release
# List installed packages
# $ pip list
# $ pip freeze > requirements.txt
# Uninstall
# $ pip uninstall requests
# Show package info
# $ pip show requests
# Modern alternative: uv (faster)
# $ uv pip install requestsDate & Heure
Module datetime
Le module datetime fournit les classes date, time, datetime et timedelta. datetime.now() renvoie l'heure locale ; utilisez datetime.now(timezone.utc) pour UTC. weekday() renvoie 0-6 (Lun-Dim). Utilisez toujours des datetimes conscients du fuseau horaire en production pour éviter l'ambiguïté.
from datetime import datetime, date, time, timedelta
# Current date and time
now = datetime.now() # local time
utc_now = datetime.utcnow() # UTC (deprecated in 3.12)
utc = datetime.now(timezone.utc) # preferred
# Current date
today = date.today()
# Create specific date/time
dt = datetime(2024, 1, 15, 10, 30, 0)
d = date(2024, 1, 15)
t = time(10, 30, 0)
# Access components
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
print(now.weekday()) # 0=Monday, 6=Sunday
# From timestamp
ts = 1705315200
dt = datetime.fromtimestamp(ts)
# To timestamp
print(datetime.now().timestamp())Formatage & Analyse
strftime (string format time) convertit datetime en chaîne ; strptime (string parse time) convertit une chaîne en datetime. Le format ISO 8601 (isoformat/fromisoformat) est le meilleur choix pour stocker les dates — il est non ambigu et triable. Mémorisez les codes courants : %Y %m %d %H %M %S.
from datetime import datetime
# Format datetime to string (strftime)
dt = datetime(2024, 1, 15, 10, 30)
print(dt.strftime("%Y-%m-%d")) # 2024-01-15
print(dt.strftime("%Y/%m/%d %H:%M")) # 2024/01/15 10:30
print(dt.strftime("%B %d, %Y")) # January 15, 2024
print(dt.strftime("%A")) # Monday
# Parse string to datetime (strptime)
dt = datetime.strptime("2024-01-15", "%Y-%m-%d")
dt = datetime.strptime("15/01/2024 10:30", "%d/%m/%Y %H:%M")
# Common format codes:
# %Y year (2024) %m month (01)
# %d day (15) %H hour (14)
# %M minute (30) %S second (00)
# %B month name %b month abbrev
# %A weekday name %a weekday abbrev
# %I 12-hour %p AM/PM
# %j day of year %U week number
# ISO format (recommended for storage)
iso = dt.isoformat() # "2024-01-15T10:30:00"
dt = datetime.fromisoformat("2024-01-15T10:30:00")timedelta & Arithmétique
timedelta représente une durée. Vous pouvez ajouter/soustraire des timedeltas à des datetimes et soustraire deux datetimes pour obtenir un timedelta. timedelta normalise : days=1, hours=25 devient days=2, hours=1. total_seconds() donne la durée entière en secondes.
from datetime import datetime, timedelta
now = datetime.now()
# Add/subtract time
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
in_2_hours = now + timedelta(hours=2)
in_90_days = now + timedelta(days=90)
# Difference between dates
date1 = datetime(2024, 1, 15)
date2 = datetime(2024, 6, 18)
diff = date2 - date1
print(diff.days) # 155
print(diff.total_seconds())
# timedelta components
td = timedelta(days=5, hours=3, minutes=30)
print(td.days) # 5
print(td.seconds) # 12600 (3h 30m in seconds)
print(td.total_seconds())
# Comparisons
if now > date1:
print("now is later")
# Business day calculation (using numpy)
# import numpy as np
# business_days = np.busday_count(date1.date(), date2.date())Fuseaux horaires
Utilisez toujours des datetimes conscients du fuseau horaire (Python 3.9+ ZoneInfo est préféré à pytz). Stockez les dates en UTC et convertissez en heure locale uniquement pour l'affichage. Les datetimes naïfs (sans tzinfo) causent des bugs subtils. ZoneInfo utilise la base de données de fuseaux horaires IANA, gérant l'heure d'été automatiquement.
from datetime import datetime, timezone, timedelta
# Timezone-aware datetime
utc_time = datetime.now(timezone.utc)
print(utc_time) # 2024-01-15 10:30:00+00:00
# Create a timezone (offset-based)
tz_ny = timezone(timedelta(hours=-5), "EST")
ny_time = datetime.now(tz_ny)
# Convert between timezones
utc_time = datetime.now(timezone.utc)
ny_time = utc_time.astimezone(timezone(timedelta(hours=-5)))
tokyo_time = utc_time.astimezone(timezone(timedelta(hours=9)))
# Use zoneinfo (Python 3.9+) for IANA timezones
from zoneinfo import ZoneInfo
tz = ZoneInfo("America/New_York")
dt = datetime.now(tz)
print(dt.tzname()) # EST or EDT
# Common IANA timezones:
# "UTC"
# "America/New_York", "America/Los_Angeles"
# "Europe/London", "Europe/Paris"
# "Asia/Tokyo", "Asia/Shanghai"
# "Australia/Sydney"
# Best practice: store UTC, convert for display
utc_stored = datetime.now(timezone.utc)
local_display = utc_stored.astimezone(ZoneInfo("Asia/Shanghai"))Expressions régulières
Bases du module re
re.search() trouve la première correspondance n'importe où ; re.match() seulement au début ; re.fullmatch() exige que toute la chaîne corresponde. Utilisez des chaînes brutes (r'...') pour les motifs afin d'éviter les problèmes d'échappement de barre oblique inverse. Les objets Match fournissent group(), start(), end() et span().
import re
# re.search - find first match anywhere in string
m = re.search(r"\d{4}", "Order #2024 was placed")
if m:
print(m.group()) # 2024
print(m.start(), m.end()) # 9 13
# re.match - match at beginning of string
m = re.match(r"Hello", "Hello, World")
print(m.group()) # Hello
# re.fullmatch - entire string must match
m = re.fullmatch(r"\d+", "12345")
print(bool(m)) # True
# re.findall - all matches as list
emails = re.findall(r"\S+@\S+", text)
numbers = re.findall(r"\d+", "a1b22c333") # ['1', '22', '333']
# re.finditer - all matches as iterator (with positions)
for m in re.finditer(r"\w+", "Hello World"):
print(m.group(), m.span())
# Match object methods
m = re.search(r"(\w+)@(\w+)", "[email protected]")
print(m.group()) # user@domain (whole match)
print(m.group(1)) # user (first group)
print(m.group(2)) # domain (second group)
print(m.groups()) # ('user', 'domain')Syntaxe des motifs
Syntaxe regex : [] pour les classes de caractères, \d \w \s pour les ensembles courants, quantificateurs (* + ? {}) pour la répétition, ^ $ \b pour les ancres, () pour les groupes, | pour l'alternance. Utilisez des chaînes brutes (r'...') pour que les barres obliques inverses soient littérales. Les quantificateurs gourmands correspondent au maximum ; ajoutez ? pour paresseux (ex. *?).
import re
# Character classes
re.findall(r"[aeiou]", "hello") # vowels
re.findall(r"[^aeiou]", "hello") # non-vowels
re.findall(r"[a-z]", "Hello123") # lowercase
re.findall(r"[A-Za-z0-9]", "Hi-1!") # alphanumeric
# Predefined classes
# . any char except newline
# \d digit [0-9] \D non-digit
# \w word char [a-zA-Z0-9_] \W non-word
# \s whitespace \S non-whitespace
# Quantifiers
# * 0 or more
# + 1 or more
# ? 0 or 1
# {n} exactly n
# {n,} n or more
# {n,m} between n and m
re.findall(r"\d{3}", "1234567") # ['123', '456']
re.findall(r"\d{2,4}", "12345678") # ['1234', '5678']
# Anchors
# ^ start of string $ end of string
# \b word boundary
re.findall(r"^\w+", "Hello World") # ['Hello']
re.findall(r"\b\w+\b", "hi there") # ['hi', 'there']
# Groups & alternation
re.findall(r"(cat|dog)", "cat and dog") # ['cat', 'dog']
re.findall(r"(\w+)@(\w+\.\w+)", "[email protected]")Substitution & Division
re.sub() remplace les correspondances — utilisez des références arrière (\1, \2) pour référencer les groupes, ou une fonction pour un remplacement dynamique. re.split() est plus puissant que str.split() — il accepte des motifs regex. Les groupes de capture dans le motif sont inclus dans le résultat. Utilisez re.subn() pour obtenir (résultat, nombre).
import re
# re.sub - replace matches
result = re.sub(r"\d+", "#", "a1b22c333")
# 'a#b#c#'
# Replace with count limit
result = re.sub(r"\d", "X", "a1b2c3", count=2)
# 'aXbXc3'
# Use backreferences in replacement
result = re.sub(r"(\w+)@(\w+)", r"\2.\1", "user@domain")
# 'domain.user'
# Use function as replacement
def upper(m):
return m.group().upper()
result = re.sub(r"\b[a-z]", upper, "hello world")
# 'Hello World' (capitalize first letter of each word)
# re.split - split by pattern
parts = re.split(r"[,;\s]+", "a, b; c d")
# ['a', 'b', 'c', 'd']
# Split with capture groups (keeps delimiters)
parts = re.split(r"([,;])", "a,b;c")
# ['a', ',', 'b', ';', 'c']
# Split with maxsplit
parts = re.split(r",", "a,b,c,d", maxsplit=2)
# ['a', 'b', 'c,d']Compilation & Indicateurs
Compilez les motifs avec re.compile() lorsque vous les utilisez plusieurs fois — c'est plus rapide. Les indicateurs modifient le comportement : IGNORECASE, MULTILINE, DOTALL, VERBOSE (autorise les commentaires/espaces dans les motifs). Les groupes nommés (?P<name>...) améliorent la lisibilité. Les lookahead (?=) et lookbehind (?<=) correspondent sans consommer.
import re
# Compile pattern for reuse (faster when used many times)
email_re = re.compile(r"^[\w.+-]+@([\w-]+\.)+[\w-]+$")
print(email_re.match("[email protected]")) # match object
print(email_re.match("invalid")) # None
# Common flags
re.IGNORECASE # case-insensitive
re.MULTILINE # ^ and $ match line boundaries
re.DOTALL # . matches newline too
re.VERBOSE # allow whitespace & comments in pattern
# Combine flags with |
pattern = re.compile(r"""
^ # start of line
(\w+) # capture word
\s+ # whitespace
(\d+) # capture number
""", re.VERBOSE | re.MULTILINE)
# Named groups (more readable)
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2024-01")
print(m.group("year")) # 2024
print(m.group("month")) # 01
print(m.groupdict()) # {'year': '2024', 'month': '01'}
# Lookahead/lookbehind
re.findall(r"\d+(?= dollars)", "100 dollars, 200 euros")
# ['100'] (positive lookahead)
re.findall(r"(?<=\$)\d+", "$100 and $200")
# ['100', '200'] (positive lookbehind)Async & Concurrence
Bases d'asyncio
asyncio est le framework d'E/S asynchrone de Python. 'async def' définit une coroutine ; 'await' suspend jusqu'à ce qu'un résultat soit prêt. asyncio.run() démarre la boucle d'événements. asyncio.gather() exécute des coroutines de manière concurrente. Les coroutines permettent une E/S à haute concurrence sans threads.
import asyncio
# Define a coroutine
async def greet(name, delay):
await asyncio.sleep(delay) # non-blocking sleep
return f"Hello, {name}!"
# Run a coroutine
async def main():
result = await greet("Alice", 1)
print(result)
# Run the event loop
asyncio.run(main())
# Concurrent execution with gather
async def main():
# Run coroutines concurrently
results = await asyncio.gather(
greet("Alice", 2),
greet("Bob", 1),
greet("Carol", 3)
)
print(results) # all complete after 3 seconds (max delay)
asyncio.run(main())
# asyncio.create_task - schedule without awaiting immediately
async def main():
task = asyncio.create_task(greet("Alice", 1))
# do other work here
result = await task # await when needed
print(result)HTTP async & Délais
asyncio.timeout() (Python 3.11+) annule les opérations qui prennent trop de temps. Pour HTTP, utilisez aiohttp (async) au lieu de requests (sync). asyncio.Queue permet des motifs producteur-consommateur. Async est idéal pour le travail lié aux E/S (réseau, disque) — pas pour le travail lié au CPU.
import asyncio
# Async timeout
async def fetch_with_timeout(url, timeout=5):
try:
async with asyncio.timeout(timeout):
# simulate async operation
await asyncio.sleep(2)
return f"Data from {url}"
except asyncio.TimeoutError:
return "Request timed out"
# Using aiohttp (third-party) for HTTP
# import aiohttp
#
# async def fetch(url):
# async with aiohttp.ClientSession() as session:
# async with session.get(url) as response:
# return await response.text()
# Producer-consumer pattern
async def producer(queue):
for i in range(5):
await asyncio.sleep(0.1)
await queue.put(f"item-{i}")
await queue.put(None) # sentinel
async def consumer(queue):
while True:
item = await queue.get()
if item is None:
break
print(f"Processed: {item}")
queue.task_done()
async def main():
queue = asyncio.Queue()
await asyncio.gather(producer(queue), consumer(queue))
asyncio.run(main())Threading
Le threading est pour la concurrence liée aux E/S (réseau, E/S fichier). Le GIL de Python empêche la véritable exécution CPU parallèle dans les threads. Utilisez Lock pour protéger l'état partagé contre les conditions de course. Pour le travail lié au CPU, utilisez multiprocessing à la place. Les threads partagent la mémoire ; les processus non.
import threading
import time
# Basic threading
def worker(name, delay):
print(f"Worker {name} starting")
time.sleep(delay)
print(f"Worker {name} done")
# Create and start threads
t1 = threading.Thread(target=worker, args=("A", 2))
t2 = threading.Thread(target=worker, args=("B", 1))
t1.start()
t2.start()
# Wait for threads to complete
t1.join()
t2.join()
print("All done")
# Thread with Lock (for shared state)
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock: # acquire/release lock
counter += 1
threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter}") # 500000 (correct with lock)Multiprocessing
Le multiprocessing contourne le GIL pour un véritable parallélisme CPU — chaque processus a son propre interpréteur Python. Utilisez Pool pour des opérations map parallèles. concurrent.futures fournit une API unifiée pour les threads et les processus. Protégez toujours avec if __name__ == '__main__' sur Windows.
from multiprocessing import Process, Pool, Queue
import os
# Basic process
def worker(name):
print(f"Process {name} PID: {os.getpid()}")
if __name__ == "__main__":
p = Process(target=worker, args=("A",))
p.start()
p.join()
# Process pool for parallel work
def square(x):
return x * x
if __name__ == "__main__":
with Pool(4) as pool: # 4 worker processes
results = pool.map(square, range(10))
print(results) # [0, 1, 4, 9, ..., 81]
# Asynchronous map
with Pool(4) as pool:
result = pool.map_async(square, range(10))
print(result.get()) # blocks until done
# concurrent.futures (higher-level API)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
with ProcessPoolExecutor() as executor:
results = list(executor.map(square, range(10)))
with ThreadPoolExecutor() as executor:
futures = [executor.submit(square, i) for i in range(10)]
results = [f.result() for f in futures]Décorateurs
Décorateur de base
Les décorateurs enveloppent une fonction pour étendre ou modifier son comportement sans changer la source originale. La syntaxe @ est du sucre syntaxique pour affecter le résultat de l'appel du décorateur au nom de la fonction. Utilisez *args, **kwargs dans le wrapper pour qu'il fonctionne avec toute signature.
# A decorator is a function that takes a function and returns a new function
def uppercase_result(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@uppercase_result
def greet(name):
return f"hello, {name}"
print(greet("world")) # HELLO, WORLD
# @uppercase_result is sugar for: greet = uppercase_result(greet)functools.wraps (Préserver les métadonnées)
Sans @wraps, la fonction enveloppée perd son __name__, __doc__ et sa signature originaux — les outils de débogage et help() affichent 'wrapper' à la place. Utilisez toujours @functools.wraps(func) dans les décorateurs pour préserver les métadonnées. C'est une meilleure pratique quasi universelle.
from functools import wraps
def log_calls(func):
@wraps(func) # copies __name__, __doc__, __module__
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}({args}, {kwargs})")
return func(*args, **kwargs)
return wrapper
@log_calls
def add(a, b):
"""Add two numbers."""
return a + b
print(add.__name__) # 'add' (not 'wrapper')
print(add.__doc__) # 'Add two numbers.'
help(add) # shows original docstringDécorateur avec arguments
Lorsqu'un décorateur prend des arguments, vous avez besoin de trois niveaux d'imbrication : la fabrique (prend les args), le décorateur (prend la fonction), et le wrapper (prend les args d'appel). @repeat(3) appelle repeat(3) en premier, qui renvoie le décorateur, qui est ensuite appliqué à la fonction.
# A decorator factory: returns the actual decorator
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def say_hi(name):
print(f"Hi, {name}!")
say_hi("Alice")
# Hi, Alice! (printed 3 times)Décorateur basé sur une classe
Les décorateurs de classe utilisent __init__ pour stocker la fonction et __call__ pour intercepter les invocations. Ils sont idéaux lorsque le décorateur doit maintenir un état (comme un compteur d'appels ou un cache). L'instance de classe remplace la fonction, donc l'appeler déclenche __call__.
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
wraps(func)(self) # preserve metadata
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} called {self.count} times")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # count=1
say_hello() # count=2
say_hello() # count=3
print(say_hello.count) # 3Décorateurs intégrés (@property, @staticmethod, @classmethod)
@property transforme une méthode en attribut calculé (accessible sans parenthèses). @classmethod reçoit la classe comme premier argument — parfait pour les constructeurs alternatifs. @staticmethod ne reçoit aucun premier argument implicite — juste une fonction qui se trouve vivre dans l'espace de noms de la classe. Ensemble, ils forment l'épine dorsale de la POO pythonique.
class Circle:
pi = 3.14159
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return Circle.pi * self._radius ** 2
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2)
@staticmethod
def is_valid_radius(r):
return r >= 0
c = Circle(5)
print(c.area) # 78.54 (no parentheses!)
c.radius = 10 # uses the setter
c2 = Circle.from_diameter(20) # alternative constructorDécorateurs empilés
Lors de l'empilement de décorateurs, ils sont appliqués de bas en haut (celui le plus proche de la fonction s'exécute en premier) mais s'exécutent de haut en bas au moment de l'appel. Donc @bold enveloppe @italic qui enveloppe greet. Le résultat est imbriqué comme des couches d'oignon. L'ordre compte — les inverser change l'imbrication de sortie.
from functools import wraps
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@bold
@italic
def greet(name):
return f"Hello, {name}"
print(greet("World"))
# <b><i>Hello, World</i></b>
# Applied bottom-up: italic first, then boldGénérateurs & Itérateurs
Fonctions génératrices (yield)
Les générateurs produisent des valeurs paresseusement avec yield — ils suspendent l'exécution après chaque yield et reprennent lorsque next() est appelé. Cela les rend efficaces en mémoire pour les séquences grandes ou infinies puisqu'une seule valeur existe en mémoire à la fois. Une fois épuisé, un générateur ne peut pas être réutilisé.
# A generator uses 'yield' to produce values lazily, one at a time
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
gen = count_up_to(5)
print(next(gen)) # 1
print(next(gen)) # 2
print(list(gen)) # [3, 4, 5] (exhausts the rest)
# Generators are memory-efficient: they don't build the whole list
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Expressions génératrices
Les expressions génératrices sont l'équivalent paresseux des compréhensions de liste — utilisez des parenthèses au lieu de crochets. Elles utilisent une mémoire constante quelle que soit la taille, ce qui les rend idéales pour sum(), max(), any(), ou pour alimenter d'autres itérateurs. Préférez-les aux compréhensions de liste lorsque vous n'avez pas besoin d'accès aléatoire.
# Like list comprehensions, but lazy (uses parentheses)
squares_list = [x ** 2 for x in range(10)] # builds full list
squares_gen = (x ** 2 for x in range(10)) # lazy generator
print(squares_gen) # <generator object>
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
# Memory comparison
import sys
print(sys.getsizeof([x for x in range(10000)])) # ~87616 bytes
print(sys.getsizeof((x for x in range(10000)))) # ~200 bytes (constant!)
# Use in sum(), list(), any() etc.
total = sum(x ** 2 for x in range(100)) # no extra list created
print(total) # 328350Protocole itérateur (__iter__, __next__)
Le protocole itérateur exige __iter__ (renvoie un itérateur) et __next__ (renvoie la valeur suivante ou lève StopIteration). Les itérables peuvent être parcourus ; les itérateurs produisent des valeurs une à la fois. Pour les itérables réutilisables, séparez l'itérable (renvoie un nouvel itérateur) de l'itérateur (détient l'état).
class Range2:
"""A custom iterator that yields even numbers."""
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self # the object is its own iterator
def __next__(self):
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += 2
return value
r = Range2(0, 10)
for num in r:
print(num) # 0, 2, 4, 6, 8
# An iterable returns a fresh iterator each time __iter__ is called.
# An iterator returns itself and raises StopIteration when exhausted.send(), throw(), close()
Les méthodes avancées des générateurs permettent une communication bidirectionnelle : send() passe une valeur dans le générateur (devient le résultat de yield), throw() injecte une exception au point de yield, et close() termine le générateur. Vous devez 'amorcer' le générateur avec next() avant d'envoyer. Elles alimentent les coroutines et les frameworks async.
def echo():
while True:
received = yield # yield without a value, receives via send()
print(f"Echo: {received}")
gen = echo()
next(gen) # prime the generator (advance to first yield)
gen.send("hello") # Echo: hello
gen.send("world") # Echo: world
# throw() injects an exception at the yield point
def safe_gen():
try:
while True:
yield
except ValueError:
print("Caught ValueError inside generator")
g = safe_gen()
next(g)
g.throw(ValueError, "boom") # Caught ValueError inside generator
# close() stops the generator (raises GeneratorExit)
gen.close()Pipelines de générateurs
Les pipelines de générateurs chaînent des producteurs paresseux afin que les données circulent par étapes, un élément à la fois — chaque élément est entièrement traité avant que le suivant ne soit lu. Cela évite de construire des listes intermédiaires et est la fondation du traitement de flux de données. Les pipes Unix fonctionnent conceptuellement de la même manière.
# Chain generators to build data-processing pipelines
def numbers():
for i in range(1, 11):
yield i
def squared(seq):
for n in seq:
yield n ** 2
def evens(seq):
for n in seq:
if n % 2 == 0:
yield n
# Each stage processes one item at a time — no intermediate lists
pipeline = evens(squared(numbers()))
print(list(pipeline)) # [4, 16, 36, 64, 100]
# Equivalent with generator expressions:
result = (n for n in (x ** 2 for x in range(1, 11)) if n % 2 == 0)
print(list(result)) # [4, 16, 36, 64, 100]yield from (Délégation)
yield from délègue tous les yields (et send/throw/close) à un sous-itérateur, aplatissant les structures imbriquées et composant les coroutines. Il est particulièrement puissant pour les générateurs récursifs — le cas d'usage classique est l'aplatissage de listes arbitrairement imbriquées. Dans le code async, 'await' est construit sur le même concept.
# yield from delegates to a sub-iterator (Python 3.3+)
def flatten(nested):
for item in nested:
if isinstance(item, (list, tuple)):
yield from flatten(item) # recursive delegation
else:
yield item
data = [1, [2, 3, [4, 5]], 6, [7, [8, [9]]]]
print(list(flatten(data)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# yield from also forwards send()/throw() to the sub-generator,
# making it essential for coroutine composition.Gestionnaires de contexte
Bases de l'instruction with
L'instruction with garantit que les ressources sont libérées (fichiers fermés, verrous relâchés, connexions renvoyées) même lorsque des exceptions se produisent. Elle appelle __enter__ au début et __exit__ à la fin. Préférez toujours 'with' au try/finally manuel pour la gestion des ressources — c'est plus sûr et plus lisible.
# 'with' guarantees cleanup even if an exception occurs
with open("data.txt", "r") as f:
content = f.read()
# f.close() is called automatically here, even if read() raised
# Without 'with' you must manually close:
f = open("data.txt", "r")
try:
content = f.read()
finally:
f.close() # easy to forget!
# Common built-in context managers:
with open("out.txt", "w") as f, open("in.txt") as g:
f.write(g.read()) # both files close automaticallyGestionnaire de contexte personnalisé (Classe)
Un gestionnaire de contexte basé sur une classe implémente __enter__ (configuration, renvoie l'objet de contexte) et __exit__(exc_type, exc_val, exc_tb) (nettoyage). Les arguments __exit__ reçoivent les informations d'exception si une s'est produite ; renvoyer True la supprime. Ce motif est idéal pour la configuration/démontage complexes comme les transactions de base de données.
class Timer:
def __init__(self, label="Timer"):
self.label = label
def __enter__(self):
import time
self.start = time.perf_counter()
return self # value bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
import time
elapsed = time.perf_counter() - self.start
print(f"{self.label}: {elapsed:.4f}s")
# Return False (or None) to propagate exceptions
# Return True to suppress the exception
return False
with Timer("Processing"):
total = sum(i ** 2 for i in range(1_000_000))
# Processing: 0.1234scontextlib.contextmanager
contextlib.contextmanager transforme une fonction génératrice en gestionnaire de contexte — le code avant yield est __enter__, le code après yield (dans finally) est __exit__. C'est plus concis qu'une classe pour les cas simples. Yield une valeur pour la fournir à la variable 'as'. Utilisez try/finally pour garantir le nettoyage.
from contextlib import contextmanager
import time
@contextmanager
def timer(label="Timer"):
start = time.perf_counter()
try:
yield # code inside the 'with' block runs here
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.4f}s")
with timer("My task"):
sum(i ** 2 for i in range(1_000_000))
# My task: 0.1234s
# You can also yield a value to bind with 'as'
@contextmanager
def open_db(path):
db = connect(path)
try:
yield db
finally:
db.close()
with open_db("app.db") as db:
db.query("SELECT 1")Gestionnaires de contexte multiples
Python 3.10+ permet des instructions 'with' multi-lignes parenthésées pour une syntaxe plus propre. Pour des nombres dynamiques de gestionnaires de contexte, contextlib.ExitStack les gère en groupe et les déroule tous dans l'ordre inverse. ExitStack est essentiel lorsque le nombre de ressources n'est pas connu avant l'exécution.
# Python 3.10+ supports parenthesized context managers
with (
open("input.txt") as fin,
open("output.txt", "w") as fout,
):
fout.write(fin.read())
# Pre-3.10: nest them or use contextlib.ExitStack
from contextlib import ExitStack
files = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
handles = [stack.enter_context(open(f)) for f in files]
for h in handles:
print(h.read())Utilitaires contextlib (suppress, redirect)
contextlib.suppress remplace try/except/pass pour les exceptions attendues — beaucoup plus lisible. redirect_stdout/redirect_stderr capturent la sortie qui irait autrement à la console, utile pour les tests ou la journalisation. Ces utilitaires évitent le code passe-partout et rendent l'intention explicite.
from contextlib import suppress, redirect_stdout, redirect_stderr
import io, warnings
# suppress: ignore specific exceptions (cleaner than try/except/pass)
with suppress(FileNotFoundError):
os.remove("temp.txt") # no error if file doesn't exist
# redirect_stdout: capture print output
buffer = io.StringIO()
with redirect_stdout(buffer):
print("This goes to the buffer, not console")
captured = buffer.getvalue()
# redirect_stderr: capture error/warning output
err_buf = io.StringIO()
with redirect_stderr(err_buf):
warnings.warn("a warning")
print(err_buf.getvalue()) # the warning text
# Also: contextlib.chdir (3.11+) to temporarily change directory
# from contextlib import chdir
# with chdir("/tmp"): ...Gestionnaires de contexte async
Les gestionnaires de contexte async utilisent __aenter__/__aexit__ (notez le préfixe 'a') et l'instruction 'async with'. Ils sont essentiels pour gérer les ressources async comme les connexions de base de données ou les sessions HTTP (ex. aiohttp.ClientSession). Le nettoyage s'exécute même si un await ou une exception se produit dans le bloc.
import asyncio
class AsyncDB:
async def __aenter__(self):
print("connecting...")
await asyncio.sleep(0.1) # simulate async connect
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("closing...")
await asyncio.sleep(0.1) # simulate async close
return False
async def query(self, sql):
return f"result of: {sql}"
async def main():
async with AsyncDB() as db:
result = await db.query("SELECT 1")
print(result)
asyncio.run(main())
# connecting...
# result of: SELECT 1
# closing...Indications de type
Annotations de base des variables & fonctions
Les indications de type documentent les types attendus mais ne sont PAS appliquées à l'exécution — Python reste dynamiquement typé. Utilisez un vérificateur statique comme mypy ou pyright pour détecter les erreurs de type avant l'exécution. Les génériques intégrés (list[str], dict[str, int]) nécessitent Python 3.9+ ; les versions antérieures ont besoin de typing.List, typing.Dict.
# Variable annotations (Python 3.6+)
name: str = "Alice"
age: int = 30
scores: list[float] = [95.5, 88.0, 92.3]
config: dict[str, int] = {"timeout": 30}
# Function annotations
def greet(name: str, excited: bool = False) -> str:
punctuation = "!" if excited else "."
return f"Hello, {name}{punctuation}"
print(greet("World", excited=True)) # Hello, World!
# Annotations are optional and NOT enforced at runtime
def add(a: int, b: int) -> int:
return a + b
add("2", "3") # runs fine, returns "23" (no error!)Module typing (List, Dict, Tuple, Optional)
Le module typing fournit des alias génériques pour les anciennes versions de Python. Depuis 3.9, vous pouvez utiliser les types intégrés directement (list[str] au lieu de List[str]). Optional[X] est un raccourci pour Union[X, None] — utilisez-le pour signaler qu'une fonction peut renvoyer None, forçant les appelants à gérer le cas None.
from typing import List, Dict, Tuple, Set, FrozenSet
# Pre-3.9 style (still works, needed for older Python)
names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 95}
point: Tuple[int, int] = (10, 20)
mixed: Tuple[str, int, float] = ("a", 1, 2.0)
variadic: Tuple[int, ...] = (1, 2, 3) # variable-length
# Python 3.9+ built-in generics (preferred)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95}
point: tuple[int, int] = (10, 20)
# Optional means "could be None"
from typing import Optional
def find_user(uid: int) -> Optional[str]:
if uid == 1:
return "Alice"
return None # could also just 'return'Types Union & Literal
Union[X, Y] (ou X | Y en 3.10+) signifie qu'une valeur peut être de l'un ou l'autre type. Literal restreint une valeur à des constantes spécifiques — excellent pour les énumérations de chaînes sans la surcharge d'enum, et pour la distribution de fonctions surchargées. mypy utilise Literal pour restreindre les types et détecter les arguments invalides au moment de la vérification.
from typing import Union, Literal, overload
# Union: value can be one of several types
def process(data: Union[str, bytes]) -> str:
if isinstance(data, bytes):
return data.decode("utf-8")
return data
# Python 3.10+ union syntax with | (preferred)
def process2(data: str | bytes) -> str:
if isinstance(data, bytes):
return data.decode("utf-8")
return data
# Literal: restrict to specific constant values
def set_mode(mode: Literal["r", "w", "a"]) -> None:
print(f"Mode set to {mode}")
set_mode("r") # OK
# set_mode("x") # mypy error: not a valid literal
# Literal for boolean-like flags
Direction = Literal["up", "down", "left", "right"]TypeVar & Génériques
TypeVar crée des variables de type génériques afin que les fonctions et classes puissent préserver les relations de type (ex. 'renvoie le même type que l'entrée'). Utilisez bound= pour contraindre à un sous-type, ou spécifiez des contraintes comme TypeVar('T', int, float). Les classes génériques utilisent Generic[T] comme base pour devenir des conteneurs paramétrés.
from typing import TypeVar, Generic, List
T = TypeVar("T") # a generic type variable
def first(items: List[T]) -> T:
return items[0]
# Type inference: T is bound to the argument's type
x: int = first([1, 2, 3]) # T = int
y: str = first(["a", "b", "c"]) # T = str
# Bounded TypeVar: T must be a subtype of Number
from typing import TypeVar
from numbers import Number
N = TypeVar("N", bound=Number)
def sum_all(values: list[N]) -> N:
total = values[0]
for v in values[1:]:
total = total + v
return total
# Generic class
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
s: Stack[int] = Stack()
s.push(1)
# s.push("x") # mypy errorCallable, Alias de type & Protocoles
Callable[[int, str], bool] décrit une fonction prenant int et str, renvoyant bool. Les alias de type donnent des noms descriptifs aux types complexes. Protocol permet le typage structurel (canard) — tout objet avec les bonnes méthodes satisfait le protocole, aucune héritage requis. C'est la réponse de Python aux interfaces.
from typing import Callable, Protocol, TypeAlias
# Callable signature: Callable[[ArgTypes], ReturnType]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
apply(lambda x, y: x + y, 3, 4) # 7
# Type aliases (3.12+ uses 'type' statement; older uses assignment)
type Vector = list[float] # 3.12+
Vector2: TypeAlias = list[float] # 3.10+
def magnitude(v: Vector) -> float:
return sum(x ** 2 for x in v) ** 0.5
# Protocol: structural typing (duck typing with static checks)
class Drawable(Protocol):
def draw(self) -> None: ...
def render(obj: Drawable) -> None:
obj.draw() # any object with a draw() method works
class Circle:
def draw(self) -> None:
print("drawing circle")
render(Circle()) # OK — Circle has draw()Vérification de type avec mypy
mypy est le vérificateur de type statique le plus populaire pour Python — il analyse les indications de type sans exécuter le code. Il détecte les bugs de gestion None, les mauvais types d'arguments et les retours manquants. Commencez par le typage progressif : ajoutez des indications au nouveau code et exécutez mypy dans CI. Utilisez --strict pour les nouveaux projets afin d'imposer des annotations complètes.
# Save as example.py, then run: mypy example.py
from typing import Optional
def divide(a: float, b: float) -> Optional[float]:
if b == 0:
return None
return a / b
result = divide(10, 0)
# Without checking, this would crash at runtime:
# print(result + 1) # TypeError: NoneType + int
# mypy catches it:
# error: Unsupported operand types for + ("None" and "int")
# fix: check for None first
if result is not None:
print(result + 1)
# Run strict mode for maximum safety:
# mypy --strict example.py
# Common strict flags: --disallow-untyped-defs, --no-implicit-optionalData Classes
@dataclass de base
@dataclass génère automatiquement __init__, __repr__ et __eq__ basés sur les champs annotés — éliminant le passe-partout pour les classes de données. Idéal pour les objets de valeur, configs, DTO et enregistrements. Disponible depuis Python 3.7. Les champs doivent avoir des annotations de type ; l'annotation définit le champ.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1) # Point(x=1.0, y=2.0) — auto __repr__
print(p1 == p2) # True — auto __eq__ (compares fields)
print(p1.x) # 1.0
# Without @dataclass you'd write all this boilerplate:
# class Point:
# def __init__(self, x, y): self.x = x; self.y = y
# def __repr__(self): ...
# def __eq__(self, other): ...Valeurs par défaut & default_factory
Les valeurs par défaut mutables (listes, dicts, sets) doivent utiliser field(default_factory=list) — utiliser [] directement partagerait une liste entre toutes les instances, un bug classique. default_factory est appelé une fois par instance pour créer un objet frais. Les valeurs par défaut immuables simples (int, str, bool, None) peuvent être affectées directement.
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
grade: str = "A" # simple default
tags: list[str] = field(default_factory=list) # mutable default!
scores: dict[str, int] = field(default_factory=dict)
s = Student("Alice")
print(s) # Student(name='Alice', grade='A', tags=[], scores={})
s.tags.append("honors")
s2 = Student("Bob")
print(s2.tags) # [] — each instance gets its own list
# NEVER use [] or {} as a direct default — all instances would share
# the same mutable object (classic Python pitfall).frozen, eq & order
frozen=True rend une dataclass immuable — les champs ne peuvent pas être réaffectés, et l'instance devient hachable (utilisable comme clés de dict ou membres de set). order=True ajoute des méthodes de comparaison pour le tri. Combinez frozen=True avec order=True pour des types de valeur immuables et triables comme les coordonnées, couleurs ou versions.
from dataclasses import dataclass
# frozen=True makes instances immutable (hashable, usable as dict keys)
@dataclass(frozen=True)
class Color:
r: int
g: int
b: int
c = Color(255, 0, 0)
# c.r = 128 # FrozenInstanceError!
print(hash(c)) # works — frozen dataclasses are hashable
# order=True generates __lt__, __le__, __gt__, __ge__ for sorting
@dataclass(order=True)
class Priority:
level: int
tasks = [Priority(3), Priority(1), Priority(2)]
tasks.sort()
print(tasks) # [Priority(level=1), Priority(level=2), Priority(level=3)]
# Common combo: frozen + order for immutable comparable values
@dataclass(frozen=True, order=True)
class Version:
major: int
minor: int__post_init__ & personnalisation des champs
__post_init__ s'exécute automatiquement après le __init__ généré — utilisez-le pour calculer les champs dérivés, valider les valeurs ou effectuer la configuration. field(init=False) crée un champ absent du constructeur (bon pour les valeurs calculées/mises en cache). field(repr=False, compare=False) masque les champs de repr et des vérifications d'égalité.
from dataclasses import dataclass, field
@dataclass
class User:
email: str
_email_normalized: str = field(init=False, repr=False)
id: int = field(default=0)
def __post_init__(self):
# runs after __init__; compute derived fields here
self._email_normalized = self.email.strip().lower()
u = User(" [email protected] ")
print(u.email) # ' [email protected] '
print(u._email_normalized) # '[email protected]'
# field(init=False) excludes a field from __init__
# field(repr=False) hides it from the repr
# field(compare=False) excludes from __eq__/__hash__
# field(metadata={...}) attaches custom metadataHéritage & slots
Les dataclasses prennent en charge l'héritage — les champs enfants sont ajoutés après les champs parents, et vous pouvez remplacer les valeurs par défaut parentes. Note : un champ avec une valeur par défaut dans le parent ne peut pas être suivi d'un champ sans valeur par défaut dans l'enfant. slots=True (3.10+) empêche l'ajout d'attributs arbitraires et réduit considérablement la mémoire par instance — idéal pour des millions de petits objets.
from dataclasses import dataclass
@dataclass
class Animal:
name: str
sound: str = "..."
@dataclass
class Dog(Animal):
breed: str = "unknown"
sound: str = "Woof" # override parent default
d = Dog("Rex", breed="Labrador")
print(d) # Dog(name='Rex', sound='Woof', breed='Labrador')
# Python 3.10+: slots=True saves memory (no __dict__)
@dataclass(slots=True)
class Pixel:
r: int
g: int
b: int
p = Pixel(0, 128, 255)
# p.new_field = 1 # AttributeError — slots prevent arbitrary attrs
# Saves ~40-50% memory vs regular dataclass for many instancesCollections & Itertools
namedtuple
namedtuple crée des sous-classes de tuple avec des champs nommés — aussi efficaces en mémoire que les tuples mais beaucoup plus lisibles. Ils sont immuables, utilisez donc _replace() pour créer des copies modifiées. Préférez typing.NamedTuple pour le nouveau code puisqu'il prend en charge les annotations de type et les valeurs par défaut. Excellent pour renvoyer plusieurs valeurs d'une fonction.
from collections import namedtuple
# Lightweight immutable class with named fields
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4 — access by name
print(p[0], p[1]) # 3 4 — also by index
print(p._asdict()) # {'x': 3, 'y': 4}
# More memory-efficient than a full class
# Use _replace to create a modified copy (immutable!)
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=4)
# Python 3.6+ typing.NamedTuple for type hints:
from typing import NamedTuple
class Point3D(NamedTuple):
x: float
y: float
z: float = 0.0Counter
Counter est une sous-classe de dict pour compter des objets hachables — parfait pour l'analyse de fréquence, les histogrammes et les votes. most_common(n) renvoie les n premiers éléments. Les clés manquantes renvoient 0 au lieu de lever KeyError. Counter prend en charge +, -, &, | pour l'arithmétique d'ensemble sur les comptes.
from collections import Counter
# Count hashable items
words = "the cat sat on the mat the cat".split()
c = Counter(words)
print(c) # Counter({'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1})
# Most common elements
print(c.most_common(2)) # [('the', 3), ('cat', 2)]
# Arithmetic on counters
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2) # Counter({'a': 4, 'b': 3})
print(c1 - c2) # Counter({'a': 2}) (drops zero/negatives)
# Missing keys return 0 (not KeyError)
print(c["dog"]) # 0
# Update and elements
c.update(["cat", "cat"])
print(sorted(c.elements())) # ['cat','cat','cat','cat','mat','on','sat','the','the','the']defaultdict
defaultdict crée automatiquement les clés manquantes avec une valeur par défaut issue d'une fonction de fabrique — list pour le regroupement, int pour le comptage, set pour la déduplication. Cela élimine le passe-partout 'if key not in dict'. La fabrique n'est appelée que lorsqu'une clé manque, pas à chaque accès.
from collections import defaultdict
# Group items by key without checking if key exists
words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_first = defaultdict(list)
for w in words:
by_first[w[0]].append(w)
print(dict(by_first))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
# Counting with int (default 0)
counts = defaultdict(int)
for w in words:
counts[w[0]] += 1
print(dict(counts)) # {'a': 2, 'b': 2, 'c': 1}
# Nested defaultdicts
tree = defaultdict(lambda: defaultdict(list))
tree["2024"]["Jan"].append("event1")
# vs regular dict: avoids the key-check boilerplate
# d = {}
# for w in words:
# if w[0] not in d:
# d[w[0]] = []
# d[w[0]].append(w)OrderedDict & deque
deque fournit append/pop en O(1) aux deux extrémités — utilisez-le pour les files, BFS et fenêtres glissantes au lieu des listes (list.pop(0) est O(n)). Avec maxlen, deque supprime automatiquement les anciens éléments, parfait pour les tampons bornés. OrderedDict est moins nécessaire depuis 3.7 (les dicts sont ordonnés), mais move_to_end et popitem restent uniques et utiles pour les caches LRU.
from collections import OrderedDict, deque
# deque: double-ended queue, O(1) append/pop at both ends
dq = deque([1, 2, 3], maxlen=5)
dq.appendleft(0) # deque([0, 1, 2, 3])
dq.append(4) # deque([0, 1, 2, 3, 4])
dq.append(5) # deque([1, 2, 3, 4, 5]) — oldest dropped (maxlen!)
print(dq.popleft()) # 1
print(dq) # deque([2, 3, 4, 5])
# deque is ideal for queues, BFS, sliding windows
from collections import deque
queue = deque(["task1", "task2"])
queue.append("task3")
next_task = queue.popleft() # FIFO — O(1) vs list.pop(0) which is O(n)
# OrderedDict: remembers insertion order (regular dicts do too in 3.7+,
# but OrderedDict has move_to_end and equality is order-sensitive)
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a") # move to last
print(list(od)) # ['b', 'a']
od.popitem(last=False) # pop first item (FIFO)itertools : chain, product, combinations, permutations
itertools fournit des outils rapides et efficaces en mémoire pour la combinatoire. chain aplatit les itérables paresseusement. product donne des produits cartésiens (remplace les boucles for imbriquées). combinations/permutations génèrent des sélections sans construire la liste complète — essentiel pour les entrées grandes ou infinies. Tous renvoient des itérateurs, enveloppez dans list() pour visualiser.
from itertools import chain, product, combinations, permutations
# chain: flatten multiple iterables
list(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
list(chain.from_iterable([[1, 2], [3, 4]])) # [1, 2, 3, 4]
# product: Cartesian product (nested loops)
list(product([1, 2], ["a", "b"]))
# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
list(product("AB", repeat=2)) # [('A','A'),('A','B'),('B','A'),('B','B')]
# combinations: unordered selections (no repeats)
list(combinations("ABC", 2)) # [('A','B'),('A','C'),('B','C')]
list(combinations("AAA", 2)) # [('A','A'),('A','A'),('A','A')]
# permutations: ordered arrangements
list(permutations("ABC", 2)) # [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]
# combinations_with_replacement: allow picking same element
from itertools import combinations_with_replacement
list(combinations_with_replacement("AB", 2)) # [('A','A'),('A','B'),('B','B')]itertools : groupby, accumulate, starmap
groupby regroupe les éléments consécutifs partageant une clé — triez par la clé d'abord ou vous obtiendrez plusieurs groupes pour la même clé. accumulate produit des totaux/produits cumulés. islice, takewhile et dropwhile sont des alternatives paresseuses au découpage et au filtrage qui fonctionnent sur tout itérateur, y compris infini.
from itertools import groupby, accumulate, starmap, islice, takewhile, dropwhile
# groupby: group consecutive items by a key (sort first!)
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
data.sort(key=lambda x: x[0]) # MUST sort by key first
for key, group in groupby(data, key=lambda x: x[0]):
print(key, list(group))
# A [('A',1),('A',2),('A',5)]
# B [('B',3),('B',4)]
# accumulate: running aggregate (sum by default)
list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10]
import operator
list(accumulate([1, 2, 3, 4], operator.mul)) # [1, 2, 6, 24]
# starmap: unpack args from tuples before calling
list(starmap(pow, [(2, 3), (3, 2), (10, 3)])) # [8, 9, 1000]
# islice: slice an iterator (doesn't support negative indices)
list(islice(range(100), 5, 10)) # [5, 6, 7, 8, 9]
# takewhile / dropwhile: filter by predicate
list(takewhile(lambda x: x < 5, [1, 4, 6, 3, 8])) # [1, 4]
list(dropwhile(lambda x: x < 5, [1, 4, 6, 3, 8])) # [6, 3, 8]functools : lru_cache, partial, reduce
lru_cache mémorise les résultats — accélérations spectaculaires pour les fonctions pures récursives ou coûteuses ; cache_info() affiche les statistiques de succès/échec. partial pré-remplit les arguments pour créer des callables spécialisés. reduce applique une fonction cumulativement (bien que sum(), any(), all() le remplacent souvent). cached_property calcule une fois puis met en cache sur l'instance.
from functools import lru_cache, partial, reduce
import operator
# lru_cache: memoize function results (Least Recently Used)
@lru_cache(maxsize=128)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(100)) # instant (without cache: impossibly slow)
print(fib.cache_info()) # CacheInfo(hits=98, misses=101, ...)
# partial: fix some arguments, create a new callable
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(3)) # 27
# reduce: cumulatively apply a function, reducing to one value
product = reduce(operator.mul, [1, 2, 3, 4]) # 24
# Equivalent: ((1*2)*3)*4
# Python 3.8+: cached_property for lazy computed attributes
from functools import cached_property
class Data:
@cached_property
def expensive(self):
print("computing...")
return [i ** 2 for i in range(1000000)]Traitement JSON & CSV
json.dumps & json.loads
json.dumps() (dump string) sérialise un objet Python en chaîne JSON ; json.loads() (load string) analyse le JSON en retour. Utilisez indent pour la lisibilité, ensure_ascii=False pour garder les caractères Unicode lisibles, et sort_keys pour une sortie déterministe. Les clés JSON doivent être des chaînes — les clés int deviennent des chaînes.
import json
# Serialize Python object to JSON string
data = {"name": "Alice", "age": 30, "scores": [95, 88, 92]}
json_str = json.dumps(data)
print(json_str) # {"name": "Alice", "age": 30, "scores": [95, 88, 92]}
# Pretty-print with indent
print(json.dumps(data, indent=2))
# {
# "name": "Alice",
# "age": 30,
# "scores": [95, 88, 92]
# }
# Parse JSON string to Python object
parsed = json.loads(json_str)
print(parsed["name"]) # Alice
print(type(parsed["scores"])) # <class 'list'>
# Sort keys, handle non-ASCII
print(json.dumps({"name": "Zoë"}, ensure_ascii=False, sort_keys=True))Lire & Écrire des fichiers JSON
json.dump() écrit directement dans un objet fichier ; json.load() lit depuis un. Spécifiez toujours encoding='utf-8' pour la portabilité. Rappelez-vous du mappage de type : les objets JSON deviennent des dicts, les tableaux des listes, et les nombres des int ou float. Les datetimes, sets et objets personnalisés ne sont PAS sérialisables en JSON par défaut.
import json
data = {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}
# Write to file
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Read from file
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded["users"][0]["name"]) # Alice
# Type conversions to remember:
# JSON object <-> Python dict
# JSON array <-> Python list
# JSON string <-> Python str
# JSON number <-> Python int/float
# JSON true/false <-> Python True/False
# JSON null <-> Python NoneEncodage JSON personnalisé (datetime, objets personnalisés)
Le module json ne peut pas sérialiser datetime, set ou classes personnalisées par défaut. Fournissez une fonction default (appelée pour les objets non sérialisables) ou une sous-classe JSONEncoder. Pour un aller-retour, associez un encodeur personnalisé avec un object_hook dans loads() pour reconstruire les types originaux. C'est ainsi que les ORMs sérialisent les objets modèle.
import json
from datetime import datetime
# Default behavior: TypeError on non-serializable types
# json.dumps({"now": datetime.now()}) # TypeError!
# Solution 1: default function for unknown types
def default_encoder(obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return sorted(obj)
raise TypeError(f"Cannot serialize {type(obj)}")
data = {"now": datetime.now(), "tags": {"a", "b"}}
print(json.dumps(data, default=default_encoder))
# Solution 2: custom JSONEncoder subclass
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return {"__datetime__": obj.isoformat()}
return super().default(obj)
print(json.dumps(data, cls=MyEncoder))
# Decoding with object_hook
def decoder(dct):
if "__datetime__" in dct:
return datetime.fromisoformat(dct["__datetime__"])
return dct
json.loads(json.dumps(data, cls=MyEncoder), object_hook=decoder)Lire des fichiers CSV
Ouvrez toujours les fichiers CSV avec newline='' pour éviter les problèmes de lignes vides sur Windows. csv.reader renvoie des listes ; csv.DictReader renvoie des dicts indexés par la ligne d'en-tête. Le module csv gère les guillemets, les virgules intégrées et les nouvelles lignes correctement — ne divisez jamais les lignes CSV manuellement avec line.split(','). Utilisez Sniffer pour détecter automatiquement les délimiteurs.
import csv
# Basic reader: each row is a list of strings
with open("data.csv", newline="", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader:
print(row) # ['name', 'age', 'city']
# DictReader: each row is a dict keyed by header
with open("data.csv", newline="") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"]) # access by column name
# Handle different delimiters and quoting
with open("data.tsv", newline="") as f:
reader = csv.reader(f, delimiter="\t", quotechar='"')
for row in reader:
print(row)
# Sniffer to auto-detect format
with open("unknown.csv", newline="") as f:
sample = f.read(1024)
dialect = csv.Sniffer().sniff(sample)
f.seek(0)
reader = csv.reader(f, dialect)Écrire des fichiers CSV
csv.writer écrit des listes ; csv.DictWriter écrit des dicts avec un ensemble fixe de noms de champs. Utilisez toujours newline='' lors de l'ouverture du fichier. Le paramètre quoting contrôle quand les champs sont entre guillemets — QUOTE_MINIMAL (par défaut) ne met entre guillemets que lorsque nécessaire, QUOTE_ALL met tout entre guillemets, utile pour les analyseurs stricts.
import csv
rows = [
["name", "age", "city"],
["Alice", 30, "NYC"],
["Bob", 25, "LA"],
]
# Basic writer
with open("out.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(rows) # write multiple rows
# DictWriter: write from dicts
with open("out.csv", "w", newline="") as f:
fieldnames = ["name", "age", "city"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({"name": "Alice", "age": 30, "city": "NYC"})
writer.writerow({"name": "Bob", "age": 25, "city": "LA"})
# Control quoting: QUOTE_MINIMAL (default), QUOTE_ALL, QUOTE_NONNUMERIC
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
# QUOTE_ALL wraps every field in quotes: "Alice","30","NYC"JSON Lines (NDJSON) & Streaming
JSON Lines (NDJSON) met un objet JSON par ligne — idéal pour les logs, les flux d'événements et les fichiers à ajout uniquement car vous pouvez traiter chaque ligne indépendamment. Pour les énormes documents JSON uniques, utilisez la bibliothèque ijson pour analyser en flux sans charger tout le fichier en mémoire. NDJSON est la norme pour de nombreux pipelines de données.
import json
# JSON Lines: one JSON object per line (great for logs, big data)
records = [{"id": 1, "msg": "first"}, {"id": 2, "msg": "second"}]
# Write NDJSON
with open("logs.jsonl", "w") as f:
for rec in records:
f.write(json.dumps(rec) + "\n")
# Read NDJSON line by line (memory-efficient for huge files)
with open("logs.jsonl", "r") as f:
for line in f:
rec = json.loads(line)
print(rec["id"], rec["msg"])
# Stream large JSON arrays without loading everything into memory
# Use ijson library for streaming parsing of huge JSON files:
# import ijson
# with open("huge.json", "rb") as f:
# for item in ijson.items(f, "items.item"):
# process(item) # one item at a timeJournalisation & Tests
Bases de logging
Le module logging est la méthode standard pour émettre une sortie de diagnostic — bien meilleur que print() car vous contrôlez les niveaux, formats et destinations. Utilisez logging.getLogger(__name__) par module pour pouvoir régler la verbosité par module. logging.exception() inclut automatiquement la trace. Configurez basicConfig une fois au démarrage.
import logging
# Basic configuration (call once at program start)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
# Log levels (severity ascending)
logging.debug("Detailed debug info") # DEBUG (10)
logging.info("General information") # INFO (20)
logging.warning("Something unexpected") # WARNING (30)
logging.error("A real error occurred") # ERROR (40)
logging.critical("System is down") # CRITICAL (50)
# Logging exceptions with traceback
try:
1 / 0
except ZeroDivisionError:
logging.exception("Division failed") # includes full traceback
# Get a named logger (best practice per module)
logger = logging.getLogger(__name__)
logger.info("Module-specific log")Journalisation vers fichier & Handlers multiples
Les Handlers routent les enregistrements de log vers des destinations — console, fichiers, réseau, email. RotatingFileHandler plafonne la taille du fichier et conserve des sauvegardes, empêchant la croissance illimitée des logs. Chaque handler peut avoir son propre niveau et format (ex. logs détaillés vers fichier, logs concis vers console). TimedRotatingFileHandler pivote par temps plutôt que par taille.
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)
# Console handler (INFO and above)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
# Rotating file handler (DEBUG and above, max 5MB x 3 backups)
file_handler = RotatingFileHandler(
"app.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)
logger.addHandler(console)
logger.addHandler(file_handler)
logger.debug("debug to file only")
logger.info("info to both console and file")
logger.error("error everywhere")Bases d'unittest
unittest est le framework de test intégré de Python (style xUnit). Les tests vivent dans des classes héritant de TestCase. setUp/tearDown s'exécutent avant/après chaque test pour l'isolation. Assertions courantes : assertEqual, assertTrue, assertRaises, assertIn. Exécutez avec python -m unittest pour la découverte automatique des fichiers test_*.py.
import unittest
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
class TestMath(unittest.TestCase):
def setUp(self):
# runs before each test method
self.data = [1, 2, 3]
def tearDown(self):
# runs after each test method
pass
def test_add(self):
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(-1, 1), 0)
def test_add_types(self):
self.assertEqual(add("a", "b"), "ab")
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(1, 0)
def test_membership(self):
self.assertIn(2, self.data)
self.assertTrue(3 in self.data)
if __name__ == "__main__":
unittest.main()
# Run: python -m unittest test_file.py -vBases de pytest
pytest est l'outil de test Python le plus populaire — de simples instructions assert donnent des rapports d'échec riches, sans classes passe-partout. pytest.raises vérifie les exceptions avec correspondance regex optionnelle. pytest.approx gère l'imprécision de comparaison des flottants. Installez avec pip install pytest et exécutez avec pytest -v pour une sortie détaillée.
# test_math.py — pytest is simpler and more powerful than unittest
# Install: pip install pytest
# Run: pytest -v
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
# Plain functions, no classes required
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
def test_add_strings():
assert add("hello", " world") == "hello world"
# Testing exceptions with pytest.raises
import pytest
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(1, 0)
# Approximate float comparison
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)Fixtures pytest
Les fixtures sont l'injection de dépendances de pytest — elles fournissent des données de configuration, des objets mock ou des ressources aux tests via des noms de paramètres. Les fixtures basées sur yield gèrent à la fois la configuration (avant yield) et le démontage (après yield). Les portées contrôlent la réutilisation : 'session' crée la fixture une fois pour toute l'exécution, 'module' une fois par fichier, 'function' (par défaut) une fois par test.
import pytest
# A fixture provides setup data/resources to tests
@pytest.fixture
def sample_list():
return [1, 2, 3, 4, 5]
# Use fixtures by passing their name as a parameter
def test_length(sample_list):
assert len(sample_list) == 5
def test_sum(sample_list):
assert sum(sample_list) == 15
# Fixture with setup AND teardown (yield)
@pytest.fixture
def db_connection():
print("\n[setup] connecting to DB")
conn = {"connected": True}
yield conn # test runs here; value passed to test
print("\n[teardown] closing DB")
conn["connected"] = False
def test_db(db_connection):
assert db_connection["connected"] is True
# Fixture scopes: function (default), class, module, session
@pytest.fixture(scope="session")
def expensive_resource():
return load_large_dataset() # created once per test sessionparametrize & mocking dans pytest
parametrize exécute une seule fonction de test sur plusieurs jeux d'entrées — élimine le copier-coller de code de test et donne une sortie claire par cas. unittest.mock.patch remplace des fonctions/objets par des mocks pour des tests isolés. assert_called_once_with vérifie que le mock a été utilisé correctement. @pytest.mark.skip et xfail gèrent les tests incomplets gracieusement.
import pytest
from unittest.mock import patch, MagicMock
# parametrize: run one test with multiple inputs
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(-1, 1, 0),
(0, 0, 0),
(100, 200, 300),
])
def test_add_many(a, b, expected):
assert add(a, b) == expected
# parametrize with IDs for readable output
@pytest.mark.parametrize("x", [1, 2, 3], ids=["one", "two", "three"])
def test_ids(x):
assert x > 0
# Mocking: replace external dependencies
def fetch_user(uid):
# imagine this calls a real API
return {"id": uid, "name": "real_user"}
@patch("__main__.fetch_user")
def test_with_mock(mock_fetch):
mock_fetch.return_value = {"id": 1, "name": "mocked"}
result = fetch_user(1)
assert result["name"] == "mocked"
mock_fetch.assert_called_once_with(1)
# Skip and expected failure
@pytest.mark.skip(reason="not implemented yet")
def test_future():
pass
@pytest.mark.xfail(reason="known bug #42")
def test_known_bug():
assert 1 == 2Programmation réseau
Serveur TCP
Crée un serveur TCP en utilisant le module socket. bind associe le socket à une adresse, listen définit la file d'attente, accept bloque jusqu'à ce qu'un client se connecte. Fermez toujours les connexions pour libérer les descripteurs de fichiers.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8080))
server.listen(5)
conn, addr = server.accept()
data = conn.recv(1024)
conn.sendall(b'Hello')
conn.close()Client TCP
Crée un client TCP qui se connecte à un serveur. connect établit la connexion, sendall envoie tous les octets, recv lit jusqu'au nombre d'octets spécifié. Utilisez encode/decode pour la conversion chaîne-vers-octets.
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 8080))
client.sendall(b'Hello Server')
response = client.recv(1024)
print(response.decode())
client.close()Socket UDP
UDP est sans connexion : pas de handshake, pas de livraison garantie. recvfrom renvoie à la fois les données et l'adresse de l'expéditeur. Utilisez SOCK_DGRAM pour UDP. Idéal pour DNS, les jeux et le streaming en temps réel.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('localhost', 9090))
data, addr = sock.recvfrom(1024)
print(f"From {addr}: {data.decode()}")
sock.sendto(b'Reply', addr)Serveur HTTP
Le module http.server fournit un serveur HTTP simple. Sous-classez BaseHTTPRequestHandler et redéfinissez do_GET, do_POST. À utiliser pour le développement uniquement ; utilisez gunicorn pour la production.
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(b'<h1>Hello</h1>')
HTTPServer(('localhost', 8000), Handler).serve_forever()Délai d'attente du socket
settimeout définit un délai d'attente pour toutes les opérations de socket. Si une opération dépasse le délai, une exception socket.timeout est levée. Utilisez try/finally pour garantir le nettoyage.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
try:
sock.connect(('example.com', 80))
data = sock.recv(1024)
except socket.timeout:
print('Connection timed out')
finally:
sock.close()Base de données (SQLite)
Créer une table
sqlite3 est intégré à Python. connect crée ou ouvre un fichier de base de données. CREATE TABLE IF NOT EXISTS empêche les erreurs si la table existe. Appelez toujours commit pour enregistrer les modifications.
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER)''')
conn.commit()Insérer des données
Utilisez toujours des requêtes paramétrées (placeholders ?) pour empêcher l'injection SQL. lastrowid renvoie l'ID auto-incrémenté. N'utilisez jamais le formatage de chaîne pour les valeurs SQL.
cursor.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
('Alice', '[email protected]', 30))
conn.commit()
print(f"ID: {cursor.lastrowid}")Interroger des données
fetchall renvoie toutes les lignes correspondantes sous forme de liste de tuples. fetchone renvoie une seule ligne ou None. Pour les grands jeux de résultats, itérez directement sur le curseur.
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))
rows = cursor.fetchall()
for row in rows:
print(row)
cursor.execute('SELECT * FROM users WHERE id = ?', (1,))
user = cursor.fetchone()Mettre à jour & Supprimer
UPDATE modifie les lignes existantes, DELETE les supprime. rowcount indique les lignes affectées. Utilisez toujours WHERE avec DELETE. commit persiste les modifications.
cursor.execute('UPDATE users SET age = ? WHERE name = ?', (31, 'Alice'))
cursor.execute('DELETE FROM users WHERE age < ?', (18,))
conn.commit()
print(f"Affected: {cursor.rowcount} rows")Fabrique de lignes
Utiliser conn comme gestionnaire de contexte valide automatiquement en cas de succès et annule en cas d'exception. row_factory = sqlite3.Row permet d'accéder aux colonnes par nom.
conn = sqlite3.connect('example.db')
conn.row_factory = sqlite3.Row
with conn:
conn.execute('INSERT INTO users (name, email) VALUES (?, ?)',
('Bob', '[email protected]'))
for row in conn.execute('SELECT * FROM users'):
print(row['name'], row['email'])Web Scraping
Bases de BeautifulSoup
requests récupère le contenu HTML, BeautifulSoup l'analyse. html.parser est intégré ; lxml est plus rapide. Vérifiez toujours response.status_code avant l'analyse.
import requests
from bs4 import BeautifulSoup
resp = requests.get('https://example.com')
soup = BeautifulSoup(resp.text, 'html.parser')
print(soup.title.string)
print(soup.find('h1').text)Trouver des éléments
find_all renvoie tous les éléments correspondants, find renvoie le premier. Utilisez class_ (avec tiret bas). select utilise les sélecteurs CSS pour les requêtes complexes.
links = soup.find_all('a')
for link in links:
print(link.get('href'), link.text)
article = soup.find('div', class_='article')
items = soup.select('ul.list > li.item')Extraire des tables
Les tables sont structurées en tr (lignes) contenant des cellules td (données) ou th (en-tête). strip supprime les espaces. find_all accepte une liste de noms de balises.
table = soup.find('table')
for row in table.find_all('tr'):
cols = row.find_all(['td', 'th'])
data = [col.text.strip() for col in cols]
print(data)Gérer la pagination
La pagination est gérée en suivant les liens de page suivante. select_one renvoie la première correspondance ou None. Ajoutez time.sleep entre les requêtes.
all_items = []
url = 'https://example.com/page/1'
while url:
resp = requests.get(url)
soup = BeautifulSoup(resp.text, 'html.parser')
all_items.extend([i.text for i in soup.select('.item')])
next_link = soup.select_one('a.next')
url = next_link.get('href') if next_link else NoneEnregistrer en CSV
csv.DictWriter écrit des dictionnaires en CSV. newline empêche les lignes vides supplémentaires sur Windows. encoding=utf-8 gère les caractères spéciaux.
import csv
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['name', 'price'])
writer.writeheader()
for item in scraped_data:
writer.writerow(item)Web async (aiohttp)
Client HTTP
aiohttp fournit du HTTP async. ClientSession gère le pool de connexions. async with garantit le nettoyage. asyncio.run exécute la coroutine.
import aiohttp, asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
data = asyncio.run(fetch('https://api.example.com'))Requêtes concurrentes
asyncio.gather exécute des coroutines de manière concurrente, réduisant le temps total. Toutes les requêtes partagent la même session. Utilisez un sémaphore pour limiter la concurrence.
async def fetch_all(urls):
async with aiohttp.ClientSession() as session:
tasks = [session.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [await r.text() for r in responses]Serveur Web
aiohttp.web crée des serveurs web async. Les routes sont définies avec la méthode HTTP et le motif de chemin. match_info extrait les paramètres de chemin.
from aiohttp import web
async def handle(request):
name = request.match_info.get('name', 'World')
return web.json_response({'message': f'Hello, {name}!'})
app = web.Application()
app.add_routes([web.get('/', handle), web.get('/{name}', handle)])
web.run_app(app, port=8080)Serveur WebSocket
Les WebSockets permettent une communication en temps réel bidirectionnelle. WebSocketResponse gère le handshake de mise à niveau. async for itère sur les messages.
async def ws_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
await ws.send_str(f'Echo: {msg.data}')
return wsSession avec cookies
ClientSession persiste les cookies entre les requêtes automatiquement. Essentiel pour le scraping authentifié. Utilisez une seule session pour toutes les requêtes.
async def login_and_fetch():
async with aiohttp.ClientSession() as session:
await session.post('https://example.com/login',
data={'user': 'admin', 'pass': '123'})
resp = await session.get('https://example.com/dashboard')
return await resp.text()Multiprocessing approfondi
Pool de processus
Pool gère les processus workers. map distribue le travail en parallèle. apply_async exécute une seule fonction de manière asynchrone. Utilisez toujours la garde if __name__ == main sur Windows.
from multiprocessing import Pool
def square(x): return x * x
if __name__ == '__main__':
with Pool(4) as pool:
results = pool.map(square, range(10))
result = pool.apply_async(square, (100,))
print(result.get(timeout=5))Mémoire partagée
Value et Array créent de la mémoire partagée entre processus. Utilisez get_lock pour synchroniser l'accès et empêcher les conditions de course.
from multiprocessing import Value, Array
counter = Value('i', 0)
arr = Array('d', [0.0, 1.0, 2.0])
with counter.get_lock():
counter.value += 1Communication par Queue
Queue permet une communication sûre entre processus. put ajoute des éléments, get les récupère. Queue est sécurisé pour les processus, gérant le verrouillage en interne.
from multiprocessing import Process, Queue
def worker(q):
q.put('Data from worker')
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
print(q.get())
p.join()Pipe
Pipe crée un canal de communication bidirectionnel. send et recv transmettent des objets Python via le pickling. Pipe est plus rapide que Queue pour la communication point à point.
from multiprocessing import Process, Pipe
def worker(conn):
conn.send(['hello', 'world'])
msg = conn.recv()
conn.close()
if __name__ == '__main__':
parent, child = Pipe()
p = Process(target=worker, args=(child,))
p.start()
print(parent.recv())
parent.send('acknowledged')
p.join()Synchronisation
Lock garantit qu'un seul processus accède à une ressource partagée à la fois. with lock acquiert et libère automatiquement. Autres primitives : RLock, Semaphore, Event.
from multiprocessing import Process, Lock
def safe_print(lock, msg):
with lock:
print(msg)
if __name__ == '__main__':
lock = Lock()
procs = [Process(target=safe_print, args=(lock, f'Task {i}'))
for i in range(5)]
for p in procs: p.start()
for p in procs: p.join()Environnements virtuels approfondis
Module venv
venv crée des environnements Python isolés avec leurs propres répertoires de packages. L'activation modifie PATH. Activez toujours avant d'installer des dépendances.
# Create
python -m venv myenv
# Activate (Linux/Mac)
source myenv/bin/activate
# Activate (Windows)
myenv\Scripts\activate
# Deactivate
deactivaterequirements.txt
requirements.txt liste les dépendances du projet. == épingle les versions exactes, >= autorise les mises à niveau dans une plage. Committez toujours au contrôle de version.
# Generate
pip freeze > requirements.txt
# Install
pip install -r requirements.txt
# Pin versions
flask==2.3.3
requests>=2.28.0,<3.0.0Poetry
Poetry est un gestionnaire de dépendances moderne. pyproject.toml remplace requirements.txt. Les environnements virtuels sont gérés automatiquement.
# Initialize
poetry init
# Add dependency
poetry add flask
poetry add pytest --group dev
# Install all
poetry install
# Run command
poetry run python app.pypipenv
pipenv combine pip et virtualenv. Pipfile déclare les dépendances, Pipfile.lock épingle les versions exactes. --dev sépare les dépendances de développement.
# Create environment
pipenv install
# Add package
pipenv install requests
pipenv install pytest --dev
# Activate shell
pipenv shell
# Run command
pipenv run python app.pyEnvironnements Conda
Conda gère à la fois les dépendances Python et non-Python. environment.yml capture l'environnement complet. Idéal pour la data science avec des dépendances binaires.
# Create
conda create -n myenv python=3.11
# Activate
conda activate myenv
# Export
conda env export > environment.yml
# Recreate
conda env create -f environment.ymlUtilisation avancée de pip
Installer depuis Git
Installez des packages directement depuis des dépôts Git. Utile pour les versions non publiées, les forks ou les packages privés. @branch ou @commit épingle à une version spécifique.
# From GitHub
pip install git+https://github.com/user/repo.git
# Specific branch
pip install git+https://github.com/user/repo.git@branch-name
# Specific commit
pip install git+https://github.com/user/repo.git@abc123Installation éditable
L'installation éditable (-e) lie le package au lieu de le copier. Les modifications sont immédiatement disponibles sans réinstallation. Essentiel pour le développement de packages.
# Install in development mode
pip install -e .
# From a specific path
pip install -e /path/to/package
# With extras
pip install -e ".[dev,test]"Contraintes & Hachages
Les contraintes limitent les versions installables. La vérification de hachage vérifie l'intégrité du package, empêchant les attaques de chaîne d'approvisionnement.
# constraints.txt
flask==2.3.3
pip install -c constraints.txt flask
# Hash checking
pip install --require-hashes -r requirements.txtGestion du cache
pip met en cache les wheels téléchargées. --no-cache-dir force les téléchargements frais. Purge libère de l'espace disque lorsque le cache devient trop volumineux.
# Show cache info
pip cache info
# List cached packages
pip cache list
# Purge entire cache
pip cache purge
# Install with no cache
pip install --no-cache-dir flaskIndex personnalisé
--index-url spécifie un dépôt de packages personnalisé. --extra-index-url ajoute un secours. --trusted-host contourne SSL pour les registres internes.
# Use custom index
pip install --index-url https://pypi.custom.com/simple/ flask
# Extra index (fallback)
pip install --extra-index-url https://pypi.custom.com/simple/ flask
# Trusted host (no SSL)
pip install --trusted-host pypi.custom.com flaskVérification de type (mypy)
Indications de type de base
Les indications de type annotent les paramètres de fonction et les types de retour. Python 3.9+ autorise les types intégrés directement. Les indications activent l'analyse statique avec mypy.
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
def process(data: list[int]) -> dict[str, int]:
return {str(x): x for x in data}Optional et Union
Optional[X] est équivalent à X | None (Python 3.10+). Les types Union autorisent plusieurs types possibles. mypy vérifie que tous les chemins de code gèrent tous les types.
from typing import Optional
def find(items: list[int], target: int) -> int | None:
for i, v in enumerate(items):
if v == target: return i
return NoneTypes génériques
Les génériques créent des conteneurs réutilisables et de type sûr. TypeVar définit une variable de type, Generic rend la classe générique. mypy assure la cohérence des types.
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()Protocol
Protocol définit le sous-typage structurel (typage canard avec vérification de type). Toute classe avec les méthodes requises satisfait le protocole, aucune héritage nécessaire.
from typing import Protocol
class Closeable(Protocol):
def close(self) -> None: ...
def cleanup(resource: Closeable) -> None:
resource.close()
class File:
def close(self) -> None: print("Closed")
cleanup(File()) # OK - File has close()Configuration mypy
mypy.ini configure la rigueur de la vérification de type. strict active toutes les vérifications. Les remplacements par module assouplissent les règles pour les tests ou le code hérité.
# mypy.ini
[mypy]
python_version = 3.11
strict = True
warn_return_any = True
disallow_untyped_defs = True
[mypy-tests.*]
ignore_errors = TrueConseils de performance
Liste vs Générateur
Les listes stockent tous les éléments en mémoire ; les générateurs produisent des valeurs à la demande. Utilisez les générateurs pour les grandes séquences itérées une fois.
# List: all in memory
squares = [x**2 for x in range(1000000)]
# Generator: lazy evaluation
squares_gen = (x**2 for x in range(1000000))
import sys
print(sys.getsizeof(squares)) # ~8MB
print(sys.getsizeof(squares_gen)) # ~200 bytesConcaténation de chaînes
La concaténation de chaînes avec += est O(n^2). join est O(n). Les f-strings sont la méthode d'interpolation la plus rapide.
# Slow: creates intermediate strings
result = ""
for s in parts:
result += s
# Fast: join in one operation
result = "".join(parts)
# Fast: f-strings
msg = f"Hello, {name}!"Variables locales
Les recherches de variables locales sont plus rapides que les recherches globales ou d'attribut. Affecter les fonctions fréquemment utilisées à des locales accélère les boucles.
import math
# Slow: global lookup
def compute_slow(values):
return [math.sqrt(v) for v in values]
# Fast: local reference
def compute_fast(values):
sqrt = math.sqrt
return [sqrt(v) for v in values]__slots__
__slots__ empêche la création de __dict__, économisant 40-50% de mémoire par instance. Significatif lors de la création de millions d'objets. Impossible d'ajouter des attributs non listés.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 # AttributeErrortimeit & cProfile
timeit mesure le temps d'exécution de petits extraits. cProfile montre où le temps est passé. Utilisez le profilage avant d'optimiser pour trouver les vrais goulots d'étranglement.
import timeit
t = timeit.timeit('sum(range(100))', number=10000)
print(f"{t:.4f}s")
import cProfile
cProfile.run('sum(x**2 for x in range(10000))')Pièges courants
Arguments par défaut mutables
Les valeurs d'arguments par défaut sont évaluées une fois au moment de la définition. Les valeurs par défaut mutables sont partagées entre tous les appels. Utilisez toujours None comme valeur par défaut.
# BUG: default list is shared
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2]!
# FIX: use None
def add_item(item, lst=None):
if lst is None: lst = []
lst.append(item)
return lstLiaison tardive des fermetures
Les fermetures capturent les variables par référence. Au moment où les lambdas sont appelés, la variable de boucle a sa valeur finale. Les arguments par défaut capturent la valeur courante.
# BUG: all print 2
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]
# FIX: default argument
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]Mise en cache des entiers
Python met en cache les petits entiers. is vérifie l'identité, == vérifie l'égalité. N'utilisez jamais is pour la comparaison de valeurs ; utilisez is uniquement pour None, True, False.
# Small integers cached (-5 to 256)
a = 256; b = 256
print(a is b) # True (cached)
c = 257; d = 257
print(c is d) # False (not cached)
print(c == d) # Trueis vs ==
is vérifie si deux références pointent vers le même objet. == vérifie si deux objets ont la même valeur. Utilisez is uniquement pour None, True, False.
a = [1, 2, 3]; b = [1, 2, 3]
print(a == b) # True (same values)
print(a is b) # False (different objects)
# Correct usage of is
if x is None: ...
if x is not None: ...GIL
Le GIL permet à un seul thread d'exécuter le bytecode Python à la fois. Le threading est efficace pour les tâches liées aux E/S. Utilisez le multiprocessing pour le parallélisme lié au CPU.
# GIL prevents true parallelism for CPU-bound tasks
import threading
def cpu_work():
total = sum(i**2 for i in range(10**7))
# Use multiprocessing for CPU work
from multiprocessing import Pool
with Pool(4) as p:
p.map(cpu_work, range(4))Snippets Python associés
Copy-paste ready code for common tasks.
Trier un dictionnaire par valeur
Trier un dictionnaire Python par ses valeurs en ordre décroissant.
List comprehension
Générer rapidement des listes en utilisant des list comprehensions.
Fusion de dictionnaires
Plusieurs façons de fusionner des dictionnaires.
Lecture/écriture de fichiers
Diverses façons de lire et écrire des fichiers.
Traitement CSV
Lire et écrire des fichiers CSV avec le module csv.
Traitement JSON
Sérialisation et désérialisation JSON.
Correspondance regex
Effectuer une correspondance regex en utilisant le module re.
Gestion des dates
Gérer les dates et heures avec datetime.
Décorateurs
Définir et utiliser des décorateurs.
Générateurs
Économiser de la mémoire en utilisant des générateurs.
Gestionnaire de contexte
Gestionnaires de contexte personnalisés.
Gestion des exceptions
Mécanisme complet de gestion des exceptions.
Héritage de classe
Héritage de classe et surcharge de méthodes.
Multithreading
Implémenter le multithreading avec le module threading.
Multiprocessing
Atteindre un véritable parallélisme avec multiprocessing.
Programmation asynchrone asyncio
Implémenter la concurrence asynchrone avec asyncio.
Programmation Socket
Serveur et client TCP Socket.
Requêtes HTTP
Envoyer des requêtes HTTP avec la bibliothèque requests.
Opérations sur base de données
Opérer sur des bases de données avec sqlite3.
Environnement virtuel
Créer et gérer des environnements virtuels Python.
Installation pip
Commandes courantes de gestion de packages pip.
Variables d'environnement
Lire et définir des variables d'environnement.
Journalisation
Configurer et utiliser le module logging.
Tests unitaires
Écrire des tests unitaires avec unittest.
Indications de type
Améliorer la lisibilité du code avec des annotations de type.
Dataclass
Simplifier les définitions de classe avec dataclass.
Enum
Définir des types enum avec Enum.
Décorateur property
Contrôler l'accès aux attributs avec property.
Méthodes magiques
Exemples courants de méthodes magiques.
Itérateur
Implémentation d'itérateur personnalisé.
Coroutine
Utilisation de base des coroutines.
Was this helpful?