Read in other languages: English 🇺🇸, Polska 🇵🇱, German 🇩🇪, French 🇫🇷, Spanish 🇪🇸, Українська 🇺🇦.
1. Qu'est-ce que Python et quelles sont ses principales caractéristiques ?
Python est un langage de haut niveau et à usage général, centré sur la lisibilité, la rapidité de développement et une vaste bibliothèque standard.
Principales caractéristiques :
- Syntaxe simple : le code est facile à lire et à maintenir.
- Modèle d'exécution interprété : cycle de modification rapide sans étape de compilation manuelle.
- Multiplateforme : le même code fonctionne sur Linux, macOS et Windows.
- Multiparadigme : approches procédurale, POO et fonctionnelle dans un même projet.
- Écosystème solide :
pip,venv,pyproject.tomlet des milliers de bibliothèques prêtes à l'emploi. - Fonctionnalités modernes de Python 3.10+ : annotations de types,
dataclass,async/await,match/case, générateurs et gestionnaires de contexte.
Exemple de fonction au style production :
from dataclasses import dataclass
@dataclass(slots=True)
class User:
name: str
active: bool
def process_users(users: list[User]) -> list[str]:
return [user.name for user in users if user.active]En bref :
- Python accélère le développement grâce à sa syntaxe simple et à sa vaste bibliothèque standard.
- Le langage convient au back-end, à l'automatisation, aux données/ML, aux tests et au DevOps.
- En Python 3.10+, les pratiques clés sont les annotations de types, l'I/O asynchrone et une bibliothèque standard moderne.
2. Quels sont les principaux avantages de Python ?
Principaux avantages de Python en production :
- vitesse de développement élevée grâce à une syntaxe concise ;
- vaste bibliothèque standard (
pathlib,itertools,collections,asyncio) ; - écosystème mature de paquets pour le back-end, les données, l'automatisation et les tests ;
- portabilité du code entre systèmes d'exploitation ;
- bon support du typage (
typing,mypy,pyright) et des pratiques modernes.
En bref :
- Python réduit le time-to-market.
- Il fournit de nombreux outils prêts à l'emploi sans dépendances supplémentaires.
- Il convient aussi bien aux MVP qu'aux services évolutifs.
3. En quoi Python diffère-t-il des langages compilés ?
Python est généralement exécuté par un interpréteur : le code est compilé en bytecode puis exécuté à l'exécution dans la VM de CPython. Dans les langages compilés (C/C++, Rust), il y a le plus souvent une compilation préalable en code machine.
Conséquences pratiques :
- Python est plus rapide pour le développement et le prototypage.
- Les langages compilés nativement sont généralement plus rapides pour les tâches intensives en CPU.
- En Python, les performances sont souvent améliorées par de meilleurs algorithmes, le profilage et les extensions C.
En bref :
- Python optimise la vitesse de développement.
- La compilation en code machine offre généralement de meilleures performances brutes.
- Le choix dépend du domaine et des exigences de latence et de débit.
4. Que signifie le typage dynamique en Python ?
Le typage dynamique signifie que le type est lié à l'objet, et non au nom de la variable. Un nom peut référencer des valeurs de types différents à des moments différents de l'exécution.
value = 10 # int
value = "10" # strLes types sont vérifiés pendant l'exécution ; les erreurs de type apparaissent donc à l'exécution si aucun analyseur statique de types n'est utilisé.
En bref :
- En Python, le type appartient à l'objet, pas à la variable.
- Le type peut changer lorsqu'un nom est réaffecté.
- Les annotations de types ajoutent un contrôle avant l'exécution du code.
5. Que signifie le typage fort en Python ?
Le typage fort en Python signifie que l'interpréteur n'effectue pas de conversions implicites dangereuses entre types incompatibles.
1 + "2" # TypeErrorPour effectuer des opérations entre types différents, une conversion explicite est nécessaire :
1 + int("2") # 3En bref :
- Python est dynamiquement typé, mais strict quant à la compatibilité des types.
- Les conversions implicites dangereuses sont bloquées par une erreur.
- La conversion explicite rend le comportement prévisible.
6. Qu'est-ce que le REPL et quand l'utilise-t-on ?
Le REPL (Read-Eval-Print Loop) est un mode interactif dans lequel vous saisissez une expression, obtenez immédiatement le résultat et validez rapidement des hypothèses.
Cas d'usage :
- vérifier l'API d'une bibliothèque ;
- tester rapidement une expression ou un algorithme ;
- explorer des données avant de les intégrer dans un module.
Outils : python standard, ipython, ptpython.
En bref :
- Le REPL offre la boucle de retour la plus rapide.
- Il est pratique pour les expériences et le débogage.
- Il ne remplace pas les tests, mais réduit le temps nécessaire pour valider des idées.
7. Que sont les littéraux en Python ? Donnez des exemples de différents types de littéraux.
Un littéral est une valeur fixe écrite directement dans le code.
name = "Ada" # str literal
count = 42 # int literal
ratio = 3.14 # float literal
enabled = True # bool literal
items = [1, 2, 3] # list literal
config = {"retries": 3} # dict literal
flags = {"a", "b"} # set literal
point = (10, 20) # tuple literal
raw = b"abc" # bytes literalEn bref :
- Les littéraux sont des valeurs constantes intégrées au code.
- Chaque type de base possède sa propre syntaxe littérale.
- Ils sont souvent utilisés pour initialiser des données.
8. Que sont les mots-clés en Python ?
Les mots-clés sont des mots réservés du langage qui ont un rôle syntaxique spécial et ne peuvent pas être utilisés comme noms de variables.
Exemples : if, for, class, def, match, case, try, except,
async, await.
Pour afficher la liste :
import keyword
print(keyword.kwlist)En bref :
- Les mots-clés définissent la syntaxe de Python.
- Ils ne peuvent pas être utilisés comme identifiants.
- La liste est disponible via le module
keyword.
9. Qu'est-ce qu'une variable en Python ?
Une variable en Python est un nom (une référence) qui pointe vers un objet en mémoire. L'affectation lie le nom à l'objet ; elle ne "met pas la valeur dans une boîte".
x = [1, 2]
y = x
y.append(3)
# x et y référencent la même listeEn bref :
- Une variable est une référence vers un objet.
- Plusieurs noms peuvent pointer vers le même objet mutable.
- C'est important pour comprendre les copies et les effets de bord.
10. Que sont `pass` et `...` (Ellipsis) en Python ?
pass est une instruction vide : elle ne fait rien, mais permet de définir un bloc vide syntaxiquement valide.
... (Ellipsis) est un objet distinct, Ellipsis. Il est souvent utilisé comme marqueur de "pas encore implémenté", dans les annotations de types et dans des bibliothèques comme NumPy.
def feature() -> None:
pass
PLACEHOLDER = ...En bref :
passest nécessaire pour les blocs de code vides....est une valeur-objet, pas un mot-clé.- Les deux servent de marqueurs temporaires, mais avec une sémantique différente.
11. Qu'est-ce qu'un PEP et comment influence-t-il l'évolution de Python ?
Un PEP (Python Enhancement Proposal) est un document officiel qui décrit une nouvelle fonctionnalité, une norme ou un processus dans l'écosystème Python.
Un PEP passe par :
- une discussion sur la conception ;
- une argumentation technique ;
- une décision d'acceptation ou de rejet ;
- une standardisation des pratiques.
Exemples : PEP 8 (style), PEP 484 (annotations de types), PEP 634 (pattern matching).
En bref :
- Un PEP est le mécanisme d'évolution du langage et de ses outils.
- Il rend les changements transparents et formalisés.
- De nombreuses pratiques modernes de Python ont été établies précisément via les PEP.
12. Qu'est-ce que le PEP 8 et à quoi sert-il ?
Le PEP 8 est le guide de style officiel du code Python : conventions de nommage, formatage, indentation, imports, longueur des lignes et lisibilité des constructions.
À quoi il sert :
- le code a un style cohérent dans toute l'équipe ;
- les code reviews sont plus rapides ;
- il y a moins de bruit dans les diffs ;
- la maintenabilité est meilleure.
En pratique, le style est automatisé avec ruff format ou black + ruff.
En bref :
- Le PEP 8 standardise le style du code Python.
- Il s'agit de lisibilité et de maintenance, pas de performance.
- Il vaut mieux automatiser son respect avec des outils.
13. Quelles nouvelles fonctionnalités sont apparues dans les versions modernes de Python (3.10+) ?
Fonctionnalités clés du Python moderne :
match/case(structural pattern matching) ;- une syntaxe améliorée et de meilleurs messages d'erreur ;
- les types union avec
|(int | None) ; typing.Self,typing.TypeAlias,typing.override;- les génériques au style PEP 695 (
class Box[T]: ...,def f[T](...) -> ...) ; tomllibdans la bibliothèque standard.
En pratique, cela réduit le boilerplate et améliore la fiabilité du code typé.
En bref :
- Python 3.10+ a nettement amélioré la syntaxe et le typage.
match/caseet letypingmoderne sont ce qui a l'impact le plus visible sur le code.- Ces nouvelles fonctionnalités devraient être utilisées par défaut dans le nouveau code.
14. Qu'est-ce qu'une docstring en Python ?
Une docstring est une chaîne de documentation, la première expression d'un module, d'une classe ou d'une fonction. Elle est
accessible via __doc__ et utilisée par les IDE, help() et les générateurs de documentation.
def normalize_email(email: str) -> str:
"""Return lowercase email without surrounding spaces."""
return email.strip().lower()Il est recommandé d'y décrire : ce que fait la fonction, les arguments, la valeur de retour et les exceptions.
En bref :
- Une docstring est une documentation intégrée au code.
- Elle sert de source pour
help()et la génération automatique de documentation. - Une docstring de qualité réduit le besoin de lire l'implémentation.
15. Comment fonctionne l'indentation en Python ?
En Python, l'indentation définit les blocs de code (corps de if, for, def, class).
L'indentation fait donc partie de la syntaxe, et pas seulement du style.
if is_ready:
run_task()
else:
stop_task()Mélanger tabulations et espaces peut provoquer un IndentationError ou une
structure de bloc incorrecte.
En bref :
- L'indentation définit la structure du programme en Python.
- Une mauvaise indentation casse l'exécution.
- Utilisez un style cohérent (4 espaces).
16. Combien d'espaces faut-il pour l'indentation selon le PEP 8 et pourquoi est-il important de garder une indentation cohérente ?
Le PEP 8 recommande 4 espaces par niveau d'indentation.
Pourquoi c'est critique :
- structure de blocs cohérente dans tout le projet ;
- rendu prévisible dans différents éditeurs ;
- moins d'erreurs dues aux conflits tabulations/espaces ;
- diffs plus propres dans Git.
En bref :
- L'indentation standard est de 4 espaces.
- Un style unique élimine toute une classe d'erreurs de formatage.
- Cela améliore directement la lisibilité et la maintenabilité du code.
17. Quelle est la différence entre `ruff` et `black` en termes de fonctionnalités et d'usage ?
black est un formateur de code avec des règles fixes.
ruff est un linter rapide, ainsi qu'un formateur et un auto-correcteur pour de nombreuses règles (PEP 8,
imports, bugs potentiels, simplifications de syntaxe).
Approches pratiques :
- soit
ruff check --fix+ruff format; - soit
ruffpour le lint +blackpour le formatage.
En bref :
blackse concentre sur le formatage.ruffcouvre le linting et une partie des corrections automatiques.- Dans les projets modernes,
ruffseul suffit souvent.
18. Expliquez l'objectif des annotations de types en Python et donnez un exemple de fonction avec annotations de types.
Les annotations de types décrivent les types attendus des arguments et de la valeur de retour. Elles améliorent l'autocomplétion, l'analyse statique et la lisibilité de l'API.
def process_users(users: list[dict[str, object]]) -> list[str]:
return [str(user["name"]) for user in users if bool(user.get("active"))]Les annotations n'effectuent pas elles-mêmes de validation à l'exécution, mais elles aident à trouver
des erreurs au niveau de la CI via mypy/pyright.
En bref :
- Les annotations de types documentent le contrat de la fonction.
- Elles permettent une détection précoce des erreurs via la vérification statique.
- Elles améliorent la qualité des API dans les grandes bases de code.
19. Qu'est-ce que le debugging et pourquoi est-ce une compétence importante pour les programmeurs ?
Le debugging est la recherche systématique de la cause d'un comportement incorrect du code et sa correction. Il ne s'agit pas seulement de "trouver un bug", mais de le reproduire, le localiser et vérifier le correctif.
Cycle de base :
- reproduire le problème ;
- réduire la zone de code concernée ;
- vérifier une hypothèse (avec des logs/un débogueur) ;
- ajouter un test qui fixe le scénario.
En bref :
- Le debugging réduit le MTTR et le nombre de régressions.
- Il fonctionne le mieux comme un processus, pas comme une recherche chaotique.
- La fin du debugging, c'est : correctif + test + vérification finale.
20. Expliquez l'intérêt d'utiliser la fonction `print` pour le débogage. Donnez un exemple de situation où cette approche est utile.
Le print-debugging est un moyen rapide de vérifier les valeurs des variables et l'ordre
d'exécution sans lancer d'outils complexes.
Il est utile quand il faut valider rapidement une hypothèse dans un script local :
def parse_port(raw: str) -> int:
value = raw.strip()
print(f"raw={raw!r} value={value!r}")
return int(value)Pour du code de production, il vaut mieux passer à logging afin d'avoir des niveaux de logs et
une sortie contrôlée.
En bref :
printconvient à un diagnostic local court.- Il montre rapidement l'état des variables au point problématique.
- Pour un diagnostic stable dans un service, utilisez
logging.
21. Décrivez comment utiliser Python Debugger (PDB) pour le débogage. Quels sont les avantages de PDB par rapport à `print` ?
pdb permet de suspendre l'exécution du programme, d'avancer ligne par ligne,
d'inspecter l'état des variables et la pile d'appels.
Utilisation de base :
import pdb
def compute(x: int) -> int:
pdb.set_trace()
return x * 2Commandes clés : n (next), s (step), c (continue), p expr (print expr),
l (list), bt (backtrace).
En bref :
pdbdonne un contrôle interactif de l'exécution.- Il est plus efficace que
printpour les scénarios complexes. - Il permet de diagnostiquer l'état sans encombrer le code de logs.
22. Quelles stratégies peut-on utiliser pour déboguer des boucles et des fonctions en Python ?
Stratégies pratiques :
- isoler le bug avec un exemple minimal reproductible ;
- journaliser les invariants aux itérations de la boucle ;
- placer des breakpoints à l'entrée et à la sortie de la fonction ;
- vérifier les cas limites (données vides,
None, gros volumes) ; - couvrir le chemin problématique par un test.
Pour les boucles, il est utile de journaliser l'index et les états intermédiaires clés.
En bref :
- Commencez par reproduire le problème et en réduire le périmètre.
- Vérifiez les invariants et les conditions limites.
- Finalisez le correctif avec un test qui capture la régression.
23. Quel impact les fonctions de longue durée ont-elles sur les performances du programme ?
Les fonctions de longue durée augmentent la latence, bloquent les workers ou les threads et dégradent le débit du service.
Risques :
- réponses API lentes ;
- timeouts ;
- croissance des files de tâches ;
- moins bonne utilisation du CPU/de l'I/O.
Approche : profiler (cProfile), découper en étapes plus petites, utiliser
des générateurs/du streaming, déplacer les calculs lourds vers multiprocessing ou l'arrière-plan.
En bref :
- Les fonctions longues frappent directement la latence.
- L'optimisation doit se faire à partir du profilage, pas de l'intuition.
- Sur le plan architectural, il vaut mieux séparer les charges CPU-bound et I/O-bound.
24. Qu'est-ce que le logging et pourquoi vaut-il mieux l'utiliser à la place de `print` ?
logging est le mécanisme standard de journalisation d'événements avec des niveaux (DEBUG,
INFO, WARNING, ERROR, CRITICAL), des formats et des handlers (console,
fichier).
Pourquoi c'est préférable à print :
- niveaux de détail contrôlables ;
- format structuré ;
- configuration centralisée ;
- possibilité d'intégration avec une stack d'observabilité.
En bref :
loggingconvient à la production,printnon.- Les niveaux de logs permettent de contrôler le bruit.
- Les logs doivent être structurés et cohérents.
25. Qu'est-ce qu'une traceback ?
Une traceback est la pile d'appels que Python affiche lors d'une exception : où l'exécution a commencé, par quelles fonctions le code est passé et où exactement l'erreur s'est produite.
Usage :
- trouver rapidement le fichier/la ligne source de l'erreur ;
- comprendre le chemin d'exécution jusqu'à l'échec ;
- construire un test précis de reproduction.
En bref :
- La traceback est la "route" vers l'erreur.
- La partie la plus précieuse : les dernières frames de la pile et le type d'exception.
- L'analyse de la traceback accélère la recherche de la cause racine.
26. Quels sont les principaux types de données en Python ?
Principaux types built-in :
- numériques :
int,float,complex; - booléen :
bool; - chaînes/octets :
str,bytes,bytearray; - collections :
list,tuple,set,dict,frozenset; - spéciaux :
NoneType(None).
Il est important de comprendre la mutabilité :
- mutable :
list,dict,set,bytearray; - immuable :
int,str,tuple,frozenset.
En bref :
- Python possède un riche ensemble de types de base "out of the box".
- Le choix de la structure de données influence la complexité des opérations.
- La mutabilité détermine le comportement des copies et les effets de bord.
27. Quelle est la différence entre la division classique (`/`) et la division entière (`//`) en Python ?
/ effectue une division classique et retourne toujours un float.
// effectue une division par plancher : il retourne la partie entière vers le bas (jusqu'à -∞), le type étant généralement
int pour des opérandes entiers.
7 / 2 # 3.5
7 // 2 # 3
-7 // 2 # -4En bref :
/-> résultat réel.//-> arrondi vers le bas à l'entier.- Pour les nombres négatifs,
//n'est pas équivalent à un "tronquage vers zéro".
28. Expliquez l'utilisation de l'opérateur modulo (`%`) en Python. Comment peut-on l'utiliser pour déterminer si un nombre est pair ou impair ?
L'opérateur % retourne le reste de la division.
Vérification de la parité :
def is_even(n: int) -> bool:
return n % 2 == 0Si n % 2 == 0, le nombre est pair ; sinon, il est impair.
Cas typiques : index cycliques, répartition en groupes, calculs calendaires.
En bref :
%retourne le reste.n % 2est la vérification standard de la parité.- Il est souvent utilisé pour la logique cyclique.
29. Comment Python gère-t-il les grands entiers et quelles sont les limites lorsqu'on travaille avec de très grands nombres ?
int en Python a une précision arbitraire, c'est-à-dire qu'il n'est pas limité
à 32/64 bits comme dans beaucoup de langages.
Les limites sont pratiques :
- la mémoire du processus ;
- le temps de calcul pour des valeurs très grandes ;
- le coût des opérations augmente avec le nombre de chiffres.
En bref :
- Il n'y a pas de débordement de
intau sens classique. - La limite, ce sont les ressources de la machine, pas une taille fixe en bits.
- Pour les gros calculs, les algorithmes et le profilage sont essentiels.
30. Pourquoi le traitement de la division par zéro est-il important en Python et comment éviter `ZeroDivisionError` dans le code ?
La division par zéro provoque ZeroDivisionError. Il faut la gérer explicitement si
le diviseur vient d'une entrée externe ou est calculé dynamiquement.
def safe_div(a: float, b: float) -> float | None:
if b == 0:
return None
return a / bDans les scénarios critiques, utilisez try/except et journalisez le contexte.
En bref :
- La division par zéro est une erreur d'exécution contrôlée.
- La prévention la plus simple : vérifier le diviseur avant l'opération.
- Pour les données externes, ajoutez protection et diagnostic.
31. Décrivez l'utilisation du module `random` en Python. Quelles sont les fonctions les plus courantes de ce module ?
random est utilisé pour produire des valeurs pseudo-aléatoires dans les simulations, les
données de test et les tâches non cryptographiques.
Fonctions courantes :
random()— nombre dans[0.0, 1.0);randint(a, b)— entier dans un intervalle ;randrange(start, stop, step)— commerange, mais retourne un élément aléatoire ;choice(seq)/choices(seq, k=...);shuffle(list_)— mélange une liste in-place ;sample(population, k)— échantillon unique.
Pour la cryptographie, utilisez secrets, pas random.
En bref :
randomconvient à l'aléatoire applicatif classique.- Les plus utilisés sont :
randint,choice,shuffle,sample. - Pour les tâches sensibles côté sécurité, il faut
secrets.
32. Comment travailler avec des nombres plus précis en Python ?
Pour les calculs financiers et les calculs décimaux précis, utilisez decimal.Decimal,
et non float.
from decimal import Decimal
total = Decimal("0.1") + Decimal("0.2") # Decimal('0.3')Pour les nombres rationnels, fractions.Fraction est utile.
En bref :
floatest pratique, mais il a des erreurs liées à la représentation binaire.- Pour une arithmétique décimale exacte, choisissez
Decimal. - Le type numérique doit correspondre au domaine du problème.
33. Que sont les caractères d'échappement dans les chaînes Python et comment les utilise-t-on ? Donnez des exemples pour `\n`, `\t`, `\r`, `\"` et `\'`.
Les séquences d'échappement sont des combinaisons spéciales avec \ qui codent des
caractères de contrôle à l'intérieur d'une chaîne.
\n— nouvelle ligne\t— tabulation\r— retour chariot\"— guillemet double dans une chaîne avec"\'— apostrophe dans une chaîne avec'
text = "A\tB\n\"quoted\"\nI\'m here\rX"En bref :
- Les caractères d'échappement contrôlent le formatage de la chaîne.
- Ils permettent d'insérer des guillemets sans casser la syntaxe.
- Pour les chemins/regex, il est souvent pratique d'utiliser des chaînes brutes
r"...".
34. Expliquez l'utilisation des méthodes `strip`, `lstrip` et `rstrip` en Python. Quelle est leur différence ?
Ces méthodes travaillent sur les bords de la chaîne :
strip()— supprime des caractères des deux côtés ;lstrip()— uniquement à gauche ;rstrip()— uniquement à droite.
Par défaut, elles suppriment les espaces blancs, ou un ensemble spécifique de caractères :
" hello ".strip() # "hello"
"--id--".strip("-") # "id"En bref :
- La famille
stripne modifie pas la chaîne in-place, mais retourne une nouvelle chaîne. - La différence se limite au côté nettoyé.
- Ces méthodes sont souvent utilisées sur des données d'entrée.
35. Décrivez l'objectif de la méthode `count` sur les chaînes. Comment fonctionne-t-elle ?
str.count(sub[, start[, end]]) compte le nombre d'occurrences non chevauchantes de la sous-chaîne
sub dans l'intervalle choisi.
"banana".count("an") # 2
"banana".count("a", 2) # 2Elle retourne 0 s'il n'y a aucune occurrence.
En bref :
countdonne rapidement le nombre d'occurrences d'une sous-chaîne.- Elle prend en charge la limitation de recherche via
start/end. - Elle compte les occurrences non chevauchantes.
36. Comment fonctionne la méthode `join` en Python et dans quels cas l'utilise-t-on le plus souvent ?
sep.join(iterable) assemble une séquence de chaînes en une seule chaîne avec le séparateur
sep.
names = ["Ada", "Linus", "Guido"]
result = ", ".join(names) # "Ada, Linus, Guido"Usages typiques : génération de chaînes de type CSV, de logs, de fragments SQL, de chemins d'URL et de messages.
En bref :
joinest le moyen standard et rapide de concaténer des chaînes.- Elle s'appelle sur le séparateur, pas sur la liste.
- Elle est plus efficace que des
+=répétés dans une boucle.
37. Qu'est-ce que le slicing en Python et comment permet-il d'obtenir des sous-chaînes ? Donnez des exemples avec des indices positifs et négatifs.
Le slicing consiste à sélectionner une partie d'une séquence via [start:stop:step].
s = "python"
s[1:4] # "yth"
s[:2] # "py"
s[-3:] # "hon"
s[::-1] # "nohtyp"start est inclus, stop est exclu.
En bref :
- Le slicing fonctionne pour les chaînes, les listes et les tuples.
- Les indices négatifs sont comptés depuis la fin.
- C'est un outil de base pour traiter des séquences sans boucle.
38. Expliquez la méthode `replace` pour les chaînes. Comment l'utiliser pour remplacer plusieurs occurrences d'une sous-chaîne ?
str.replace(old, new, count=-1) retourne une nouvelle chaîne où old est remplacé par
new. Par défaut, toutes les occurrences sont remplacées.
"foo bar foo".replace("foo", "baz") # "baz bar baz"
"foo bar foo".replace("foo", "baz", 1) # "baz bar foo"En bref :
replacene modifie pas la chaîne in-place.- Sans
count, elle remplace toutes les occurrences. countpermet de contrôler le nombre de remplacements.
39. Comment fonctionne la méthode `split` sur les chaînes ?
split(sep=None, maxsplit=-1) découpe une chaîne en liste de sous-chaînes.
- sans
sep, elle découpe sur n'importe quel caractère d'espacement ; - avec
sep, elle découpe selon un séparateur spécifique ; maxsplitlimite le nombre de découpages.
"a,b,c".split(",") # ["a", "b", "c"]
"a b c".split() # ["a", "b", "c"]
"k=v=x".split("=", 1) # ["k", "v=x"]En bref :
splittransforme une chaîne en liste de tokens.sep=Nonea un comportement spécial pour les espaces blancs.maxsplitest utile pour parser des formats key/value.
40. Comment fonctionne la conversion de types (type casting) ?
Le type casting est une conversion explicite d'une valeur entre types via des constructeurs :
int(), float(), str(), bool(), list(), tuple(), set(), dict().
age = int("42")
price = float("19.99")
text = str(10)Des données invalides provoquent une exception (ValueError, TypeError), donc
une validation est nécessaire pour les entrées externes.
En bref :
- Python fournit des fonctions explicites de conversion de types.
- Toutes les valeurs ne peuvent pas être converties sans risque.
- Pour les entrées utilisateur, il faut des vérifications et une gestion des exceptions.
41. Comment Python convertit-il automatiquement différents types de données en valeurs booléennes ?
Dans un contexte booléen, Python applique les règles de truthiness.
Valeurs de type False :
False,None;- nombres nuls :
0,0.0,0j; - collections vides :
"",[],{},set(),tuple(),range(0).
Tout le reste est généralement True.
bool([]) # False
bool("0") # TrueEn bref :
- La conversion booléenne repose sur les règles truthy/falsy.
- Le vide et le zéro donnent
False. - Une chaîne non vide
"0"reste malgré toutTrue.
42. Expliquez comment convertir une chaîne en entier ou en nombre réel en Python. Que se passe-t-il si la chaîne ne peut pas être convertie en nombre ?
Pour la conversion, utilisez int() et float() :
count = int("42")
ratio = float("3.14")Si la chaîne n'est pas valide, Python lève ValueError.
def parse_int(value: str) -> int | None:
try:
return int(value)
except ValueError:
return NoneEn bref :
int()/float()effectuent une conversion explicite depuis une chaîne.- Format invalide ->
ValueError. - Pour les données externes, il faut
try/except.
43. Que fait Python lorsqu'il compare différents types de données, comme des entiers et des réels, ou des entiers et des booléens ?
Python autorise la comparaison numérique de types numériques compatibles.
intetfloatsont comparés par valeur.boolest une sous-classe deint:False == 0,True == 1.
1 == 1.0 # True
True == 1 # True
False < 1 # TruePour les types incompatibles (par exemple int et str), la comparaison d'ordre (<, >)
provoque TypeError.
En bref :
int,floatetboolpartagent une même sémantique numérique.boolse comporte comme0/1dans les comparaisons.- Les types incompatibles ne se comparent pas avec des opérateurs d'ordre.
44. Comment Python gère-t-il les comparaisons entre listes et sets ?
list et set sont des types différents avec une sémantique différente ; une comparaison d'ordre directe
entre eux n'est donc pas prise en charge.
[1, 2] == {1, 2} # False
[1, 2] < {1, 2} # TypeErrorSi vous devez comparer la composition des éléments, normalisez le type :
set([1, 2]) == {1, 2} # TrueEn bref :
listetsetsont comparés comme des structures de données différentes.==entre eux renvoie généralementFalse.- Pour une comparaison pertinente, convertissez-les d'abord vers un même type.
45. Décrivez l'utilisation de la fonction `bool()` en Python.
bool(x) retourne la valeur booléenne de x selon les règles de truthiness.
Scénarios typiques :
- conversion explicite d'une valeur vers
True/False; - conditions plus lisibles ;
- construction de filtres.
bool("text") # True
bool("") # False
bool(0) # FalseEn bref :
bool()unifie les vérifications de type "vide/non vide".- Elle s'appuie sur les règles intégrées truthy/falsy.
- Elle est utile pour la validation et la logique conditionnelle.
46. Quelle est la différence entre `list` et `tuple` ?
La différence principale : list est mutable, tuple est immuable.
Conséquences pratiques :
listconvient aux données qui changent ;tupleconvient aux enregistrements fixes ;tuplepeut être utilisé comme clé de dictionnaire (si ses éléments sont hashables).
coords: tuple[float, float] = (50.45, 30.52)
queue: list[str] = ["task-1", "task-2"]En bref :
listsert aux collections modifiables.tuplesert aux structures de données immuables.- Le choix influence la sûreté de l'API et l'utilisation dans
dict/set.
47. Comment peut-on créer un `tuple` ?
Principales façons :
t1 = (1, 2, 3)
t2 = 1, 2, 3
t3 = tuple([1, 2, 3])
single = (42,) # важлива кома
empty = ()Pour un seul élément, la virgule est obligatoire ; sinon, ce n'est qu'une expression entre parenthèses.
En bref :
- On crée un
tupleavec un littéral ou viatuple(iterable). single = (x,)est un tuple à un seul élément correct.- Le tuple vide :
().
48. Quelle est la différence entre la méthode `reverse` et le slicing `[::-1]` pour inverser une liste ?
list.reverse() modifie la liste existante in-place et retourne None.
lst[::-1] crée une nouvelle liste inversée (une copie).
values = [1, 2, 3]
values.reverse() # values -> [3, 2, 1]
other = values[::-1] # nouvelle listeEn bref :
reverse()modifie l'original.[::-1]retourne un nouvel objet.- Le choix dépend du besoin de conserver les données d'origine.
49. Expliquez le rôle et l'utilisation de la méthode `sort` pour les listes. Comment trier une liste dans l'ordre décroissant ?
list.sort() trie la liste in-place. Elle prend en charge les paramètres :
key— fonction de clé de tri ;reverse=True— ordre décroissant.
nums = [5, 1, 7]
nums.sort(reverse=True) # [7, 5, 1]Si vous avez besoin d'une nouvelle copie triée, utilisez sorted(iterable).
En bref :
sort()modifie la liste sur place.- Pour l'ordre décroissant, utilisez
reverse=True. sorted()est pratique quand il faut conserver l'original.
50. Quelle est la différence entre la méthode `copy` et l'affectation directe d'une liste à une autre ? Pourquoi est-il parfois important d'utiliser `copy` ?
L'affectation ne copie que la référence :
a = [1, 2]
b = a
b.append(3) # a changera aussia.copy() crée une nouvelle liste (superficielle) :
a = [1, 2]
b = a.copy()
b.append(3) # a ne changera pasPour les structures imbriquées, copy.deepcopy peut être nécessaire.
En bref :
=ne copie pas les données, mais partage un seul objet entre plusieurs variables.copy()isole les changements au niveau de la liste supérieure.- Pour les objets imbriqués, utilisez
deepcopy.
51. Que fait la méthode `pop` sur une liste ? En quoi son comportement diffère-t-il selon qu'on indique un index ou non ?
pop() supprime et retourne un élément de la liste.
pop()sans argument supprime le dernier élément ;pop(i)supprime l'élément à l'indexi.
items = ["a", "b", "c"]
last = items.pop() # "c"
first = items.pop(0) # "a"Un index invalide provoque IndexError.
En bref :
popcombine suppression et retour de valeur.- Sans index, elle agit sur la fin de la liste.
- Avec un index, elle supprime une position précise.
52. Comment fusionner deux listes en Python en utilisant l'opérateur `+` et la méthode `extend` ? Quelle est la différence clé entre ces deux approches ?
a + b crée une nouvelle liste, tandis que a.extend(b) modifie la liste a
in-place.
a = [1, 2]
b = [3, 4]
c = a + b # [1, 2, 3, 4], a n'a pas changé
a.extend(b) # a -> [1, 2, 3, 4]En bref :
+retourne un nouvel objet.extend()mute la liste existante.- Le choix dépend du fait qu'il faille ou non conserver l'original.
53. À quoi servent les fonctions `min`, `max`, `sum`, `all`, `any` dans le contexte des listes ?
Ce sont des agrégateurs de base pour les collections itérables :
min()/max()— minimum et maximum ;sum()— somme des nombres ;all()—Truesi tous les éléments sont truthy ;any()—Truesi au moins un élément est truthy.
nums = [3, 10, 1]
min(nums), max(nums), sum(nums) # (1, 10, 14)
all([True, 1, "x"]) # True
any([0, "", None, 5]) # TrueEn bref :
- Ces fonctions réduisent le boilerplate dans les boucles.
- Elles fonctionnent avec n'importe quel iterable.
- Elles se combinent bien avec les générateurs pour un traitement lazy.
54. Qu'est-ce qu'un dictionnaire (`dict`) en Python et en quoi diffère-t-il des autres structures de données ?
dict est un tableau associatif : il stocke des paires clé -> valeur. Les clés doivent être
hashables, les valeurs peuvent être de n'importe quel type.
Différences :
- accès par clé, et non par index ;
- complexité moyenne de recherche/insertion/suppression proche de
O(1); - depuis Python 3.7+, conserve l'ordre d'insertion des clés.
En bref :
dictest optimal pour un accès rapide par clé.- C'est la structure principale pour les configurations et les mappings.
- Les clés doivent être immuables (hashables).
55. Quelle est la différence entre récupérer une valeur d'un dictionnaire via des crochets `[]` et via la méthode `get` ?
d[key] retourne la valeur ou lève KeyError si la clé n'existe pas.
d.get(key, default) retourne la valeur ou default (ou None), sans exception.
config = {"timeout": 30}
config["timeout"] # 30
config.get("retries", 3) # 3En bref :
[]sert pour les clés obligatoires.get()sert pour les champs optionnels.get()réduit le besoin detry/exceptlors de la lecture des données.
56. Décrivez comment mettre à jour et supprimer des éléments dans un dictionnaire.
Mise à jour :
d[key] = value— insérer/mettre à jour une seule clé ;d.update({...})— mise à jour groupée.
Suppression :
del d[key]— supprimer la clé (erreur si absente) ;d.pop(key, default)— supprimer et retourner la valeur ;d.popitem()— supprimer la dernière paire ;d.clear()— vider tout le dictionnaire.
En bref :
- Pour un upsert,
[]etupdate()conviennent. - Pour une suppression sûre, on utilise plus souvent
pop(). - Choisissez l'API selon le comportement souhaité en cas de clé absente.
57. À quoi servent les méthodes `keys`, `values` et `items` dans les dictionnaires ?
Ces méthodes retournent des objets-vues du dictionnaire :
keys()— toutes les clés ;values()— toutes les valeurs ;items()— les paires(key, value).
data = {"a": 1, "b": 2}
for key, value in data.items():
...Les vues sont dynamiques : elles reflètent l'état actuel du dictionnaire.
En bref :
keys/values/itemssont utiles pour l'itération et les vérifications.items()est la plus pratique pour boucler sur la clé et la valeur.- Ce ne sont pas des copies, mais des représentations "vivantes" des données.
58. Comment `dict` est-il implémenté en Python sous le capot ?
dict est implémenté comme une table de hachage avec des optimisations mémoire et d'accès rapide.
La clé est hachée, une case est choisie à partir du hash, et les collisions sont résolues par un
algorithme interne de probing.
Conséquences pratiques :
- accès moyen proche de
O(1); - la qualité de
__hash__et__eq__influence le comportement ; - les objets mutables ne peuvent pas être utilisés comme clés.
En bref :
dictest une structure de hachage haute performance.- Sa rapidité vient du hachage.
- Les clés doivent être hashables et stables.
59. Qu'est-ce qu'une fonction de hachage ?
Une fonction de hachage transforme un objet en entier (hash), utilisé pour
le placement/la recherche rapides dans dict et set.
Conditions de correction :
- si
a == b, alorshash(a) == hash(b); - la valeur du hash doit rester stable pendant toute la durée de vie de l'objet.
En bref :
- La fonction de hachage est la base du fonctionnement rapide de
dict/set. - Elle ne garantit pas l'unicité (des collisions sont possibles).
- Pour les classes personnalisées, la cohérence entre
__eq__et__hash__est essentielle.
60. Qu'est-ce qu'un `set` en Python et en quoi diffère-t-il des autres structures de données ?
set est une collection non ordonnée d'éléments uniques.
Différences :
- supprime automatiquement les doublons ;
- opérations rapides de test d'appartenance (
x in s) ; - prend en charge les opérations ensemblistes : union/intersection/difference.
En bref :
setest optimal pour l'unicité et les tests d'appartenance.- L'ordre des éléments n'est pas garanti.
- Les éléments doivent être hashables.
61. Comment créer un `set` en Python et quelles restrictions existent pour ses éléments ?
Création :
s1 = {1, 2, 3}
s2 = set([1, 2, 2, 3]) # {1, 2, 3}
empty = set() # pas {}Restrictions :
- les éléments doivent être hashables (par exemple
int,str,tuple) ; list,dict,setne peuvent pas être ajoutés directement.
En bref :
set()crée un ensemble vide.- Les doublons sont supprimés automatiquement.
- Seuls les éléments hashables sont autorisés.
62. À quoi sert la méthode `intersection()` d'un set en Python et en quoi diffère-t-elle de `union()` ?
intersection() retourne les éléments communs des ensembles, tandis que union() réunit tous
les éléments uniques des deux ensembles.
a = {1, 2, 3}
b = {3, 4}
a.intersection(b) # {3}
a.union(b) # {1, 2, 3, 4}En bref :
intersection= intersection (commun).union= union (tout l'unique).- Ces deux méthodes sont fondamentales pour comparer des jeux de données.
63. Quels types de données sont immuables ?
Types immuables courants :
int,float,bool,complex;str,bytes;tuple(si ses éléments sont aussi immuables) ;frozenset;NoneType.
En bref :
- Un objet immuable ne peut pas être modifié après sa création.
- Au lieu d'une mutation, un nouvel objet est créé.
- Ces types sont plus sûrs pour les clés de
dictet les éléments deset.
64. Quels types de données sont mutables ?
Types mutables courants :
list;dict;set;bytearray;- la plupart des objets de classes définies par l'utilisateur.
En bref :
- Les objets mutables changent in-place.
- Les mutations peuvent créer des effets de bord via des références partagées.
- Il faut être prudent avec les copies.
65. Pourquoi est-il important de comprendre la mutabilité quand on travaille avec des dictionnaires ou des sets ?
dict et set reposent sur le hachage ; leurs éléments/clés doivent donc être
stables (hashables). Les objets mutables ne peuvent pas être utilisés en toute sécurité comme
clés ou comme éléments d'un set.
De plus, la mutation de valeurs via des références partagées produit souvent des bugs inattendus.
En bref :
- La mutabilité affecte la validité des clés dans
dict/set. - Les objets mutables partagés produisent souvent des effets de bord.
- Les copies explicites et le contrôle des mutations réduisent les bugs.
66. Qu'est-ce que `None` ?
None est une valeur singleton spéciale du type NoneType qui signifie
"absence de valeur".
Scénarios typiques :
- une fonction ne retourne rien explicitement ;
- des paramètres optionnels ;
- un marqueur "les données ne sont pas encore définies".
La vérification se fait avec is :
if value is None:
...En bref :
Nonesignifie l'absence de valeur.- C'est un singleton, donc on le compare avec
is. - Il est souvent utilisé dans une API comme état optionnel.
67. Qu'est-ce que `id` en Python, comment l'utiliser et pourquoi est-ce important ?
id(obj) retourne l'identifiant d'un objet (unique pendant le cycle de vie
de l'objet dans le processus courant).
Utile pour le diagnostic :
- savoir s'il s'agit du même objet ;
- savoir si une copie a été créée ;
- savoir si une mutation d'une référence partagée a eu lieu.
En bref :
idaide à analyser l'identité des objets.- Il est utile pour déboguer les copies et mutations.
- Il n'est pas utilisé comme identifiant métier.
68. Quelle est la différence entre les opérateurs `is` et `==` ?
== compare les valeurs (équivalence), tandis que is compare l'identité
(s'il s'agit ou non du même objet en mémoire).
a = [1, 2]
b = [1, 2]
a == b # True
a is b # FalseImportant à propos des optimisations (interning) : CPython met en cache ("intern") les petits
entiers (de -5 à 256) et les chaînes courtes au moment de la compilation/du chargement. Ainsi,
pour eux, is peut retourner True même s'ils ont été créés séparément. Mais ce sont des
détails d'implémentation sur lesquels il ne faut pas s'appuyer dans la logique métier.
Pour None, utilisez toujours is.
En bref :
==concerne l'égalité des valeurs.isconcerne le même objet en mémoire.- L'interning peut produire un
is Truenon évident pour de petitsintetstr. value is Noneest le seul style correct pour testerNone.
69. Comment le pattern Singleton s'applique-t-il en Python ? Donnez des exemples d'objets singleton en Python.
Singleton signifie une seule instance globale d'un objet dans un processus. En Python, il est souvent remplacé par le niveau du module (les modules ne sont importés qu'une seule fois).
Exemples d'objets singleton du langage :
None;TrueіFalse;Ellipsis.
Dans le code applicatif, au lieu d'un Singleton rigide, on utilise plus souvent un conteneur d'injection de dépendances ou des fabriques pour une meilleure testabilité.
En bref :
- Python possède déjà des objets singleton intégrés.
- Souvent, une portée au niveau du module suffit sans pattern séparé.
- Abuser du Singleton dégrade la testabilité.
70. À quoi servent les opérateurs `break` et `continue` dans les boucles Python ?
break termine la boucle de manière anticipée, continue saute l'itération en cours et
passe à la suivante.
for n in range(10):
if n == 5:
break
if n % 2 == 0:
continueEn bref :
breakarrête complètement la boucle.continuene saute que l'étape actuelle.- Ils rendent le contrôle de la boucle explicite et lisible.
71. Expliquez la notion de boucle infinie. Dans quels cas est-il pertinent d'utiliser une boucle infinie et comment assurer sa terminaison correcte ?
Une boucle infinie est une boucle sans condition naturelle de terminaison, par exemple
while True. Elle est utilisée pour des processus daemon/worker, du polling et de la logique
d'event loop.
Arrêt correct :
- une condition
breakclaire ; - la gestion des signaux d'arrêt ;
- des timeouts et
try/finallypour le nettoyage.
while True:
task = queue.get()
if task is None:
break
handle(task)En bref :
while Trueest approprié pour des boucles de service de longue durée.- Un mécanisme d'arrêt contrôlé est nécessaire.
- Prévoyez toujours la libération des ressources.
72. Décrivez le fonctionnement des boucles imbriquées en Python. Quels problèmes de performance peuvent apparaître lors de leur utilisation et comment les éviter ?
Une boucle imbriquée est une boucle à l'intérieur d'une autre boucle. Souvent, la complexité devient O(n*m)
ou pire, ce qui est critique sur de grands volumes de données.
Optimisations :
- remplacer les recherches dans des listes par
set/dict; - sortir les invariants hors de la boucle interne ;
- utiliser des générateurs,
itertools, la vectorisation ; - profiler les zones "chaudes".
En bref :
- Les boucles imbriquées multiplient rapidement le coût des calculs.
- Les structures de données sont souvent plus importantes que les micro-optimisations.
- Le profilage montre précisément ce qu'il faut optimiser.
73. Qu'est-ce que le structural pattern matching (`match`/`case`) ?
match/case (Python 3.10+) est un mécanisme d'analyse de structures de données par motifs.
Il fonctionne avec des littéraux, des types, des séquences, des dictionnaires et des classes.
def handle(message: dict[str, object]) -> str:
match message:
case {"type": "ping"}:
return "pong"
case {"type": "user", "id": int(user_id)}:
return f"user:{user_id}"
case _:
return "unknown"En bref :
match/casese lit mieux pour des branchements complexes.- Il permet de vérifier la forme et d'extraire des données en même temps.
- Il est particulièrement utile pour les protocoles/événements et le parsing de structures.
74. Dans quels cas le pattern matching est-il meilleur que `if`/`elif` ?
match/case est meilleur lorsqu'il faut :
- vérifier de nombreuses formes de données mutuellement exclusives ;
- déstructurer des structures imbriquées ;
- éviter de longues chaînes de
if/elif.
if/elif est meilleur pour des conditions booléennes simples et une logique courte.
En bref :
- Pour des scénarios structurels,
match/caseest préférable. - Pour des conditions simples,
if/elifsuffit. - Le critère de choix est la lisibilité et la maintenance.
75. Qu'est-ce qu'une fonction en Python ?
Une fonction est un bloc de code invocable nommé qui reçoit des arguments, retourne un résultat et permet d'encapsuler une logique.
def normalize_name(name: str) -> str:
return name.strip().title()Les fonctions prennent en charge les valeurs par défaut, les arguments nommés, *args/**kwargs,
les annotations de types et les décorateurs.
En bref :
- La fonction est l'unité de base de la réutilisation du code.
- Elle définit un contrat clair via les paramètres et la valeur de retour.
- Les annotations de types rendent ce contrat explicite.
76. Quels types d'arguments de fonction existent ?
En Python moderne :
- positional-only (
/) ; - positional-or-keyword ;
- keyword-only (
*) ; - variadic positional (
*args) ; - variadic keyword (
**kwargs).
def f(a, /, b, *, c, **kwargs):
...En bref :
- Python offre un contrôle souple sur la manière d'appeler une fonction.
/et*formalisent le contrat de l'API.*args/**kwargssont utiles pour des interfaces extensibles.
77. Que sont les arguments positionnels et nommés ?
Les arguments positionnels sont passés selon l'ordre, les arguments nommés (keyword) selon le nom
du paramètre.
def connect(host: str, port: int) -> str:
return f"{host}:{port}"
connect("localhost", 5432) # positionnel
connect(host="localhost", port=5432) # nomméEn bref :
- Les arguments positionnels dépendent de l'ordre des paramètres.
- Les arguments nommés améliorent la lisibilité de l'appel.
- Ils peuvent être combinés en respectant les règles de la signature.
78. Que sont les arguments par défaut et quels problèmes peuvent-ils poser ?
Les arguments par défaut sont utilisés si aucune valeur n'est passée lors de l'appel. Ils sont évalués une seule fois lors de la définition de la fonction.
Problème : mutable default.
def add_item(item: int, bucket: list[int] | None = None) -> list[int]:
if bucket is None:
bucket = []
bucket.append(item)
return bucketEn bref :
- Les valeurs par défaut sont pratiques pour des valeurs stables.
- Un mutable default peut accumuler de l'état entre les appels.
- Le pattern sûr :
None+ initialisation à l'intérieur.
79. Expliquez le rôle et l'utilisation de `*args` et `**kwargs` dans les fonctions Python. Quelle est leur différence ?
*args rassemble les arguments positionnels supplémentaires dans un tuple. **kwargs rassemble
les arguments nommés supplémentaires dans un dict.
def log_event(event: str, *args: object, **kwargs: object) -> None:
...Usages : wrappers, adaptateurs d'API, décorateurs, relais de paramètres.
En bref :
*args= arguments positionnels supplémentaires.**kwargs= arguments nommés supplémentaires.- Ils rendent les fonctions flexibles, mais demandent une validation claire.
80. Comment définir une fonction avec des annotations de types en Python ? Donnez un exemple et expliquez les avantages de cette approche.
Les types sont spécifiés dans la signature des paramètres et dans la valeur de retour.
def process_users(users: list[dict[str, object]]) -> list[str]:
return [str(user["name"]) for user in users if bool(user.get("active"))]Avantages :
- meilleure DX (autocomplétion, navigation) ;
- vérification statique dans la CI ;
- contrat explicite pour les autres développeurs.
En bref :
- Les annotations de types documentent l'API.
- Elles réduisent le risque d'erreurs d'intégration.
- Elles sont le plus utiles dans les bases de code moyennes et grandes.
81. Que sont les fonctions lambda ?
lambda est une fonction anonyme à une seule expression. Elle est généralement utilisée pour
de courts callbacks dans sorted, map, filter.
users = [{"name": "Ada"}, {"name": "Bob"}]
users_sorted = sorted(users, key=lambda u: u["name"])Pour une logique complexe, une fonction def classique est préférable.
En bref :
lambdaest pratique pour de courtes expressions locales.- Elle est limitée à une seule expression.
- Pour la lisibilité, il vaut mieux placer le code complexe dans une
def.
82. Quelle est la portée des variables dans une fonction ?
Python utilise la règle LEGB pour rechercher des noms :
Local ;Enclosing (fonctions externes) ;Global (module) ;Builtins.
À l'intérieur d'une fonction, une affectation crée une variable locale si global
ou nonlocal ne sont pas déclarés.
En bref :
- La portée détermine où un nom est accessible et modifiable.
- LEGB explique l'ordre de recherche des variables.
- Une mauvaise compréhension de la portée conduit souvent à
UnboundLocalError.
83. Que sont les variables locales et globales en Python ?
Les variables locales vivent dans le corps d'une fonction. Les variables globales sont définies au niveau du module.
Pour modifier une globale depuis une fonction, global est nécessaire, mais il vaut généralement mieux l'éviter
à cause des dépendances implicites.
En bref :
- Les variables locales sont plus sûres pour la maintenance et les tests.
- Les variables globales simplifient l'accès, mais compliquent le contrôle de l'état.
- Il vaut mieux passer les dépendances en paramètres.
84. Quelle est la différence entre les variables locales et nonlocal en Python ?
nonlocal est utilisé dans une fonction imbriquée pour modifier une variable du
scope englobant le plus proche (et non du scope global).
from collections.abc import Callable
def counter() -> Callable:
value = 0
def inc() -> int:
nonlocal value
value += 1
return value
return incEn bref :
- Une variable locale appartient à la fonction courante.
nonlocalmodifie l'état d'une fonction externe.- C'est un mécanisme clé pour un closure avec état.
85. Qu'est-ce que l'unpacking en Python et comment l'applique-t-on lors d'une affectation ?
L'unpacking consiste à décomposer les éléments d'une séquence/structure dans des variables séparées.
x, y = (10, 20)
first, *middle, last = [1, 2, 3, 4]Il fonctionne aussi avec les dictionnaires dans match/case, les appels de fonctions et les boucles.
En bref :
- L'unpacking rend le code plus compact et plus lisible.
- Il prend en charge la capture "avec étoile" du reste des valeurs.
- Il est souvent utilisé dans le parsing de structures de données.
86. Expliquez la notion de packing de valeurs en Python et donnez des exemples.
Le packing consiste à rassembler plusieurs valeurs dans une seule structure (tuple, list, dict).
point = 10, 20 # tuple packing
def collect(*args: int) -> tuple[int, ...]:
return args*args et **kwargs sont un exemple typique de packing d'arguments.
En bref :
- Le packing rassemble plusieurs valeurs dans un seul conteneur.
- Il est le plus souvent utilisé dans les signatures de fonctions.
- Il se combine bien avec l'unpacking côté appel.
87. À quoi sert l'opérateur `*` dans le packing et l'unpacking ?
Dans les paramètres de fonction, * packe les arguments positionnels (*args), et lors de l'appel,
il unpacke un iterable en arguments positionnels.
def add(a: int, b: int) -> int:
return a + b
nums = [2, 3]
add(*nums) # 5* est aussi utilisé dans une affectation pour capturer "le reste" des éléments.
En bref :
*est un opérateur universel pour travailler avec les varargs.- Dans la signature, il packe ; dans l'appel, il unpacke.
- Il réduit le code répétitif lors du passage de données.
88. Qu'est-ce qu'un closure et quel est son lien avec les decorators ?
Un closure est une fonction interne qui "se souvient" des variables du scope englobant même après la fin de la fonction externe.
Un decorator est généralement implémenté justement via un closure : le wrapper conserve une référence à la fonction d'origine et à des paramètres supplémentaires.
En bref :
- Un closure est une fonction + un contexte capturé.
- Un decorator est souvent une application pratique d'un closure.
- Il permet d'ajouter un comportement sans modifier le corps de la fonction.
89. Qu'est-ce qu'un decorator en Python et comment fonctionne-t-il ?
Un decorator est un callable qui reçoit une fonction/une classe et retourne une version modifiée (un wrapper).
from functools import wraps
def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapperUsages : logging, mise en cache, autorisation, retries, métriques.
En bref :
- Un decorator ajoute un comportement transverse.
- Il fonctionne en enveloppant un callable.
- Il évite de dupliquer la logique technique dans le code métier.
90. Peut-on utiliser plusieurs decorators pour une seule fonction ?
Oui, on peut empiler plusieurs decorators. Ils s'appliquent de bas en haut
(celui le plus proche de def enveloppe en premier).
@decorator_a
@decorator_b
def handler() -> None:
...Équivalent : handler = decorator_a(decorator_b(handler)).
En bref :
- Plusieurs decorators sont autorisés et courants.
- L'ordre d'application a de l'importance.
- La pile de decorators doit être documentée pour rester claire.
91. Décrivez un problème possible lié à l'ordre des decorators lorsqu'ils sont appliqués à une fonction.
Un ordre incorrect peut changer la sémantique : par exemple, mettre le cache avant un contrôle d'accès peut mettre en cache un résultat indésirable ou contourner la logique attendue.
Risques typiques :
- le logging voit des arguments déjà modifiés ;
- les retries enveloppent la mauvaise exception ;
- le cache est appliqué avant/après la validation au mauvais endroit.
En bref :
- L'ordre des decorators influence le comportement de la fonction.
- C'est une cause fréquente de bugs cachés.
- Pour les chaînes critiques, des tests sur l'ordre d'exécution sont nécessaires.
92. Peut-on créer un decorator à l'aide d'une classe ?
Oui. Une classe-decorator implémente __call__ pour que son instance se comporte comme une fonction.
class CallCounter:
def __init__(self, func):
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
return self.func(*args, **kwargs)En bref :
- Un decorator peut être implémenté non seulement par une fonction, mais aussi par une classe.
- Une classe est pratique lorsqu'un état interne est nécessaire.
- Le mécanisme clé est
__call__.
93. Comment définir un decorator qui accepte des paramètres ?
Il faut une structure à trois niveaux : fabrique de decorator -> decorator -> wrapper.
from functools import wraps
def retry(times: int):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times - 1):
try:
return func(*args, **kwargs)
except Exception:
pass
return func(*args, **kwargs)
return wrapper
return decoratorEn bref :
- Un decorator paramétré est une fonction qui retourne un decorator.
- On utilise souvent un closure pour conserver les paramètres.
- Cette approche est pratique pour un comportement configurable.
94. À quoi sert `functools.wraps` dans les fonctions decorator ?
functools.wraps copie les métadonnées de la fonction d'origine dans le wrapper : nom,
docstring, module, ainsi que __wrapped__.
C'est important pour :
- un débogage et des logs corrects ;
- l'introspection et la documentation ;
- la compatibilité avec les outils qui lisent la signature.
En bref :
wrapspréserve "l'identité" de la fonction d'origine.- Sans lui, les fonctions décorées perdent des métadonnées utiles.
- C'est une bonne pratique pour tous les decorators de type wrapper.
95. Comment fonctionnent les dict comprehension, list comprehension et set comprehension ?
Une comprehension crée une collection à partir d'une expression et d'une ou plusieurs boucles, éventuellement avec un filtre.
squares = [x * x for x in range(6)]
mapping = {x: x * x for x in range(6)}
unique = {x % 3 for x in range(10)}Format :
- list :
[expr for x in it if cond] - set :
{expr for x in it if cond} - dict :
{k_expr: v_expr for x in it if cond}
En bref :
- Une comprehension exprime de façon compacte une transformation de données.
- Elle fonctionne pour
list,set,dict. - Elle donne un code plus propre qu'un ajout manuel dans une boucle.
96. Quels sont les avantages des list comprehensions par rapport aux boucles classiques ?
Avantages :
- code plus court et plus déclaratif ;
- moins de variables auxiliaires ;
- généralement des performances légèrement meilleures sous CPython ;
- risque plus faible d'oublier
append.
Inconvénient : pour une logique très complexe, une comprehension dégrade la lisibilité.
En bref :
- Une list comprehension convient bien aux transformations simples.
- Elle est souvent plus rapide et plus propre qu'une boucle manuelle.
- Pour des branches complexes, un
forclassique est préférable.
97. Pouvez-vous donner un exemple de list comprehension imbriquée ?
Exemple de flatten d'une matrice :
matrix = [[1, 2], [3, 4], [5, 6]]
flat = [item for row in matrix for item in row] # [1, 2, 3, 4, 5, 6]Exemple avec condition :
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]En bref :
- Une comprehension imbriquée, c'est plusieurs
fordans une même expression. - L'ordre des
forcorrespond à celui des boucles imbriquées. - Utilisez-la seulement si l'expression reste lisible.
98. Qu'est-ce qui est plus rapide en Python : une list comprehension ou la création d'une liste à l'aide d'une boucle ?
Dans les scénarios typiques, une list comprehension est légèrement plus rapide qu'une boucle avec append, car elle bénéficie
d'un bytecode optimisé et de moins de surcoût.
Important : le gain réel dépend du corps de l'opération ; pour les sections critiques,
il faut donc mesurer (timeit, pyperf).
En bref :
- Souvent plus rapide : la list comprehension.
- La différence peut être faible.
- Pour des décisions de production, basez-vous sur des mesures.
99. Qu'est-ce qu'un iterator en Python ?
Un iterator est un objet qui retourne des éléments séquentiellement et mémorise son état courant. Il implémente le protocole :
__iter__()retourne lui-même ;__next__()retourne l'élément suivant ou lèveStopIteration.
En bref :
- Un iterator donne un accès élément par élément sans charger toutes les données.
- C'est la base des
for, des générateurs et du traitement lazy. - Une fois épuisé, un iterator ne se "rembobine" pas tout seul.
100. Comment créer un iterator à partir d'un objet itérable avec la fonction `iter()` ?
Appelez iter(iterable) pour obtenir un iterator.
items = [10, 20, 30]
it = iter(items)Ensuite, les valeurs sont lues via next(it).
En bref :
iter()transforme un iterable en iterator.- C'est une manière explicite de contrôler l'itération manuellement.
- Elle est utilisée dans le traitement bas niveau de flux de données.
101. À quoi sert la fonction `next()` lorsqu'on travaille avec des iterators ?
next(iterator[, default]) renvoie l'élément suivant d'un iterator. Lorsque
les éléments sont épuisés, elle lève StopIteration ou renvoie default si
celui-ci a été fourni.
it = iter([1, 2])
next(it) # 1
next(it) # 2
next(it, None) # NoneEn bref :
next()donne un contrôle manuel sur le pas d'itération.- Sans
default, la fin du flux provoqueStopIteration. - Avec
default, on peut lire le flux de manière sûre.
102. Peut-on utiliser de façon interchangeable `__next__()` et `__iter__()` avec les fonctions `next()` et `iter()` ?
Oui, mais avec une nuance liée au protocole :
iter(obj)appelleobj.__iter__();next(it)appelleit.__next__().
Autrement dit, les fonctions sont l'interface standard vers ces méthodes dunder et sont généralement utilisées à la place d'un appel direct.
En bref :
iter()correspond à__iter__().next()correspond à__next__().- Dans le code applicatif, il vaut mieux appeler les fonctions intégrées.
103. Quel est le rôle de `StopIteration` dans la conception des iterators et quand cela se produit-il généralement ?
StopIteration signale qu'un iterator est épuisé. La boucle for
l'intercepte automatiquement et termine l'itération.
Cela se produit généralement :
- lors d'un appel à
next(it)après le dernier élément ; - dans des iterators personnalisés, lorsque les données sont terminées.
En bref :
StopIterationest la fin normale du flux.- Il ne faut pas le journaliser comme une « erreur » dans le flux normal.
- Dans vos propres iterators, il faut le lever correctement.
104. Qu'est-ce qu'un generator et en quoi diffère-t-il d'un iterator ou d'une fonction classique ?
Un generator est un iterator spécial créé par une fonction avec yield. Il
génère les valeurs une par une et conserve son état interne entre les appels.
Différences :
- par rapport à une fonction classique : il ne se termine pas par un seul
return, mais fonctionne par « pause / reprise » ; - par rapport à un iterator manuel : l'implémentation est plus simple, sans
classe explicite avec
__next__.
En bref :
- Un generator est la manière la plus pratique de faire de la lazy iteration.
- Il demande moins de code qu'une classe iterator personnalisée.
- Il est particulièrement utile pour de grands flux de données.
105. Comment créer une generator function ?
Il faut définir une fonction avec yield.
def countdown(start: int):
current = start
while current > 0:
yield current
current -= 1L'appel countdown(3) renvoie un objet generator que l'on peut itérer.
En bref :
- La présence de
yieldtransforme la fonction en générateur. - Le generator renvoie les valeurs étape par étape.
- L'état de la fonction est conservé entre les itérations.
106. Comment le mot-clé `yield` rend-il possible le fonctionnement des generators et pourquoi économisent-ils de la mémoire ?
yield renvoie la valeur suivante et « fige » le contexte de la fonction
(variables locales, position d'exécution). Le next() suivant reprend
l'exécution à partir de ce point.
Économie de mémoire : les données ne sont pas créées entièrement à l'avance, mais calculées on demand.
En bref :
yieldmet en œuvre la pause et la reprise de l'exécution.- Un generator prend en charge la lazy evaluation.
- Cela réduit l'empreinte mémoire sur de grands ensembles de données.
107. Quelle est la différence entre `return` et `yield` ?
return termine la fonction et renvoie une seule valeur finale. yield
renvoie une valeur intermédiaire et conserve l'état pour une reprise ultérieure.
Dans un générateur, return signifie la fin de l'itération (StopIteration).
En bref :
return-> termine la fonction.yield-> produit les valeurs étape par étape.yieldest utilisé pour le traitement en flux.
108. Qu'est-ce que l'Object-Oriented Programming (OOP) et quels sont ses principaux principes en Python ?
L'OOP est une approche où les données et le comportement sont réunis dans des objets.
Principes clés :
- encapsulation ;
- héritage ;
- polymorphisme ;
- abstraction.
En Python, l'OOP se combine souvent avec la composition et le duck typing.
En bref :
- L'OOP structure le domaine à travers les classes et les objets.
- Python prend en charge l'OOP de manière souple, sans code trop cérémoniel.
- En pratique, la composition vaut souvent mieux qu'un héritage profond.
109. Qu'est-ce qu'une `class` en Python ?
Une class est un modèle (blueprint) pour créer des objets avec des attributs
et des méthodes.
class User:
def __init__(self, name: str) -> None:
self.name = nameLa classe définit la structure et le comportement des futures instances.
En bref :
- Une classe décrit les données et les opérations qui leur sont associées.
- Les objets (instances) sont créés à partir d'une classe.
- C'est l'unité de base de la modélisation en OOP.
110. Comment créer un object en Python ?
Un objet est créé en appelant la classe :
class User:
def __init__(self, name: str) -> None:
self.name = name
user = User("Ada")Lors de la création, __init__ est exécuté pour initialiser l'état.
En bref :
- Object = instance d'une classe.
- Création :
instance = ClassName(...). __init__configure les attributs initiaux.
111. Que sont les objets et les attributs ?
Un objet est une instance concrète d'un type ou d'une classe. Un attribut est une paire nom-valeur liée à l'objet (donnée ou méthode).
user.name # attribut de données
user.save() # attribut-méthodeEn bref :
- L'objet stocke un état et un comportement.
- Les attributs décrivent cet état et ce comportement.
- L'accès aux attributs se fait avec la notation par point.
112. Quel rôle joue la méthode `__init__()` dans une classe ?
__init__ est l'initialiseur d'instance : il est appelé après la création de
l'objet et remplit son état initial.
class Account:
def __init__(self, owner: str, balance: float = 0.0) -> None:
self.owner = owner
self.balance = balanceEn bref :
__init__définit les attributs initiaux de l'objet.- Il sert de point d'entrée pour configurer l'instance.
- Il ne crée pas l'objet, il l'initialise seulement.
113. À quoi sert le paramètre `self` dans les méthodes d'une classe Python ?
self est une référence à l'instance courante ; la méthode l'utilise pour lire
ou modifier les attributs d'instance.
def deposit(self, amount: float) -> None:
self.balance += amountLe nom self n'est pas réservé par la syntaxe, mais c'est le standard
généralement accepté.
En bref :
selflie la méthode à un objet concret.- Les données d'instance sont accessibles via
self. - C'est le premier paramètre obligatoire d'une méthode d'instance.
114. Comment définir des méthodes dans une classe ?
Les méthodes sont définies comme des fonctions à l'intérieur de class.
class Calculator:
def add(self, a: int, b: int) -> int:
return a + bLes types les plus courants sont : instance (self), classe
(@classmethod, cls) et statique (@staticmethod, sans self/cls).
En bref :
- Une méthode est une fonction définie dans le corps d'une classe.
- Le type de méthode est déterminé par le décorateur et la signature.
- La méthode d'instance est la plus utilisée.
115. Expliquez le concept « tout est objet » en Python et donnez des exemples.
En Python, presque tout est un objet : les nombres, les chaînes, les fonctions, les classes, les modules. Cela signifie que tout possède un type, des attributs et un comportement.
type(10) # <class 'int'>
type(len) # <class 'builtin_function_or_method'>
type(str) # <class 'type'>En bref :
- Un modèle objet unique simplifie le langage.
- Les fonctions et les classes sont aussi des objets de première classe.
- Cela rend Python flexible pour la métaprogrammation.
116. Donnez un exemple de classe avec des méthodes qui effectuent des calculs à partir des attributs.
class Rectangle:
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)En bref :
- Les méthodes peuvent calculer des valeurs à partir des attributs d'instance.
- Cela encapsule la logique métier dans l'objet.
- L'API de la classe devient auto-explicative.
117. Décrivez une situation où l'utilisation de plusieurs classes peut être nécessaire dans un programme Python.
Quand le domaine comporte plusieurs responsabilités, on les répartit entre
plusieurs classes. Par exemple dans l'e-commerce : Order, OrderItem,
PaymentService, InventoryService.
Cela apporte :
- une répartition claire des responsabilités ;
- un couplage plus faible ;
- un remplacement et un test plus simples des composants.
En bref :
- Plusieurs classes sont nécessaires pour modéliser un domaine complexe.
- La séparation des responsabilités améliore la maintenabilité.
- La composition de classes est généralement préférable à un « god object ».
118. Quelle est la différence entre une instance method, une class method et une static method ?
- Instance method : reçoit
selfet travaille avec une instance concrète. - Class method : reçoit
clset travaille avec la classe dans son ensemble. - Static method : ne reçoit ni
selfnicls; c'est une fonction utilitaire dans le namespace de la classe.
En bref :
- Instance -> logique liée à l'instance.
- Classe -> logique de classe / constructeurs alternatifs.
- Statique -> logique auxiliaire sans accès à l'état.
119. Qu'est-ce que `@classmethod` en Python et en quoi diffère-t-il des méthodes ordinaires ?
@classmethod passe la classe (cls) en premier argument, et non l'instance.
Il est souvent utilisé pour les méthodes de fabrique.
class User:
def __init__(self, name: str) -> None:
self.name = name
@classmethod
def from_email(cls, email: str) -> User:
return cls(email.split("@")[0])En bref :
classmethodfonctionne au niveau de la classe.- Il est pratique pour les constructeurs alternatifs.
- Il ne nécessite pas d'instance déjà créée.
120. Qu'est-ce que `@staticmethod` dans les classes Python et quand est-il pertinent de l'utiliser ?
@staticmethod définit une méthode sans self ni cls automatiques. Elle
appartient logiquement à la classe, mais ne dépend pas de son état.
class Math:
@staticmethod
def clamp(value: int, min_v: int, max_v: int) -> int:
return max(min_v, min(value, max_v))En bref :
staticmethodest une fonction dans le namespace de la classe.- Utilisez-la pour une logique auxiliaire sans accès aux attributs.
- Elle ne convient pas si l'état de l'instance ou de la classe est nécessaire.
121. Quelle est la différence entre `@classmethod` et `@staticmethod` dans les classes Python ?
La différence se situe dans le premier argument et dans le niveau d'accès :
@classmethodreçoitclset peut travailler avec l'état de la classe ;@staticmethodne reçoit rien automatiquement.
classmethod est plus souvent utilisé pour les fabriques et les constructeurs
polymorphes, staticmethod pour les utilitaires.
En bref :
classmethodconnaît la classe.staticmethodest isolé de l'état de la classe et de l'instance.- Le choix dépend du besoin d'accéder à
cls.
122. Que sont les attributs d'instance dans les classes Python et en quoi diffèrent-ils des attributs de classe ?
Les attributs d'instance appartiennent à un objet concret (self.x). Les
attributs de classe appartiennent à la classe et sont partagés entre toutes les
instances.
class User:
role = "member" # class attribute
def __init__(self, name: str) -> None:
self.name = name # instance attributeEn bref :
- Les attributs d'instance stockent un état individuel.
- Les attributs de classe stockent une configuration commune.
- Les mutations des attributs de classe affectent toutes les instances.
123. Qu'est-ce que `__slots__` en Python ?
__slots__ limite l'ensemble des attributs autorisés dans une classe et peut
réduire la consommation mémoire en supprimant __dict__ pour les instances.
class Point:
__slots__ = ("x", "y")
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = yNuances d'utilisation :
- Économie de mémoire : les objets occupent nettement moins de place, car
les attributs sont stockés dans un tableau fixe plutôt que dans la table de
hachage
__dict__. - Vitesse : l'accès aux attributs définis dans
__slots__est en général un peu plus rapide. - Absence de
__dict__: vous ne pourrez pas ajouter dynamiquement de nouveaux attributs qui ne figurent pas dans__slots__(sauf si vous ajoutez explicitement"__dict__"à la liste). - Références faibles : si vous souhaitez utiliser
weakref, vous devez ajouter explicitement"__weakref__"à__slots__.
En bref :
__slots__est utile pour des millions d'objets légers, par exemple des nœuds de graphe.- Il supprime
__dict__et__weakref__par défaut. - Il réduit la flexibilité au profit des performances et du contrôle.
124. Que sont les magic methods (méthodes dunder) dans les classes Python et pourquoi les appelle-t-on « magiques » ?
Les méthodes dunder (__init__, __str__, __len__, __eq__, ...) sont des
hooks spéciaux que Python appelle automatiquement en réponse aux opérateurs et
aux built-ins.
On les appelle « magiques » parce qu'elles intègrent votre classe dans le comportement du langage.
En bref :
- Les méthodes dunder définissent le comportement protocolaire de l'objet.
- Elles permettent à vos classes de se comporter « comme des types intégrés ».
- Utilisez-les seulement lorsqu'il existe un besoin sémantique clair.
125. À quoi sert la méthode `__del__` ?
__del__ est un finaliseur qui peut être appelé avant la destruction de
l'objet par le GC. Son comportement n'est pas déterministe ; pour gérer les
ressources, il vaut mieux utiliser des gestionnaires de contexte (with) et une
fermeture explicite.
En bref :
__del__ne garantit pas une exécution au bon moment.- N'appuyez pas un nettoyage critique uniquement sur lui.
- L'approche recommandée est
with/try-finally.
126. Donnez un exemple d'utilisation de la méthode magique `__str__` pour définir la représentation textuelle d'une classe personnalisée en Python.
class User:
def __init__(self, name: str, active: bool) -> None:
self.name = name
self.active = active
def __str__(self) -> str:
status = "active" if self.active else "inactive"
return f"User(name={self.name}, status={status})"str(user) et print(user) utiliseront __str__.
En bref :
__str__fournit une représentation de l'objet compréhensible par un humain.- Il est utile pour les logs et la sortie CLI.
- Il doit être court et lisible.
127. À quoi servent `__str__` et `__repr__` ?
__str__ est destiné au lecteur final. __repr__ est destiné au développeur
et au debug ; il est préférable qu'il soit non ambigu.
print(obj) utilise généralement __str__, alors que le REPL et repr(obj)
utilisent __repr__.
En bref :
__str__sert à un affichage convivial.__repr__sert à une représentation technique.- Une bonne pratique consiste à avoir les deux pour une classe métier.
128. Comment fonctionne l'operator overloading en Python et pourquoi est-ce utile ?
L'operator overloading consiste à implémenter les méthodes dunder des
opérateurs (__add__, __sub__, __eq__, ...) afin que les objets de votre
classe personnalisée prennent en charge les opérateurs.
class Vector2:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __add__(self, other: "Vector2") -> "Vector2":
return Vector2(self.x + other.x, self.y + other.y)En bref :
- Il offre une syntaxe naturelle pour les types métier.
- Il améliore l'expressivité de l'API.
- Il est important de conserver une sémantique mathématiquement attendue.
129. Qu'est-ce que l'inheritance (héritage) ?
L'héritage permet de créer une classe enfant qui hérite des attributs et des méthodes d'une classe de base et peut étendre ou redéfinir le comportement.
En bref :
- L'héritage favorise la réutilisation du code.
- La classe enfant peut redéfinir les méthodes de la classe de base.
- Une hiérarchie excessive complique la maintenance ; la composition est donc souvent préférable.
130. Qu'est-ce que le single inheritance en Python ?
Le single inheritance signifie qu'une classe n'a qu'un seul parent direct.
class Animal:
...
class Dog(Animal):
...C'est la forme d'héritage la plus simple et généralement la plus lisible.
En bref :
- Une classe enfant -> une classe de base.
- Un modèle simple de résolution des méthodes.
- C'est souvent suffisant pour la plupart des modèles métier.
131. Comment implémenter l'héritage dans les classes Python et quelle syntaxe utilise-t-on pour cela ?
Syntaxe : class Child(Base):.
class Base:
def greet(self) -> str:
return "hello"
class Child(Base):
def greet(self) -> str:
return "hi"Si vous devez appeler la logique de base, utilisez super().
En bref :
- L'héritage est défini entre parenthèses après le nom de la classe.
- La classe enfant reçoit l'API de la classe de base.
- La redéfinition permet d'adapter le comportement.
132. Comment accéder aux membres de la classe de base dans une classe enfant ?
L'accès est possible directement via les attributs et méthodes hérités, ou via
super().
class Base:
def greet(self) -> str:
return "hello"
class Child(Base):
def greet(self) -> str:
return super().greet() + " world"En bref :
- Les membres hérités sont disponibles automatiquement dans la classe enfant.
super()appelle correctement la logique de la classe de base.- C'est important pour étendre le comportement plutôt que le dupliquer.
133. À quoi sert la fonction `super()` dans l'héritage Python et comment l'utiliser ?
super() renvoie un proxy vers la classe suivante selon la MRO, afin
d'appeler ses méthodes. Elle est typiquement utilisée dans __init__ et dans
le cadre du cooperative multiple inheritance.
class Child(Base):
def __init__(self, value: int) -> None:
super().__init__()
self.value = valueEn bref :
super()appelle l'implémentation parente sans nommer explicitement la classe.- Elle aide à préserver la cohérence avec la MRO.
- C'est la manière recommandée de travailler avec l'héritage.
134. Décrivez la fonction `isinstance()` en Python et donnez un exemple de son utilisation.
isinstance(obj, cls_or_tuple) vérifie si un objet appartient à un type ou à
l'un de ses sous-types.
isinstance(10, int) # True
isinstance(True, int) # True
isinstance("x", (str, bytes)) # TrueEn bref :
isinstanceest plus sûr quetype(obj) is ...dans du code polymorphe.- Il prend en compte la hiérarchie d'héritage.
- Il prend en charge un tuple de types.
135. Expliquez la fonction `issubclass()` en Python et donnez un exemple de son usage.
issubclass(Sub, Base) vérifie si la classe Sub est une sous-classe de
Base.
class Animal: ...
class Dog(Animal): ...
issubclass(Dog, Animal) # TrueEn bref :
- Cela fonctionne avec des classes, pas avec des instances.
- C'est pratique pour valider une API au niveau des types.
- Cela prend en compte l'héritage transitif.
136. Qu'est-ce que le multiple inheritance (héritage multiple) ?
Le multiple inheritance consiste à faire hériter une classe de plusieurs classes de base.
class A: ...
class B: ...
class C(A, B): ...Cela offre de la flexibilité, mais exige de la discipline dans la conception
des méthodes et dans l'utilisation de super().
En bref :
- Une classe peut hériter d'un comportement provenant de plusieurs sources.
- C'est utile pour une approche par mixins.
- Cela peut compliquer la compréhension de la MRO.
137. Comment fonctionne la MRO (Method Resolution Order) en multiple inheritance ?
La MRO définit l'ordre de recherche des méthodes dans la hiérarchie des classes. En Python, l'algorithme utilisé est la C3 linearization.
Diamond Problem (problème du diamant) : c'est le cas classique où la classe
D hérite de B et C, qui héritent toutes deux de A. La MRO garantit que
A ne sera consultée qu'après tous ses descendants (B et C).
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro())
# [D, B, C, A, object]Vous pouvez consulter cet ordre via ClassName.__mro__ ou ClassName.mro().
En bref :
- La MRO détermine dans quelle classe de base prendre une méthode.
- L'ordre est prévisible et formellement défini (C3).
- Grâce à la MRO, Python gère correctement le problème du diamant.
- Pour le cooperative inheritance, toutes les classes doivent appeler
super().
138. Quels sont les avantages et les inconvénients de l'utilisation du multiple inheritance ?
Avantages :
- réutilisation d'un comportement depuis plusieurs sources ;
- mixins pratiques pour « ajouter » des capacités.
Inconvénients :
- MRO plus complexe ;
- risque de conflits de noms ou de comportement ;
- debug et onboarding plus difficiles.
En bref :
- Le MI est puissant, mais exige des règles de conception strictes.
- Pour la plupart des cas, la composition est plus simple.
- Utilisez le MI surtout pour de petits mixins.
139. Que sont les mixins ?
Un mixin est une petite classe dotée d'un comportement additionnel ciblé, destinée à être combinée par héritage plutôt qu'utilisée seule.
Exemples : TimestampMixin, JsonSerializableMixin.
En bref :
- Un mixin ajoute une seule capacité précise.
- Il n'a généralement pas de cycle de vie complet propre.
- Il se combine bien avec le multiple inheritance.
140. Qu'est-ce que l'encapsulation en Python ?
L'encapsulation consiste à masquer l'implémentation interne derrière une API
publique stable. En Python, cela se fait surtout via des conventions et
property, plutôt que via des modificateurs d'accès stricts.
En bref :
- Le code client travaille avec l'interface, pas avec les détails.
- L'encapsulation réduit le couplage entre les composants.
- Elle facilite l'évolution de l'implémentation interne sans casser l'API.
141. Quelle est la différence entre les accès public, private et protected ?
En Python, il s'agit surtout de conventions de nommage :
public:name— accessible partout ;protected:_name— usage interne par convention ;private:__name— name mangling (_ClassName__name), sans protection absolue.
En bref :
- Python n'a pas de modificateurs d'accès stricts comme Java ou C#.
_nameet__namesont des signaux d'intention pour les développeurs.- Le vrai contrôle d'accès se construit par le design de l'API.
142. Qu'est-ce que le polymorphism (polymorphisme) et comment est-il implémenté en Python ?
Le polymorphisme est la capacité à travailler avec différents objets via une interface commune. En Python, il est souvent implémenté par le duck typing et les protocoles.
def render(obj) -> str:
return obj.to_text()Tout objet disposant d'une méthode to_text convient.
En bref :
- Une interface, plusieurs implémentations.
- En Python, le polymorphisme est souvent comportemental plutôt que hiérarchique.
- Cela facilite l'extension du système avec de nouveaux types.
143. Qu'est-ce que l'abstraction en Python ?
L'abstraction consiste à exposer l'API essentielle tout en masquant les détails d'implémentation inutiles. Le client travaille avec un contrat, pas avec les étapes internes.
En bref :
- L'abstraction réduit la charge cognitive.
- Elle facilite le remplacement d'une implémentation sans changer le code client.
- Elle se met en place via des interfaces, des ABC, des protocoles et des façades.
144. Comment implémenter la data abstraction ?
Approche :
- masquer l'accès direct aux champs internes (
_field) ; - fournir une API contrôlée via des méthodes ou
@property; - valider les invariants dans la logique du setter.
class Temperature:
def __init__(self, celsius: float) -> None:
self.celsius = celsius
@property
def celsius(self) -> float:
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
if value < -273.15:
raise ValueError("invalid temperature")
self._celsius = valueEn bref :
- La data abstraction protège les invariants de l'objet.
propertydonne un accès contrôlé à l'état.- Les détails internes peuvent évoluer sans changer l'API.
145. Que sont les ABC et `@abstractmethod` ?
Une ABC (Abstract Base Class) est une classe de base abstraite du module
abc. @abstractmethod marque une méthode qui doit obligatoirement être
implémentée par une sous-classe.
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, data: bytes) -> None:
...En bref :
- Une ABC formalise le contrat d'une hiérarchie.
@abstractmethodempêche d'instancier une implémentation incomplète.- C'est utile pour des architectures à plugins ou extensibles.
146. Qu'est-ce qu'une property en Python et comment l'utilise-t-on ?
property transforme des méthodes d'accès en une API ressemblant à des
attributs, avec possibilité de validation, de calculs ou de logique lazy.
class User:
def __init__(self, name: str) -> None:
self._name = name
@property
def name(self) -> str:
return self._nameEn bref :
propertypermet de contrôler l'accès sans changer la syntaxe externe.- Elle convient à la validation et aux valeurs dérivées.
- Elle permet de faire évoluer l'API sans casser les clients.
147. Qu'est-ce que `@property` ?
@property est un décorateur pour une méthode getter. Avec @x.setter et
@x.deleter, il forme un attribut contrôlé.
En bref :
@propertypermet de lire une méthode comme un champ.- Il aide à encapsuler l'implémentation interne.
- Il est souvent utilisé pour des API backward-compatible.
148. Qu'est-ce qu'un descriptor en Python ?
Un descriptor est un objet qui implémente __get__, __set__ ou __delete__
et contrôle l'accès aux attributs d'une autre classe.
property, classmethod et staticmethod fonctionnent via le mécanisme des
descriptors.
En bref :
- Un descriptor est un mécanisme bas niveau d'accès aux attributs.
- Il permet de réutiliser une logique de validation ou de proxy pour les champs.
- C'est la base de nombreuses techniques de métaprogrammation en Python.
149. Quelle est la différence entre une property et un descriptor ?
property est un descriptor prêt à l'emploi et de haut niveau pour un seul
attribut. Un descriptor personnalisé est un mécanisme plus général, réutilisable
dans plusieurs champs ou classes.
En bref :
propertyest plus simple et local.- Un descriptor est plus flexible et plus réutilisable.
propertyest en pratique construit sur le protocole des descriptors.
150. Quand vaut-il mieux utiliser une property, et quand un descriptor ?
Utilisez property lorsque la logique concerne un ou deux champs d'une classe
concrète. Utilisez un descriptor lorsque la même logique (validation, casting,
initialisation paresseuse) doit être réutilisée dans plusieurs classes.
En bref :
- Logique locale d'un champ ->
property. - Réutilisation d'une politique d'accès -> descriptor.
- Un descriptor est avantageux dans de grands modèles métier.
151. À quoi servent `setattr()`, `getattr()` et `hasattr()` ? Quelle est la différence entre eux ?
Ce sont des fonctions d'accès dynamique aux attributs :
getattr(obj, name[, default])— lire un attribut ;setattr(obj, name, value)— définir un attribut ;hasattr(obj, name)— vérifier sa présence.
value = getattr(user, "email", None)
if not hasattr(user, "active"):
setattr(user, "active", True)En bref :
getattr/setattr/hasattrsont utiles pour travailler dynamiquement avec des objets.- Ils sont utiles dans le code générique, la sérialisation et les adaptateurs.
- Il ne faut pas en abuser pour ne pas perdre en lisibilité.
152. Expliquez le rôle de la méthode `__set_name__` dans les descripteurs Python et donnez un exemple de son utilisation.
__set_name__(self, owner, name) est appelée lors de la création de la classe
et informe le descripteur du nom de l'attribut auquel il est lié.
class Field:
def __set_name__(self, owner, name): self.name = nameEn bref :
__set_name__initialise le descripteur avec le contexte de la classe.- Il permet de créer des validateurs de champs réutilisables.
- Il s'exécute une seule fois au moment de la création de la classe.
153. Qu'est-ce qu'une `dataclass` et quand faut-il l'utiliser ?
@dataclass génère automatiquement le boilerplate (__init__, __repr__,
__eq__). Elle convient aux modèles de données sans comportement complexe.
from dataclasses import dataclass
@dataclass(slots=True)
class User:
name: str
active: bool = TrueEn bref :
- Une
dataclassréduit le code des modèles de données. - C'est un bon choix pour les DTO et les configurations.
- Pour une validation complexe, d'autres outils sont souvent nécessaires.
154. Quelle est la différence entre `dataclass` et Pydantic ?
dataclass se concentre sur une description pratique de la structure.
Pydantic ajoute la validation à l'exécution, le parsing et la sérialisation des
données.
En bref :
dataclassest plus léger et plus rapide pour les modèles internes.- Pydantic est meilleur pour les entrées externes et les API.
- Le choix dépend du besoin de validation à l'exécution.
155. Que sont les type hints et à quoi servent-ils ?
Les type hints sont des annotations de type dans les signatures et les variables, qui forment un contrat explicite. Ils améliorent l'aide de l'IDE et l'analyse statique.
En bref :
- Les type hints améliorent la clarté de l'API.
- Ils permettent de détecter tôt toute une classe d'erreurs.
- Ils restent utiles même dans un langage dynamique.
156. Comment fonctionne la vérification statique des types (`mypy`) ?
mypy lit les type hints et analyse le code sans l'exécuter, en vérifiant la
compatibilité des types. Il détecte des erreurs d'intégration avant le runtime.
En bref :
mypyapporte à Python une vérification proche du compile-time.- Il fonctionne particulièrement bien dans la CI.
- Les modes stricts réduisent le nombre de bugs en production.
157. Comment garantir la type safety dans un projet Python ?
- ajouter progressivement des type hints dans l'API publique ;
- activer
mypyoupyrightdans la CI ; - utiliser
TypedDict,Protocolet les generics ; - minimiser
Anyet les casts implicites.
En bref :
- La type safety est un processus, pas une action ponctuelle.
- Le plus grand effet vient d'un garde-fou de types dans la CI.
- Une augmentation progressive du niveau de strictness fonctionne mieux qu'un « big bang ».
158. Qu'est-ce que `TypedDict` ?
TypedDict décrit la forme typée d'un dictionnaire : quelles clés sont
attendues et quels sont les types de leurs valeurs.
from typing import TypedDict
class UserPayload(TypedDict): name: str; active: boolEn bref :
TypedDicttype undictavec des clés fixes.- Il est pratique pour les structures proches de JSON.
- Il est vérifié par l'analyseur statique.
159. Qu'est-ce qu'un `Protocol` dans `typing` ?
Protocol décrit un contrat comportemental (structural typing) : un type est
compatible s'il possède les méthodes ou attributs requis, indépendamment de
l'héritage.
En bref :
Protocolimplémente le duck typing dans le typage statique.- Il réduit le couplage fort avec des classes concrètes.
- Il est utile pour des API testables et extensibles.
160. Que sont les generics en Python ?
Les generics permettent d'écrire des types et des fonctions paramétrés par
d'autres types. Dans la syntaxe moderne : class Box[T]: ...,
def first[T](...) -> T.
En bref :
- Les generics rendent les types réutilisables.
- Ils renforcent la type safety des collections et des conteneurs.
- Ils réduisent la duplication du code typé.
161. Qu'est-ce que Pydantic et à quoi sert-il ?
Pydantic est une bibliothèque pour décrire des schémas de données avec validation à l'exécution, conversion de types et sérialisation pratique.
Cas typiques : modèles de requête et de réponse FastAPI, configuration d'application.
En bref :
- Pydantic valide les données externes à l'exécution.
- Il est pratique pour les API et les intégrations.
- Il fournit des erreurs de validation claires et des schémas explicites.
162. Que sont les exceptions en Python ?
Une exception est un objet qui signale une situation erronée ou exceptionnelle pendant l'exécution du code.
En bref :
- Les exceptions interrompent le flux normal.
- Il faut les traiter là où une décision peut être prise.
- Un bon modèle d'erreurs améliore la fiabilité du système.
163. Quels sont les trois types d'erreurs en Python et en quoi diffèrent-ils ?
- Syntax errors : erreurs de syntaxe avant l'exécution ;
- Runtime exceptions : erreurs pendant l'exécution ;
- Logical errors : le code s'exécute, mais le résultat est incorrect.
En bref :
- Les erreurs de syntaxe et d'exécution sont détectées par l'interpréteur.
- Les erreurs logiques sont détectées par les tests et la revue.
- Chaque type d'erreur exige une approche de diagnostic différente.
164. Comment utiliser `try`, `except`, `else` et `finally` ?
try contient l'opération risquée, except traite l'erreur, else s'exécute
si aucune erreur n'a eu lieu, et finally s'exécute toujours (clean-up).
try: data = load()
except FileNotFoundError: data = {}
else: validate(data)
finally: close_connections()En bref :
- Réservez
elseau code du « chemin de succès ». - Utilisez
finallypour les ressources et le nettoyage. - Interceptez des exceptions précises, pas « tout ce qui passe ».
165. Que signifie l'ordre des catégories `except` ?
Les blocs except sont vérifiés de haut en bas, donc les exceptions les plus
spécifiques doivent venir d'abord, et les plus générales (Exception) à la
fin.
En bref :
- L'ordre des
exceptinfluence le gestionnaire qui sera exécuté. - Un
excepttrop large placé en haut « mange » les cas spécifiques. - C'est critique pour un recovery-flow correct.
166. À quoi sert le mot-clé `assert` dans le code Python ?
assert vérifie un invariant et lève AssertionError si la condition est
fausse. Il convient aux vérifications internes du développeur, pas à la
validation des entrées utilisateur.
En bref :
assertdocumente que « ceci doit être vrai ».- Il ne remplace pas la gestion des erreurs en production.
- Utilisez-le pour des contrats dans la logique interne.
167. En quoi `raise` diffère-t-il d'un simple message d'erreur affiché avec `print` en Python ?
raise modifie le flux de contrôle et signale l'erreur à l'appelant. print
se contente d'afficher du texte et n'arrête pas le scénario erroné.
En bref :
raiseest un mécanisme de gestion des erreurs,printne l'est pas.- Les exceptions peuvent être interceptées et journalisées de manière centralisée.
printsert au diagnostic, pas au contrat d'erreur.
168. Comment créer une exception personnalisée (custom exception) ?
Créez une classe qui hérite de Exception (ou d'une exception de base plus
spécifique).
class InvalidOrderError(Exception):
passEn bref :
- Une exception personnalisée rend les erreurs plus expressives pour le domaine.
- Elle permet d'intercepter précisément les cas voulus.
- Elle est préférable à l'utilisation systématique d'un
ValueErroruniversel.
169. Quelle est l'utilité de créer des custom exceptions en Python et comment améliorent-elles la gestion des erreurs ?
Les exceptions personnalisées forment un modèle d'erreurs explicite du domaine et séparent les erreurs techniques des règles métier.
En bref :
- Elles améliorent la lisibilité et la maintenabilité des gestionnaires.
- Elles donnent une sémantique plus précise dans les logs et les API.
- Elles simplifient les tests de scénarios négatifs.
170. Comment les exceptions sont-elles organisées en Python et quelle est la hiérarchie des classes d'exception ?
La hiérarchie part de BaseException. Dans le code applicatif, on travaille
presque toujours avec les descendants de Exception.
Branches principales : ValueError, TypeError, KeyError, OSError,
RuntimeError.
En bref :
- Les exceptions forment un arbre de classes.
- Il vaut mieux intercepter des sous-classes spécifiques.
BaseExceptionn'est généralement pas interceptée dans la logique métier.
171. Quelles approches utiliser pour gérer plusieurs exceptions différentes en Python et pourquoi faut-il les traiter séparément ?
Approches :
- des blocs
exceptséparés pour chaque type ; - le regroupement de cas logiquement identiques :
except (A, B):; - des stratégies de recovery distinctes pour chaque catégorie.
En bref :
- Des exceptions différentes exigent souvent des actions différentes.
- Un traitement séparé réduit les défauts cachés.
- Les logs deviennent plus précis et plus utiles.
172. Pourquoi faut-il parfois relancer une exception (re-raise) en Python et quand est-ce utile ?
Le re-raise (raise sans argument dans un except) permet d'ajouter du
contexte (logs, métriques, nettoyage) et de propager la même erreur vers un
niveau supérieur.
En bref :
- Le re-raise conserve le traceback initial.
- Il est utile pour une gestion centralisée à un niveau supérieur.
- N'avalez pas les erreurs critiques sans raison.
173. Pourquoi faut-il limiter l'utilisation des blocs try-except dans les programmes Python et quel est l'impact sur les performances ?
try/except est nécessaire, mais ne doit pas entourer de grands blocs « au cas
où ». C'est particulièrement coûteux lorsque les exceptions se produisent
souvent (exception-driven flow).
En bref :
- Interceptez seulement les erreurs attendues à l'endroit précis.
- Des exceptions fréquentes dégradent les performances et la lisibilité.
- Préférez des vérifications explicites lorsque c'est pertinent.
174. Comment gérer les erreurs lors du travail avec des fichiers ?
Utilisez with et interceptez les exceptions concrètes (FileNotFoundError,
PermissionError, UnicodeDecodeError, OSError).
try:
with open(path, "r", encoding="utf-8") as f:
content = f.read()
except FileNotFoundError:
...En bref :
withgarantit la fermeture du fichier.- Traitez les erreurs I/O spécifiques.
- Journalisez le contexte (chemin, mode, encodage).
175. Quels sont les modes d'ouverture de fichiers en Python ?
Modes principaux :
rlecture ;wécrasement ;aajout à la fin ;xcréation d'un nouveau fichier ;bmode binaire ;tmode texte (par défaut) ;+lecture + écriture.
En bref :
- Le mode détermine la sémantique de sécurité et de modification du fichier.
wefface le contenu,aconserve l'existant.- Utilisez
bpour les données binaires.
176. Pourquoi est-il important de fermer les fichiers après les opérations et que peut-il se passer si un fichier reste ouvert ?
Un fichier ouvert retient un descripteur du système d'exploitation. Si vous ne le fermez pas :
- fuite de file descriptors ;
- verrous sur les fichiers ou flush incomplet ;
- instabilité dans les processus de longue durée.
En bref :
- La fermeture d'un fichier libère les ressources du système.
withautomatise une fermeture sûre.- C'est critique pour les services et les scripts batch.
177. Quelle est la différence entre `read()`, `readline()` et `readlines()` pour lire des fichiers ?
read()lit tout le fichier (ounoctets/caractères) ;readline()lit une seule ligne ;readlines()lit toutes les lignes dans une liste.
En bref :
read()etreadlines()peuvent être coûteux en mémoire.- Pour les gros fichiers, mieux vaut itérer avec
for line in file. - Le choix dépend du volume et du scénario de traitement.
178. Comment écrire des données dans un fichier en Python et quelle est la différence entre les modes `w` (write) et `a` (append) ?
Écriture :
with open("out.txt", "w", encoding="utf-8") as f:
f.write("hello\n")w réécrit le fichier depuis le début, a ajoute le nouveau contenu à la fin.
En bref :
wefface les anciennes données.aconserve le contenu existant.- Pour les journaux, on utilise généralement
a.
179. Comment travailler efficacement avec de gros fichiers ?
- lire en streaming (ligne par ligne ou par chunks) ;
- éviter de charger tout le fichier en mémoire ;
- utiliser la mise en mémoire tampon et les générateurs ;
- pour les données tabulaires ou en colonnes, choisir les formats et parseurs adaptés.
En bref :
- Le point clé : le streaming au lieu du full-read.
- Les générateurs réduisent l'empreinte mémoire.
- L'algorithme de traitement est plus important que les micro-optimisations.
180. Que sont les gestionnaires de contexte ?
Un gestionnaire de contexte gère une ressource via le protocole __enter__/__exit__ :
ouverture ou initialisation, puis terminaison ou nettoyage garanti.
En bref :
- Il fournit un cycle de vie sûr à la ressource.
- Il fonctionne via
with. - Il réduit le nombre de fuites de ressources.
181. Comment fonctionne la construction `with` ?
with appelle __enter__ à l'entrée dans le bloc et __exit__ à la sortie,
même lorsqu'une erreur s'est produite.
with open("data.txt", "r", encoding="utf-8") as f:
data = f.read()En bref :
withgarantit le nettoyage.- Il rend le travail avec les ressources déclaratif.
- Il est recommandé pour les fichiers, les locks, les transactions et les sessions.
182. Comment créer son propre gestionnaire de contexte ?
Deux approches :
- une classe avec
__enter__/__exit__; - une fonction avec
contextlib.contextmanager.
from contextlib import contextmanager
@contextmanager
def temp_flag():
yieldEn bref :
- Une classe convient à un état complexe.
contextmanagerest pratique pour des scénarios courts.- Les deux garantissent un nettoyage sûr.
183. Comment Python gère-t-il la mémoire ?
CPython utilise le reference counting et un garbage collector cyclique. Il
dispose en plus d'un allocateur interne (pymalloc) pour les petits objets.
En bref :
- Le mécanisme de base est le compteur de références.
- Le GC supprime les références cycliques.
- La mémoire n'est pas toujours libérée instantanément au niveau de l'OS.
184. Qu'est-ce que le reference counting en Python et pourquoi est-ce important pour la gestion de la mémoire ?
Chaque objet possède un compteur de références. Lorsqu'il atteint zéro, l'objet peut être libéré. Cela permet de libérer rapidement la plupart des objets à courte durée de vie.
En bref :
- Le reference counting donne un cycle de vie prévisible aux objets.
- Il ne résout pas à lui seul les références cycliques.
- Avec le GC, il forme un modèle complet de gestion mémoire.
185. Qu'est-ce que le garbage collection en Python ?
Le garbage collection dans CPython détecte et supprime les cycles d'objets inaccessibles qui ne peuvent pas être nettoyés par le seul reference counting.
En bref :
- Le GC complète le reference counting.
- Il est particulièrement important pour les graphes cycliques de références.
- On peut le contrôler via le module
gc.
186. Comment obtenir l'adresse mémoire d'un objet en Python ?
Dans CPython, id(obj) correspond souvent à l'adresse mémoire de l'objet (sous
forme d'entier). C'est un outil de diagnostic, pas un identifiant externe
stable.
En bref :
id()donne l'identité de l'objet.- Dans CPython, c'est généralement une adresse mémoire.
- Ne l'utilisez pas dans la logique métier.
187. À quoi sert la fonction `getrefcount()` du module `sys` et comment fonctionne-t-elle en Python ?
sys.getrefcount(obj) renvoie le compteur de références actuel de l'objet
(dans CPython). La valeur est généralement supérieure de 1 à cause de la
référence temporaire de l'argument.
En bref :
- C'est un outil de diagnostic du comportement mémoire.
- Il est utile pour analyser les fuites de références.
- Il ne faut pas s'y fier dans la logique applicative.
188. Expliquez avec des exemples la différence entre objets mutables et immutables du point de vue du reference counting et de la gestion mémoire.
Les objets immutables ne changent pas, donc une « modification » crée un nouvel objet. Les objets mutables changent in-place, et toutes les références voient la modification.
a = "x"; b = a; a += "y" # новий об'єкт
x = [1]; y = x; y.append(2) # зміна того ж об'єктаEn bref :
- Les objets immutables réduisent les effets de bord.
- Les objets mutables sont plus efficaces pour les modifications in-place.
- La différence est critique pour la copie et le partage de références.
189. Quelle est la différence entre shallow copy et deep copy en Python, et quand utiliser chaque approche ?
Une shallow copy copie seulement le conteneur externe, tandis que les objets imbriqués restent partagés. Une deep copy copie récursivement toute la structure.
import copy
copy.copy(obj)
copy.deepcopy(obj)En bref :
- Une shallow copy suffit pour les structures « plates ».
- Une deep copy est nécessaire pour travailler de manière isolée avec des données mutables imbriquées.
- Une deep copy coûte plus cher en temps et en mémoire.
190. Que sont les modules et les packages en Python ?
Un module est un fichier .py distinct contenant du code. Un package est un
répertoire de modules (espace de noms) qui organise le code en blocs plus
grands.
En bref :
- Module = unité de code.
- Package = structure pour faire évoluer un ensemble de modules.
- La séparation en modules et packages améliore la maintenabilité.
191. Comment importer un module en Python ?
Formes principales :
import module;import module as m;from module import name;from package import submodule.
En bref :
- L'import charge un module et le rend accessible dans le namespace.
- Les alias (
as) améliorent la lisibilité et évitent les conflits. - Il vaut mieux privilégier les imports explicites.
192. Quels types d'imports existe-t-il ?
Types courants :
- absolus ;
- relatifs ;
- sélectifs (
from x import y) ; - avec alias (
as) ; - dynamiques (
importlib).
En bref :
- Le type d'import influence la lisibilité et la maintenance.
- En production, on utilise surtout des imports absolus.
- Les imports dynamiques s'emploient de manière ciblée.
193. Comment fonctionne le système d'import ?
L'interpréteur cherche le module dans sys.path, le charge une seule fois et
le met en cache dans sys.modules. Un import répété réutilise ce cache.
En bref :
- Import = recherche + exécution du module + mise en cache.
- Cela explique pourquoi le code du module s'exécute au premier import.
- Les imports cycliques apparaissent à cause de l'ordre d'initialisation des modules.
194. Quelle est la différence entre un import absolu et un import relatif ?
Un import absolu commence depuis la racine du package (from app.utils import x).
Un import relatif utilise des points (from .utils import x).
En bref :
- Les imports absolus sont plus lisibles et plus stables.
- Les imports relatifs sont pratiques à l'intérieur d'un package, mais moins bons pour le refactoring.
- Pour les grands projets, il vaut mieux standardiser sur les imports absolus.
195. Que fait la fonction `dir()` et comment peut-on l'utiliser avec des modules ?
dir(obj) renvoie la liste des attributs ou noms disponibles sur un objet.
Pour les modules, c'est un moyen rapide d'inspecter l'API.
import math
dir(math)En bref :
dir()aide à explorer interactivement les modules.- Il est utile dans le REPL et pour le debug.
- Il ne remplace pas la documentation officielle.
196. Expliquez le rôle de la construction `if __name__ == "__main__":` dans les scripts Python.
Ce bloc ne s'exécute que lorsque le fichier est lancé comme script, et non importé comme module. Cela sépare le code réutilisable et le point d'entrée CLI.
En bref :
- Il permet d'utiliser le module à la fois comme script et comme import.
- Il évite une exécution non souhaitée lors de l'import.
- C'est un pattern standard pour les scripts.
197. Que signifie le fichier `__init__.py` dans un package Python ?
__init__.py marque un répertoire comme package et peut initialiser l'API au
niveau du package, par exemple en réexportant des classes ou des fonctions.
En bref :
- Il façonne l'interface publique du package.
- Il peut contenir une initialisation minimale.
- Il ne faut pas le surcharger avec une logique lourde.
198. Quelles sont les best practices pour le style des imports en Python ?
- regrouper les imports : stdlib, third-party, local ;
- éviter
from x import *; - garder les imports au début du fichier, sauf cas justifiés de lazy import ;
- utiliser le tri et le formatage (
ruff,isort).
En bref :
- Des imports cohérents améliorent la lisibilité.
- Les imports explicites réduisent les conflits de noms.
- L'automatisation avec des outils élimine les erreurs manuelles.
199. Quels built-in modules populaires existe-t-il en Python ?
Exemples de modules populaires de la bibliothèque standard :
os,sys,pathlib,json,datetime,re,collections,itertools,functools,typing,asyncio,subprocess,logging,argparse.
En bref :
- La bibliothèque standard couvre la plupart des tâches de base.
- Cela réduit le nombre de dépendances externes.
- Il vaut mieux bien connaître la stdlib avant d'ajouter des packages tiers.
200. Que savez-vous du package `collections`, et quels autres built-in modules ont été utilisés ?
collections fournit des structures de haut niveau :
Counter,defaultdict,deque,namedtuple,ChainMap.
Il se combine souvent avec :
itertoolspour les pipelines d'itération ;functoolspour le caching et les outils fonctionnels ;pathlib,json,datetimedans les tâches applicatives.
En bref :
collectionsétend les conteneurs de base avec des structures pratiques.- Il améliore souvent la simplicité et les performances du code.
- Il fonctionne bien avec
itertoolsetfunctools.
201. Que renvoie `sys.argv` ?
sys.argv renvoie une liste d'arguments de ligne de commande :
sys.argv[0]— le nom du script ;- les autres éléments — les arguments passés.
En bref :
sys.argvest l'interface de base des arguments CLI.- Les valeurs arrivent sous forme de chaînes.
- Pour des CLI plus complexes, mieux vaut utiliser
argparse.
202. Quel est le module principal pour travailler avec le système d'exploitation en Python ?
Le module principal est os (avec os.path), et pour une API moderne de
gestion des chemins, pathlib est recommandé.
En bref :
osdonne accès aux API de processus, d'environnement et de système de fichiers de l'OS.pathlibest plus pratique pour travailler avec les chemins.- Dans les vrais projets, on utilise souvent les deux.
203. Comment mélanger les éléments d'une liste avec le module `random` ?
Utilisez random.shuffle(list_) pour un mélange in-place.
import random
items = [1, 2, 3, 4]
random.shuffle(items)En bref :
shufflemodifie la liste d'origine.- Il fonctionne uniquement avec des séquences mutables, comme les listes.
- Pour obtenir une nouvelle copie :
random.sample(items, k=len(items)).
204. Qu'est-ce qu'un virtual environment ?
Un virtual environment est un environnement Python isolé avec ses propres packages et versions de dépendances pour un projet donné.
En bref :
- L'isolation supprime les conflits de dépendances entre projets.
- L'outil standard est
venv. - C'est une pratique de base pour un développement reproductible.
205. Comment fonctionne `pip` ?
pip installe, met à jour et supprime des packages depuis des index
(principalement PyPI), résout les dépendances et les installe dans
l'environnement courant.
En bref :
pipest le gestionnaire de paquets standard de Python.- Il fonctionne dans le venv ou l'interpréteur actif.
- Pour la stabilité, il est important d'épingler les versions.
206. Qu'est-ce que `requirements.txt` ?
requirements.txt est un fichier contenant la liste des dépendances, souvent
avec des versions exactes, utilisé pour une installation reproductible.
En bref :
- Le format est simple et compatible avec
pip install -r. - Il vaut mieux fixer des versions exactes pour la production.
- Il est souvent généré par des outils comme
pip-toolsoupoetry exportà partir d'une liste déclarative ou de lock files.
207. Qu'est-ce que `pyproject.toml` et pourquoi est-il devenu un standard ?
pyproject.toml est un fichier standardisé de configuration du projet : les
métadonnées du package, le build-system et les réglages d'outils comme ruff,
pytest, mypy, etc.
En bref :
- Il centralise la configuration d'un projet Python.
- Il est pris en charge par les outils modernes de packaging.
- Il réduit le nombre de fichiers de configuration dispersés.
208. Comment gérer les dépendances dans un projet Python moderne ?
- isoler l'environnement (
venv) ; - définir les dépendances dans
pyproject.toml; - fixer des lock files ou des constraints pour la reproductibilité ;
- mettre à jour régulièrement avec des vérifications en CI.
En bref :
- La gestion des dépendances doit être reproductible.
- Ne mélangez pas les packages globaux et ceux du projet.
- Faites les mises à jour de manière contrôlée via les tests.
209. Comment organiser correctement la structure d'un grand projet Python ?
- répartir le code en packages métier ;
- avoir des couches séparées :
api,services,domain,infrastructure; - isoler
tests/,scripts/,configs/; - maintenir des frontières de modules explicites et une API publique claire.
En bref :
- La structure doit refléter le domaine, et non des détails techniques accidentels.
- Des frontières claires réduisent les dépendances cycliques.
- Les tests et le tooling doivent être des éléments de première classe dans l'arborescence.
210. Quelle est la différence entre les tests automatisés et les tests manuels, et quels sont les avantages des tests automatisés ?
Le test manuel est effectué par une personne pas à pas. Le test automatisé est exécuté par des scripts ou des frameworks.
En bref :
- Les autotests sont rapides, répétables et adaptés à la CI.
- Les tests manuels sont utiles pour les scénarios UX exploratoires.
- En production, une combinaison des deux approches est nécessaire.
211. Qu'est-ce que le TDD (Test-Driven Development) ?
Le TDD suit un cycle : écrire un test qui échoue -> implémentation minimale -> refactoring.
En bref :
- Le TDD façonne l'API à travers les tests.
- Il donne un feedback rapide sur les régressions.
- Il fonctionne le mieux pour une logique métier modulaire.
212. Quels sont les frameworks de test populaires en Python ?
Les plus populaires sont pytest, unittest (stdlib), ainsi que hypothesis
pour les tests basés sur des propriétés.
En bref :
pytestest le plus souvent choisi pour les nouveaux projets.unittestreste utile comme outil standard de base.hypothesisrenforce la couverture des cas limites.
213. Qu'est-ce que `unittest` en Python ?
unittest est le framework xUnit intégré pour les tests : classes de cas de
test, méthodes d'assertion, setup/teardown et exécuteur de tests.
En bref :
- Il fait partie de la bibliothèque standard.
- Il convient aux environnements conservateurs sans dépendances externes.
- Sa syntaxe est plus « cérémonielle » que celle de pytest.
214. Qu'est-ce que `pytest` et en quoi diffère-t-il de `unittest` par sa syntaxe et ses fonctionnalités ?
pytest est un framework avec une syntaxe simple pour les tests fonctionnels,
des fixtures puissantes, la paramétrisation et un écosystème de plugins.
En bref :
pytestest moins verbeux et plus pratique pour de grands jeux de tests.unittestest class-based et standard dans la stdlib.- Dans les projets modernes,
pytestdomine généralement.
215. Décrivez comment `pytest.raises` est utilisé pour tester l'apparition d'exceptions spécifiques dans du code Python.
pytest.raises(ExpectedError) vérifie qu'un bloc de code lève bien
l'exception attendue.
import pytest
with pytest.raises(ValueError):
int("abc")En bref :
- Il teste explicitement les scénarios négatifs.
- Il protège contre un passage « silencieux » des erreurs.
- Il peut aussi vérifier le message ou les attributs de l'exception.
216. Qu'est-ce que la paramétrisation dans les tests et comment pytest la prend-il en charge avec `@pytest.mark.parametrize` ?
La paramétrisation exécute un même test sur plusieurs jeux de données d'entrée.
Dans pytest, cela se fait avec le décorateur @pytest.mark.parametrize.
En bref :
- Cela réduit la duplication du code de test.
- Il devient facile de faire évoluer les cas.
- La couverture des scénarios d'entrée est plus transparente.
217. Comment définir des noms personnalisés dans pytest pour des tests paramétrés afin d'améliorer la lisibilité ?
Utilisez le paramètre ids dans parametrize.
@pytest.mark.parametrize("value,expected", [(2, True), (3, False)], ids=["even", "odd"])En bref :
idsrend la sortie de l'exécution des tests plus compréhensible.- Il facilite le diagnostic des échecs.
- C'est particulièrement utile lorsqu'il y a beaucoup de cas.
218. Qu'est-ce que l'étape Arrange/Setup en test, et pourquoi est-ce important ?
Arrange/Setup consiste à préparer l'état du test : données, objets, mocks, environnement. Un bon setup rend le test déterministe.
En bref :
- Un setup instable produit des tests flaky.
- Un bon setup isole le test des effets externes.
- Un Arrange clair améliore la lisibilité du scénario.
219. Que comprend l'étape Cleanup/Teardown et pourquoi est-ce important ?
Le teardown supprime tout ce que le test a créé : fichiers temporaires, connexions, mocks, enregistrements de test en base de données.
En bref :
- Le nettoyage garantit l'isolation entre les tests.
- Il réduit les effets de bord et la flakiness.
- Dans pytest, on le fait facilement via les fixture finalizers et les yield-fixtures.
220. Quelle est la différence entre `setUp()` et `setUpClass()` dans unittest ?
setUp() s'exécute avant chaque méthode de test. setUpClass()
(classmethod) s'exécute une seule fois avant tous les tests de la classe.
En bref :
setUpsert à l'isolation test par test.setUpClasssert aux ressources partagées coûteuses.- Un état partagé excessif via
setUpClasspeut compliquer les tests.
221. Qu'est-ce qu'un mock object et comment aide-t-il à améliorer la qualité des tests ?
Un mock object est un objet de substitution qui imite une dépendance et permet de contrôler le comportement ou de vérifier les appels.
En bref :
- Les mocks isolent l'unité des services externes.
- Ils rendent les tests plus rapides et plus stables.
- Ils permettent de vérifier les interactions, pas seulement le résultat.
222. Quelle est la différence entre `mock.patch` dans unittest et `monkeypatch` dans pytest pour le mocking d'objets ?
mock.patch de unittest.mock patche des objets via leur chemin d'import et
fournit une API riche pour vérifier les appels. monkeypatch dans pytest
modifie plus simplement des attributs, des variables d'environnement ou des
dictionnaires pendant le test.
En bref :
patchest plus puissant pour les scénarios de mock assertion.monkeypatchest pratique pour des remplacements de test rapides.- On les combine souvent selon le cas.
223. Quel est le rôle du paramètre `scope` dans les fixtures pytest ?
scope définit le cycle de vie d'une fixture : function, class, module,
package, session.
En bref :
- Un scope plus petit = une meilleure isolation.
- Un scope plus grand = une exécution plus rapide des grands jeux de tests.
- Le choix du scope est un équilibre entre vitesse et indépendance.
224. Qu'est-ce que la complexité d'un algorithme et comment est-elle déterminée ?
La complexité d'un algorithme évalue la croissance du coût en temps et en
mémoire lorsque la taille des données d'entrée n augmente.
En bref :
- On mesure la complexité temporelle et spatiale.
- L'évaluation est généralement asymptotique.
- Cela aide à choisir l'algorithme et les structures de données.
225. Expliquez la notation Big O et son importance dans l'évaluation de la complexité des algorithmes.
Big O décrit la borne supérieure de croissance du coût d'un algorithme lorsque
n devient grand, en ignorant les constantes et les termes inférieurs.
En bref :
- Big O montre le passage à l'échelle, pas le temps exact.
- Elle fournit un langage pour comparer les algorithmes.
- Elle est cruciale pour les performances sur de grands volumes.
226. Quels types de complexité algorithmique rencontre-t-on le plus souvent ?
Les plus fréquents sont : O(1), O(log n), O(n), O(n log n), O(n^2).
En bref :
O(n)etO(n log n)sont les plus courants dans les tâches applicatives.O(n^2)et au-delà deviennent souvent un goulot d'étranglement.- Il faut considérer la mémoire, pas seulement le temps.
227. Donnez des exemples de problèmes efficacement résolus par des algorithmes linéaires (O(n)).
Exemples :
- recherche du maximum ou du minimum ;
- filtrage d'éléments ;
- comptage de fréquences avec
Counter; - vérification de conditions pour chaque élément.
En bref :
- O(n) signifie un seul parcours des données.
- C'est optimal pour de nombreuses agrégations.
- Les algorithmes linéaires passent bien à l'échelle.
228. Comment fonctionne `lru_cache` et quand faut-il l'utiliser ?
functools.lru_cache met en cache les résultats d'une fonction selon ses
arguments et renvoie la valeur déjà calculée lors des appels répétés.
En bref :
- Il est efficace pour des fonctions pures avec des entrées répétées.
- Il ne convient pas aux fonctions ayant des effets de bord.
maxsizecontrôle la taille du cache en mémoire.
229. Comment profiler les performances d'un code Python ?
Outils de base :
timeitpour les micro-mesures ;cProfile/pstatspour un profil au niveau des appels ;py-spy/scalenepour une analyse proche de la production.
En bref :
- N'optimisez qu'après avoir mesuré.
- Profilez des scénarios de charge réalistes.
- Fixez une baseline avant et après les changements.
230. Quels sont les principaux moyens d'optimiser du code Python ?
- choisir les bonnes structures de données ;
- réduire la complexité asymptotique ;
- éviter les copies inutiles ;
- utiliser des générateurs et des pipelines lazy ;
- mettre en cache les calculs coûteux ;
- déplacer les chemins critiques vers C, Rust ou NumPy si nécessaire.
En bref :
- Le plus grand gain vient de l'algorithme, pas d'un tweak syntaxique.
- L'optimisation doit s'appuyer sur le profiling.
- Il ne faut pas sacrifier la lisibilité sans bénéfice mesuré.
231. Quand faut-il utiliser des C extensions ou PyPy ?
Les C extensions sont pertinentes pour des points chauds CPU très ciblés et l'intégration avec des bibliothèques natives. PyPy est pertinent lorsque du code pure Python de longue durée bénéficie du JIT.
En bref :
- C extension : performances maximales au prix d'une complexité de build.
- PyPy : gain potentiel sans réécriture en C.
- Le choix doit se faire sur la base de benchmarks de votre charge réelle.
232. Qu'est-ce que le memory profiling ?
Le memory profiling consiste à mesurer où et combien de mémoire le code consomme au fil du temps afin d'identifier les fuites et les zones coûteuses.
En bref :
- Il met en évidence les points chauds mémoire et les pics.
- Il est utile pour les traitements batch et les charges de données.
- Outils :
tracemalloc,memory_profiler,scalene.
233. Qu'est-ce que le GIL (Global Interpreter Lock) ?
Le GIL est un mécanisme de CPython qui permet à un seul thread d'exécuter du bytecode Python à la fois dans un processus.
En bref :
- Le GIL affecte le multithreading CPU-bound.
- Pour les tâches I/O-bound, les threads restent utiles.
- Pour le parallélisme CPU, on utilise plus souvent le multiprocessing.
234. Comment le GIL de Python affecte-t-il la concurrency dans CPython et quelles conséquences cela a-t-il pour le multithreading ?
À cause du GIL, les threads dans CPython n'exécutent pas le bytecode Python réellement en parallèle pour du code CPU-bound. Ils se relaient de manière coopérative.
Conséquences :
- pour les threads I/O-bound, l'effet est positif car l'attente I/O se recouvre ;
- pour les tâches CPU-bound, le gain des threads est généralement limité.
En bref :
- Le GIL limite le parallélisme des threads dans les scénarios CPU-bound.
- Les threads restent utiles pour le réseau et le disque.
- Pour le CPU, utilisez des processus ou du calcul natif.
235. Comment le GIL affecte-t-il les performances ?
Le GIL gêne peu les tâches I/O-bound, mais il freine le throughput du code Python multithreadé CPU-bound dans un seul processus.
En bref :
- L'effet du GIL dépend du type de charge.
- CPU-bound + threads dans CPython passe souvent mal à l'échelle.
- L'architecture doit être choisie selon le profil des tâches.
236. Expliquez le threading en Python et en quoi il diffère du multiprocessing.
threading lance plusieurs threads dans un même processus avec une mémoire
partagée. multiprocessing lance des processus séparés avec des mémoires
distinctes.
En bref :
- Les threads sont plus légers et pratiques pour l'I/O-bound.
- Les processus offrent un vrai parallélisme CPU.
- Les processus ont un surcoût plus élevé de création et d'IPC.
237. Quand utiliser multiprocessing plutôt que threading ?
Lorsque la tâche est CPU-bound et nécessite plusieurs cœurs dans CPython. Exemples : parsing intensif, traitement d'images ou de vidéo, calculs numériques.
En bref :
- CPU-bound -> généralement
multiprocessing. - I/O-bound -> généralement
threadingouasyncio. - Tenez compte du coût de sérialisation entre les processus.
238. Quelle est la différence entre concurrency et parallelism en programmation, et quand utiliser l'un ou l'autre ?
La concurrency est le chevauchement de tâches dans le temps. Le parallelism est l'exécution physique simultanée de tâches sur plusieurs cœurs.
En bref :
- La concurrency est utile pour les latences I/O.
- Le parallelism est nécessaire pour les calculs intensifs en CPU.
- En Python, le choix de l'outil dépend du type de goulot d'étranglement.
239. Qu'est-ce que la concurrency en Python ?
La concurrency en Python est l'organisation de l'exécution de plusieurs tâches afin qu'elles progressent ensemble, via des threads, asyncio ou des processus.
En bref :
- Il s'agit de gérer de nombreuses tâches, pas forcément en parallèle.
- Elle permet d'augmenter le débit des scénarios I/O.
- Elle demande une conception soignée de la synchronisation et de l'annulation.
240. Quelle est la différence entre les tâches IO-bound et CPU-bound ?
Les tâches IO-bound attendent surtout le réseau, le disque ou la base de données. Les tâches CPU-bound passent surtout du temps dans les calculs du processeur.
En bref :
- L'IO-bound passe bien à l'échelle via asyncio ou les threads.
- Le CPU-bound passe mieux à l'échelle via multiprocessing ou du code natif.
- Identifiez d'abord le goulot d'étranglement par le profiling.
241. À quoi sert le module `asyncio` en Python et comment permet-il de mettre en œuvre la programmation asynchrone ?
asyncio fournit un event loop, l'ordonnancement des tâches et des primitives asynchrones
pour une concurrence coopérative dans les tâches I/O-bound.
En bref :
- Il permet de gérer efficacement de nombreuses opérations I/O.
- Il repose sur
async/await. - Il convient bien aux services et clients réseau.
242. Quelle est la différence entre programmation synchrone et asynchrone en Python ?
Synchrone : un appel bloque le thread courant jusqu'à sa fin. Asynchrone :
await rend la main à l'event loop pendant que l'opération attend l'I/O.
En bref :
- L'async réduit le temps d'inactivité pendant l'I/O.
- Le sync est plus simple pour une logique linéaire.
- L'async ajoute de la complexité dans la gestion des tâches et de la cancellation.
243. Que sont `async` / `await` ?
async def définit une fonction coroutine. await suspend la coroutine
jusqu'à ce que l'awaitable soit prêt et rend le contrôle à la loop.
En bref :
- C'est la syntaxe du modèle asynchrone coopératif.
- Elle s'utilise avec
asyncioet les bibliothèques async. awaitn'est possible qu'à l'intérieur d'unasync def.
244. Comment fonctionne `asyncio` ?
asyncio exécute un event loop qui traite les tâches (coroutines), en basculant
aux points await et en planifiant les opérations I/O prêtes.
Règle critique : l'event loop fonctionne dans un seul thread. Toute
opération bloquante (time.sleep(), requêtes synchrones via requests,
calculs lourds) arrête toute la boucle et toutes les autres tâches.
En bref :
- Un seul thread peut servir de nombreuses tâches I/O grâce à la coopération.
- L'ordonnancement n'est pas préemptif.
- Les appels bloquants détruisent les performances d'asyncio.
245. Qu'est-ce qu'un event loop ?
L'event loop est un ordonnanceur qui surveille les événements et la disponibilité de l'I/O, puis lance les callbacks ou coroutines correspondants.
En bref :
- C'est le composant central du modèle asyncio.
- Il gère le cycle de vie des tâches async.
- Il détermine quand chaque coroutine reprend son exécution.
246. Comment `asyncio` permet-il la programmation asynchrone et quels sont les principaux composants impliqués dans du code asyncio ?
Composants clés :
- event loop ;
- coroutine (
async def) ; - task (
asyncio.create_task) ; - awaitables (futures, tasks, coroutines) ;
- primitives de synchronisation (
Lock,Queue,Semaphore).
En bref :
asyncioréunit l'ordonnancement et l'API async dans un modèle unique.- Les tâches partagent un seul thread d'exécution de manière coopérative.
- L'architecture doit prendre en compte les timeouts, le retry et la cancellation.
247. Quand `asyncio` n'apporte-t-il pas d'avantage ?
Lorsque la charge est CPU-bound ou que les bibliothèques principales sont bloquantes et n'ont pas d'API async. Cela ne se justifie pas non plus pour des scripts simples et courts.
Solution pour le code bloquant : si vous devez utiliser une bibliothèque
bloquante dans un environnement async, utilisez
loop.run_in_executor(None, sync_func), qui l'exécutera dans un thread séparé
sans bloquer l'event loop.
En bref :
- L'async n'accélère pas les calculs purs.
- Sans bibliothèques I/O non bloquantes, le gain est minimal.
run_in_executoraide à intégrer du code legacy ou sync.- La complexité de l'async doit être justifiée par la charge.
248. Comment fonctionne l'annulation dans asyncio ?
L'annulation d'une tâche (task.cancel()) lève CancelledError dans la
coroutine. Le code doit gérer correctement le nettoyage dans try/finally.
En bref :
- La cancellation fait partie du flux de contrôle normal en async.
- Il faut intégrer sa gestion dans le design des coroutines.
- L'ignorer conduit à des tâches « bloquées ».
249. Qu'est-ce que `contextvars` ?
contextvars fournit des variables locales au contexte, sûres pour des
scénarios async ou multithread. C'est utile pour les request-id,
correlation-id ou tenant-context.
En bref :
- C'est une alternative à l'état global dans du code concurrent.
- La valeur est isolée par contexte.
- Cela améliore le traçage et l'observability.
250. Quelles best practices faut-il appliquer lors de l'écriture de code Python ?
- respecter PEP 8 et automatiser le formatage ;
- écrire des type hints explicites pour l'API publique ;
- utiliser
withpour les ressources ; - couvrir la logique métier par des tests ;
- éviter l'optimisation prématurée et profiler ;
- garder les modules petits avec une responsabilité claire ;
- gérer les dépendances via
pyproject.toml+ une stratégie de lock.
En bref :
- La lisibilité et la prévisibilité sont plus importantes que les « astuces ».
- La qualité repose sur l'automatisation : lint, types, tests, CI.
- La simplicité architecturale réduit le coût de maintenance.