Apprends Python — chaque question, chaque réponse
Les 125 questions sur 5 niveaux avec extraits de code, réponses acceptées et explications détaillées. Gratuit, sans inscription, entièrement indexé.
Une référence complète pour Python : des types et du flux de contrôle de base aux dataclasses, décorateurs et contextlib, jusqu'à asyncio, FastAPI et Pydantic. Chaque entrée montre le code exécutable, la bonne réponse et la règle qui justifie ce résultat.
python-basic · Bases · 25
pb1Qu'affiche ce code ?
x = 7
y = 2
print(x // y)- A3.5
- B3✓ Bonne réponse
- C4
- D3.0
Pourquoi: L'opérateur // effectue une division entière, renvoyant 3 avec deux entiers.
pb2Quel identifiant n'est PAS valide en Python ?
- A_value
- Bmy_var2
- C2nd_place✓ Bonne réponse
- DCamelCase
Pourquoi: Les identifiants ne peuvent pas commencer par un chiffre ; lettre ou underscore requis.
pb3Quel type est affiché ?
print(type(3 / 2))- A<class 'int'>
- B<class 'float'>✓ Bonne réponse
- C<class 'number'>
- D<class 'double'>
Pourquoi: L'opérateur / renvoie toujours un float en Python 3, même avec des entiers.
pb4Qu'affiche ce code ?
s = " Hello "
print(s.strip().upper())- A HELLO
- BHELLO✓ Bonne réponse
- CHello
- D Hello
Pourquoi: .strip() retire les espaces autour, puis .upper() met en majuscules.
pb5Qu'affiche ce slice ?
s = "python"
print(s[1:4])- Apyt
- Byth✓ Bonne réponse
- Cytho
- Dthon
Pourquoi: Le slice s[1:4] prend les indices 1, 2, 3 — la fin est exclusive.
pb6Qu'affiche cette f-string ?
name = "Ada"
print(f"Hi, {name}!")- AHi, {name}!
- BHi, Ada!✓ Bonne réponse
- CHi, "Ada"!
- DSyntaxError
Pourquoi: Les f-strings interpolent la valeur des expressions entre accolades.
pb7Qu'affiche ce code ?
xs = [1, 2]
xs.append([3, 4])
print(len(xs))- A2
- B3✓ Bonne réponse
- C4
- DTypeError
Pourquoi: append ajoute un seul élément (la liste interne) — la longueur devient 3.
pb8Qu'affiche ce code ?
xs = [1, 2, 3]
xs.extend([4, 5])
print(xs)- A[1, 2, 3, [4, 5]]
- B[1, 2, 3, 4, 5]✓ Bonne réponse
- C[4, 5, 1, 2, 3]
- D[1, 2, 3]
Pourquoi: extend itère l'argument et ajoute chaque élément individuellement à la liste.
pb9Que se passe-t-il à l'exécution ?
t = (1, 2, 3)
t[0] = 9
print(t)- AAffiche (9, 2, 3)
- BAffiche (1, 2, 3)
- CTypeError✓ Bonne réponse
- DIndexError
Pourquoi: Les tuples sont immuables ; l'affectation à un index lève TypeError.
pb10Qu'affiche ce code ?
d = {"a": 1, "b": 2}
print(d.get("c", 0))- ANone
- BKeyError
- C0✓ Bonne réponse
- D1
Pourquoi: dict.get(clé, défaut) renvoie la valeur par défaut si la clé est absente.
pb11Que vérifie l'opérateur in sur un dict ?
d = {"x": 1}
print("x" in d)- AAppartenance des valeurs
- BAppartenance des clés✓ Bonne réponse
- CAppartenance des paires
- DToujours False
Pourquoi: Pour les dicts, l'opérateur in teste l'existence d'une clé, pas des valeurs.
pb12Qu'affiche ce code ?
s = {1, 2, 2, 3, 3, 3}
print(len(s))- A6
- B3✓ Bonne réponse
- C2
- D1
Pourquoi: Les sets ne contiennent que des éléments uniques ; les doublons disparaissent.
pb13Qu'affiche ce code ?
vals = [0, "", None, [], "x"]
truthy = [v for v in vals if v]
print(len(truthy))- A5
- B4
- C1✓ Bonne réponse
- D0
Pourquoi: 0, "", None et [] sont falsy ; seul "x" passe le filtre.
pb14Qu'affiche ce code ?
x = 10
if x > 20:
print("A")
elif x > 5:
print("B")
else:
print("C")- AA
- BB✓ Bonne réponse
- CC
- DBC
Pourquoi: Seule la première branche correspondante s'exécute ; 10 > 5 donne "B".
pb15Qu'affiche ce code ?
total = 0
for i in range(1, 5):
total += i
print(total)- A15
- B10✓ Bonne réponse
- C6
- D4
Pourquoi: range(1, 5) produit 1, 2, 3, 4 (fin exclusive) ; la somme est 10.
pb16Quelle est la première ligne affichée ?
names = ["a", "b", "c"]
for i, n in enumerate(names):
print(i, n)- A1 a
- B0 a✓ Bonne réponse
- Ca 0
- D0 "a"
Pourquoi: enumerate produit des paires (index, valeur) en commençant à 0 par défaut.
pb17Qu'affiche ce code ?
a = [1, 2, 3]
b = ["x", "y"]
print(list(zip(a, b)))- A[(1, 'x'), (2, 'y'), (3, None)]
- B[(1, 'x'), (2, 'y')]✓ Bonne réponse
- C[1, 'x', 2, 'y', 3]
- DErreur
Pourquoi: zip s'arrête au plus court itérable ; le troisième élément de a est ignoré.
pb18Qu'affiche cette compréhension ?
squares = [x*x for x in range(4)]
print(squares)- A[1, 4, 9, 16]
- B[0, 1, 4, 9]✓ Bonne réponse
- C[0, 1, 2, 3]
- D[1, 2, 4, 8]
Pourquoi: range(4) produit 0, 1, 2, 3 ; les carrés donnent [0, 1, 4, 9].
pb19Qu'affiche ce code ?
def greet(name, greeting="Hi"):
return f"{greeting}, {name}"
print(greet("Sam"))- AHi, Sam✓ Bonne réponse
- BSam, Hi
- CTypeError
- DHi, name
Pourquoi: Les arguments par défaut sont utilisés si non fournis ; greeting vaut "Hi".
pb20Qu'affiche ce code ?
def total(*args, **kwargs):
return sum(args) + sum(kwargs.values())
print(total(1, 2, x=3, y=4))- A3
- B7
- C10✓ Bonne réponse
- DTypeError
Pourquoi: *args reçoit (1, 2) et **kwargs {x:3, y:4} ; les sommes font 3 + 7 = 10.
pb21Qu'affiche ce code ?
def f():
pass
print(type(f()).__name__)- ANone
- BNoneType✓ Bonne réponse
- Cfunction
- Dvoid
Pourquoi: Une fonction sans return renvoie None, dont la classe s'appelle NoneType.
pb22Qu'affiche ce code ?
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b, a is b)- ATrue True
- BTrue False✓ Bonne réponse
- CFalse True
- DFalse False
Pourquoi: == compare les valeurs (égales) ; is teste l'identité — objets différents.
pb23Qu'affiche ce code ?
print(isinstance(True, int))- ATrue✓ Bonne réponse
- BFalse
- CTypeError
- DNone
Pourquoi: En Python, bool est une sous-classe de int, donc isinstance(True, int) vaut True.
pb24Qu'affiche ce code ?
try:
x = int("abc")
except ValueError:
x = -1
print(x)- A0
- B-1✓ Bonne réponse
- Cabc
- DValueError
Pourquoi: int("abc") lève ValueError, capturée, et x devient -1.
pb25Qu'affiche ce code ?
import math
print(math.sqrt(16))- A4
- B4.0✓ Bonne réponse
- C16
- DImportError
Pourquoi: math.sqrt renvoie toujours un float ; sqrt(16) affiche 4.0, pas 4.
python-medium · Intermédiaire · 25
pm1Qu'est-ce que cela affiche ?
class Dog:
species = 'canine'
def __init__(self, name):
self.name = name
a = Dog('Rex')
Dog.species = 'wolf'
print(a.species)- Acanine
- Bwolf✓ Bonne réponse
- CRex
- DAttributeError
Pourquoi: species est un attribut de classe ; le modifier sur la classe se reflète sur les instances.
pm2Qu'est-ce que cela affiche ?
class A:
def hi(self): return 'A'
class B(A):
def hi(self): return 'B' + super().hi()
print(B().hi())- AB
- BA
- CBA✓ Bonne réponse
- DAB
Pourquoi: B.hi renvoie "B" concaténé à super().hi() (A.hi -> "A"), donnant "BA".
pm3Qu'est-ce que cela affiche ?
class Temp:
def __init__(self, c):
self._c = c
@property
def f(self):
return self._c * 9/5 + 32
t = Temp(100)
print(t.f)- A212.0✓ Bonne réponse
- B212
- C<property>
- DTypeError
Pourquoi: @property permet f sans parenthèses ; 100*9/5+32 vaut 212.0 (division réelle).
pm4Qu'est-ce que cela affiche ?
class Math:
@staticmethod
def add(a, b): return a + b
@classmethod
def name(cls): return cls.__name__
print(Math.add(2, 3), Math.name())- A5 Math✓ Bonne réponse
- B5 cls
- CTypeError
- D23 Math
Pourquoi: @staticmethod n'a pas d'argument implicite ; @classmethod reçoit cls lié à la classe.
pm5Qu'est-ce que cela affiche ?
def shout(fn):
def wrap(*a, **k):
return fn(*a, **k).upper()
return wrap
@shout
def greet(n): return f'hi {n}'
print(greet('ann'))- AHI ANN✓ Bonne réponse
- Bhi ann
- CHI ann
- DTypeError
Pourquoi: Le décorateur enveloppe greet ; "hi ann" passe en majuscules en "HI ANN".
pm6Quel est l'ordre de sortie ?
class Open:
def __enter__(self):
print('in'); return self
def __exit__(self, *a):
print('out')
with Open():
print('mid')- Ain, mid, out✓ Bonne réponse
- Bmid, in, out
- Cin, out, mid
- Dout, mid, in
Pourquoi: __enter__ d'abord, puis le corps du with, enfin __exit__ à la fin du bloc.
pm7Qu'est-ce que cela affiche ?
def gen():
yield 1
yield 2
yield 3
g = gen()
print(next(g), next(g))- A1 2✓ Bonne réponse
- B1 1
- C2 3
- D1 2 3
Pourquoi: Chaque next() avance le générateur au prochain yield, renvoyant 1 puis 2.
pm8Qu'est-ce que cela affiche ?
try:
raise ValueError('a')
except ValueError:
print('v')
else:
print('e')
finally:
print('f')- Av puis f✓ Bonne réponse
- Bv puis e puis f
- Ce puis f
- Dseulement f
Pourquoi: else ne s'exécute que sans exception ; finally s'exécute toujours.
pm9Qu'est-ce que cela affiche ?
class C:
def __init__(self): self.i = 0
def __iter__(self): return self
def __next__(self):
if self.i >= 2: raise StopIteration
self.i += 1
return self.i
print(list(C()))- A[1, 2]✓ Bonne réponse
- B[0, 1]
- C[1, 2, 3]
- D[]
Pourquoi: list() itère jusqu'à StopIteration ; 1 et 2 sont renvoyés avant que i atteigne 2.
pm10Qu'est-ce que cela affiche ?
nums = [1, 2, 3, 4, 5]
print([x*x for x in nums if x % 2])- A[1, 9, 25]✓ Bonne réponse
- B[1, 4, 9, 16, 25]
- C[4, 16]
- D[1, 3, 5]
Pourquoi: La compréhension garde les impairs et les carre : 1, 9, 25.
pm11Qu'est-ce que cela affiche ?
nums = [1, 2, 3, 4]
print(list(filter(lambda x: x > 2, nums)))- A[3, 4]✓ Bonne réponse
- B[1, 2]
- C[2, 3, 4]
- D[True, True]
Pourquoi: filter garde les éléments où la lambda est vraie ; seuls 3 et 4 dépassent 2.
pm12Qu'est-ce que cela affiche ?
from functools import partial
def power(base, exp): return base ** exp
square = partial(power, exp=2)
print(square(5))- A25✓ Bonne réponse
- B10
- C32
- DTypeError
Pourquoi: partial fixe exp=2 ; square(5) calcule 5**2, soit 25.
pm13Qu'est-ce que cela affiche ?
from pathlib import Path
p = Path('/tmp/data.txt')
print(p.suffix, p.stem)- A.txt data✓ Bonne réponse
- Btxt data
- C.txt /tmp/data
- Ddata .txt
Pourquoi: Path.suffix inclut le point (".txt") ; Path.stem est le nom sans extension ("data").
pm14Qu'est-ce que cela affiche ?
from collections import Counter
print(Counter('mississippi').most_common(2))- A[('i', 4), ('s', 4)]✓ Bonne réponse
- B[('i', 4), ('m', 1)]
- C[('s', 4), ('i', 4)]
- D[('p', 2), ('m', 1)]
Pourquoi: "mississippi" contient quatre i et quatre s ; most_common(2) renvoie ces deux paires.
pm15Quelle est la première ligne affichée ?
for i, v in enumerate(['a', 'b', 'c'], start=10):
print(i, v)- A10 a✓ Bonne réponse
- B0 a
- C1 a
- D10 c
Pourquoi: enumerate(start=10) commence à 10, donc la première paire est (10, "a").
pm16Qu'est-ce que cela affiche ?
def classify(x):
match x:
case 0: return 'zero'
case int(): return 'int'
case _: return 'other'
print(classify(5))- Aint✓ Bonne réponse
- Bzero
- Cother
- DSyntaxError
Pourquoi: 5 n'est pas 0 mais correspond à int(), donc on renvoie "int" avant le joker.
pm17Qu'est-ce que cela affiche ?
pi = 3.14159
print(f'{pi:.2f}')- A3.14✓ Bonne réponse
- B3.142
- C3.14159
- D3.1
Pourquoi: Le format .2f arrondit le flottant à deux décimales.
pm18Qu'est-ce que cela affiche ?
import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
b[0].append(9)
print(a)- A[[1, 2, 9], [3, 4]]✓ Bonne réponse
- B[[1, 2], [3, 4]]
- C[[1, 2], [3, 4, 9]]
- D[[1, 2, 9], [3, 4, 9]]
Pourquoi: copy.copy est superficielle ; les sous-listes sont partagées, modifier b[0] affecte a[0].
pm19Qu'est-ce que cela affiche ?
print('a,b,c,d'.split(',', maxsplit=2))- A['a', 'b', 'c,d']✓ Bonne réponse
- B['a', 'b', 'c', 'd']
- C['a,b', 'c', 'd']
- D['a', 'b,c,d']
Pourquoi: maxsplit=2 produit trois morceaux ; le reste "c,d" reste un seul élément.
pm20Qu'est-ce que cela affiche ?
a = {'x': 1, 'y': 2}
b = {'y': 9, 'z': 3}
print(a | b)- A{'x': 1, 'y': 9, 'z': 3}✓ Bonne réponse
- B{'x': 1, 'y': 2, 'z': 3}
- C{'y': 9, 'z': 3}
- DTypeError
Pourquoi: L'opérateur | fusionne les dicts ; en cas de conflit, la droite l'emporte, y=9.
pm21Qu'est-ce que cela affiche ?
pairs = [(1, 'b'), (3, 'a'), (2, 'c')]
print(sorted(pairs, key=lambda p: p[1]))- A[(3, 'a'), (1, 'b'), (2, 'c')]✓ Bonne réponse
- B[(1, 'b'), (2, 'c'), (3, 'a')]
- C[(1, 'b'), (3, 'a'), (2, 'c')]
- D[(2, 'c'), (1, 'b'), (3, 'a')]
Pourquoi: La clé extrait le second élément du tuple, triant alphabétiquement par lettre.
pm22Quelle est la deuxième ligne affichée ?
def add(x, items=[]):
items.append(x)
return items
print(add(1))
print(add(2))- A[1, 2]✓ Bonne réponse
- B[2]
- C[1]
- D[2, 1]
Pourquoi: Les arguments par défaut mutables sont partagés ; la même liste accumule 1 puis 2.
pm23Qu'est-ce que cela affiche ?
from collections import defaultdict
d = defaultdict(int)
for c in 'abca':
d[c] += 1
print(dict(d))- A{'a': 2, 'b': 1, 'c': 1}✓ Bonne réponse
- B{'a': 1, 'b': 1, 'c': 1}
- CKeyError
- D{'a': 2}
Pourquoi: defaultdict(int) crée les clés manquantes à 0 ; "abca" donne a=2, b=1, c=1.
pm24Qu'est-ce que cela affiche ?
from functools import reduce
print(reduce(lambda a, b: a + b, [1, 2, 3, 4], 10))- A20✓ Bonne réponse
- B10
- C24
- D14
Pourquoi: reduce part de 10 et ajoute 1+2+3+4=10, donc 20 au total.
pm25Qu'est-ce que cela affiche ?
from collections import deque
d = deque([1, 2, 3])
d.appendleft(0)
d.append(4)
print(list(d))- A[0, 1, 2, 3, 4]✓ Bonne réponse
- B[1, 2, 3, 0, 4]
- C[4, 0, 1, 2, 3]
- D[0, 4, 1, 2, 3]
Pourquoi: appendleft ajoute 0 au début ; append ajoute 4 à la fin, donnant [0, 1, 2, 3, 4].
python-advanced · Avancé · 25
pa1Le type-check passe-t-il et qu'affiche-t-il ?
from typing import Protocol
class Greeter(Protocol):
def hi(self) -> str: ...
class En:
def hi(self) -> str:
return 'hello'
def shout(g: Greeter) -> str:
return g.hi().upper()
print(shout(En()))- ANon, En n'hérite pas de Greeter
- BOui, affiche HELLO✓ Bonne réponse
- COui, affiche hello
- DTypeError au runtime
Pourquoi: Protocol utilise le typage structurel : toute classe avec les méthodes correspondantes le satisfait sans héritage explicite.
pa2Que se passe-t-il avec cette dataclass figée ?
from dataclasses import dataclass, field
@dataclass(frozen=True)
class P:
name: str
tags: list[str] = field(default_factory=list)
p = P('a')
p.tags.append('x')
print(p.tags)- AFrozenInstanceError sur append
- BAffiche []
- CAffiche ['x']✓ Bonne réponse
- DTypeError à la construction
Pourquoi: frozen=True empêche la réassignation, mais les objets mutables référencés peuvent toujours être modifiés en place.
pa3Qu'affiche fmt(True) ?
from functools import singledispatch
@singledispatch
def fmt(x): return f'obj:{x}'
@fmt.register
def _(x: int): return f'int:{x}'
@fmt.register
def _(x: list): return f'list:{x}'
print(fmt(True))- Aobj:True
- Bint:True✓ Bonne réponse
- Cbool:True
- DTypeError
Pourquoi: bool est sous-classe de int, donc singledispatch résout True vers l'implémentation int via MRO.
pa4Que garantit le GIL de Python ?
- AExécution CPU parallèle des threads
- BUn seul thread exécute le bytecode à la fois✓ Bonne réponse
- CCode utilisateur thread-safe
- DI/O plus rapide qu'asyncio
Pourquoi: Le GIL sérialise l'exécution du bytecode à un thread, empêchant le parallélisme CPU sans rendre le code thread-safe.
pa5Qu'est-ce qui est affiché ?
from itertools import accumulate
import operator
nums = [1, 2, 3, 4]
print(list(accumulate(nums, operator.mul)))- A[1, 2, 6, 24]✓ Bonne réponse
- B[1, 3, 6, 10]
- C[24]
- D[1, 2, 3, 4]
Pourquoi: accumulate produit les résultats successifs : 1, 1*2, 1*2*3, 1*2*3*4 avec l'opérateur fourni.
pa6Que se passe-t-il ?
class C:
__slots__ = ('x',)
c = C()
c.x = 1
c.y = 2
print(c.x, c.y)- AAffiche 1 2
- BAttributeError sur c.y = 2✓ Bonne réponse
- CTypeError à la définition
- DAffiche 1 None
Pourquoi: __slots__ désactive le __dict__ d'instance, donc assigner un attribut hors slots lève AttributeError.
pa7Est-ce valide et qu'affiche-t-il ?
from typing import TypedDict, NotRequired
class User(TypedDict):
name: str
age: NotRequired[int]
u: User = {'name': 'Ada'}
print(u.get('age', 0))- AErreur de type : age manquant
- BValide, affiche 0✓ Bonne réponse
- CValide, affiche None
- DKeyError au runtime
Pourquoi: NotRequired marque la clé comme optionnelle, donc omettre age est valide ; .get renvoie la valeur par défaut 0.
pa8Pourquoi utiliser ExitStack ici ?
from contextlib import ExitStack
def open_all(paths):
with ExitStack() as stack:
files = [stack.enter_context(open(p)) for p in paths]
return [f.read() for f in files]
print(type(open_all([])).__name__)- APour ouvrir en parallèle
- BPour gérer un nombre dynamique de gestionnaires de contexte✓ Bonne réponse
- CPour supprimer IOError
- DIl remplace try/except
Pourquoi: ExitStack permet d'entrer dans un nombre inconnu de gestionnaires de contexte et garantit leur sortie correcte.
pa9Qu'affiche print ?
class Desc:
def __set_name__(self, owner, name):
self.name = '_' + name
def __get__(self, obj, t=None):
return getattr(obj, self.name, 0)
def __set__(self, obj, v):
setattr(obj, self.name, v * 2)
class A:
x = Desc()
a = A(); a.x = 5
print(a.x)- A5
- B10✓ Bonne réponse
- C0
- DAttributeError
Pourquoi: Le __set__ du descripteur stocke 5*2 dans _x ; __get__ lit _x et renvoie 10.
pa10Qu'est-ce qui est affiché ?
from enum import StrEnum, auto
class Color(StrEnum):
RED = auto()
BLUE = auto()
print(Color.RED == 'red', Color.RED.value)- AFalse 1
- BTrue red✓ Bonne réponse
- CTrue RED
- DFalse red
Pourquoi: StrEnum.auto() génère le nom en minuscules ; les membres StrEnum sont égaux à leur valeur chaîne.
pa11Que produit itertools.groupby ?
from itertools import groupby
data = [1, 1, 2, 2, 1, 1]
print([(k, list(g)) for k, g in groupby(data)])- A[(1,[1,1,1,1]),(2,[2,2])]
- B[(1,[1,1]),(2,[2,2]),(1,[1,1])]✓ Bonne réponse
- C[(1,4),(2,2)]
- D[(1,[1,1,1,1,1,1])]
Pourquoi: groupby ne regroupe que les éléments consécutifs égaux ; les séries non adjacentes forment des groupes distincts.
pa12Qu'est-ce qui est affiché ?
import weakref
class Node: pass
n = Node()
r = weakref.ref(n)
print(r() is n)
del n
print(r())- ATrue puis None✓ Bonne réponse
- BTrue puis <Node object>
- CFalse puis None
- DReferenceError
Pourquoi: Un weakref ne maintient pas l'objet en vie ; après suppression de la référence forte, le weakref renvoie None.
pa13Quelle est la sortie ?
from typing import TypeVar, Generic
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, x: T) -> None:
self.x = x
def get(self) -> T:
return self.x
b: Box[int] = Box(3)
print(b.get() + 1)- A4✓ Bonne réponse
- B3
- CTypeError
- DBox[int]
Pourquoi: Generic[T] permet le typage paramétrique ; au runtime les types sont effacés et 3 + 1 donne 4.
pa14Quelle affirmation sur pickle vs json est VRAIE ?
- Ajson sérialise tout objet Python
- Bpickle est sûr depuis sources non fiables
- Cpickle peut exécuter du code arbitraire au load✓ Bonne réponse
- Djson préserve tuple vs list
Pourquoi: pickle.load peut exécuter du code via des données forgées ; ne jamais charger des données non fiables. json est sûr.
pa15Combien de fois "compute" est-il affiché ?
from functools import cached_property
class C:
@cached_property
def v(self):
print('compute')
return 42
c = C()
print(c.v); print(c.v)- A0
- B1✓ Bonne réponse
- C2
- DErreur : nécessite __slots__
Pourquoi: cached_property calcule une fois par instance et stocke dans __dict__ ; les accès suivants évitent le recalcul.
pa16Qu'affiche-t-il ?
ba = bytearray(b'hello')
mv = memoryview(ba)
mv[0] = ord('H')
print(ba)- Abytearray(b'hello')
- Bbytearray(b'Hello')✓ Bonne réponse
- CTypeError : bytes immutable
- DBufferError
Pourquoi: memoryview partage le buffer du bytearray (mutable), donc écrire à travers modifie l'original en place.
pa17Que renvoie re.findall ?
import re
text = 'a1 b22 c333'
print(re.findall(r'\d+', text))- A['1', '22', '333']✓ Bonne réponse
- Bitérateur de Match
- C['a1', 'b22', 'c333']
- D[1, 22, 333]
Pourquoi: findall renvoie une liste de chaînes ; finditer renvoie un itérateur d'objets Match.
pa18Que fait assert_never ici ?
from typing import Literal, assert_never
def area(shape: Literal['sq', 'tr'], v: float) -> float:
match shape:
case 'sq': return v * v
case 'tr': return v * v / 2
case _: assert_never(shape)
print(area('sq', 3))- ALève toujours au runtime
- BVérif statique d'exhaustivité ; erreur si cas incomplets✓ Bonne réponse
- CSupprime les warnings
- DAssert shape est None
Pourquoi: assert_never indique au type-checker que la branche est inatteignable ; un cas oublié provoque une erreur de type.
pa19Meilleur choix pour parallélisme CPU en CPython ?
- Athreading
- Basyncio
- Cmultiprocessing✓ Bonne réponse
- Dconcurrent.futures.ThreadPoolExecutor
Pourquoi: multiprocessing crée des processus séparés avec leur propre GIL, offrant un vrai parallélisme CPU.
pa20Qu'est-ce qui est affiché ?
class Meta(type):
def __new__(mcs, name, bases, ns):
ns['greeting'] = 'hi'
return super().__new__(mcs, name, bases, ns)
class A(metaclass=Meta):
pass
print(A.greeting)- Ahi✓ Bonne réponse
- BAttributeError
- CNone
- DTypeError : conflit metaclass
Pourquoi: La métaclasse injecte greeting dans l'espace de la classe avant sa création, donc A.greeting vaut "hi".
pa21Quelle est la valeur de r ?
match point := (1, 0):
case (0, 0): r = 'origin'
case (x, 0): r = f'x-axis:{x}'
case (0, y): r = f'y-axis:{y}'
case _: r = 'other'
print(r)- Aorigin
- Bx-axis:1✓ Bonne réponse
- Cy-axis:0
- Dother
Pourquoi: Les motifs sont testés en ordre ; (1,0) ne correspond pas à (0,0), correspond à (x,0) avec x=1, donc "x-axis:1".
pa22Pourquoi ContextVar plutôt qu'une globale ?
from contextvars import ContextVar
import asyncio
user: ContextVar[str] = ContextVar('user')
async def who():
return user.get()
async def main():
user.set('ada')
print(await who())
asyncio.run(main())- APlus rapide que global
- BIsolation par tâche/coroutine sans passage explicite✓ Bonne réponse
- CRemplaçant de verrou thread-safe
- DRequis par asyncio.run
Pourquoi: Les ContextVar sont liés au contexte courant (tâche) ; les tâches concurrentes voient des valeurs indépendantes sans les passer en arguments.
pa23Que préserve ParamSpec ?
from typing import ParamSpec, TypeVar, Callable
from functools import wraps
P = ParamSpec('P')
R = TypeVar('R')
def log(f: Callable[P, R]) -> Callable[P, R]:
@wraps(f)
def w(*a: P.args, **k: P.kwargs) -> R:
return f(*a, **k)
return w- ASeulement le type de retour
- BLa signature complète des paramètres✓ Bonne réponse
- CLes perfs runtime
- DLe nom de la fonction
Pourquoi: ParamSpec capture les paramètres positionnels et nommés pour que les décorateurs les transmettent avec fidélité de type.
pa24Qu'affiche-t-il ?
from itertools import islice, count
print(list(islice(count(10, 2), 2, 5)))- A[14, 16, 18]✓ Bonne réponse
- B[10, 12, 14]
- C[12, 14, 16, 18]
- D[2, 3, 4]
Pourquoi: count(10,2) donne 10,12,14,16,18,... ; islice prend les indices 2,3,4 → 14,16,18.
pa25Pourquoi annoter le retour avec Self plutôt que "Builder" ?
from typing import Self
class Builder:
def __init__(self) -> None:
self.parts: list[str] = []
def add(self, p: str) -> Self:
self.parts.append(p)
return self
class SubBuilder(Builder): pass- ASelf est plus rapide
- BSelf préserve le type de la sous-classe pour le chaînage fluide✓ Bonne réponse
- CSelf interdit l'héritage
- DIls sont équivalents
Pourquoi: Self désigne le type réel du receveur ; SubBuilder().add("x") est typé SubBuilder, permettant un chaînage conscient des sous-classes.
python-async · Async (asyncio) · 25
pas1Qu'affiche ce code ?
import asyncio
async def f(n):
await asyncio.sleep(0.01)
return n * 2
async def main():
out = await asyncio.gather(f(3), f(1), f(2))
print(out)
asyncio.run(main())- A[6, 2, 4]✓ Bonne réponse
- B[2, 4, 6]
- C[1, 2, 3]
- DUn objet coroutine
Pourquoi: gather conserve l'ordre de soumission, donc le résultat correspond à f(3), f(1), f(2) indépendamment du temps.
pas2Qu'est-ce qui est affiché ?
async def greet():
return "hi"
result = greet()
print(type(result).__name__)- Acoroutine✓ Bonne réponse
- Bstr
- Cfunction
- DTask
Pourquoi: Appeler une fonction async def renvoie une coroutine; le corps ne s'exécute qu'après await ou planification.
pas3Combien de temps environ ?
import asyncio, time
async def work():
time.sleep(1)
return 42
async def main():
return await asyncio.gather(work(), work(), work())
asyncio.run(main())- A~3 secondes✓ Bonne réponse
- B~1 seconde
- C~0 secondes
- DLève immédiatement
Pourquoi: time.sleep bloque la boucle d'événements, donc les trois coroutines s'exécutent en série.
pas4Qu'est-ce qui est affiché ?
async def boom():
raise ValueError("x")
async def main():
res = await asyncio.gather(boom(), boom(), return_exceptions=True)
print([type(r).__name__ for r in res])
asyncio.run(main())- A['ValueError', 'ValueError']✓ Bonne réponse
- BLève ValueError
- C['NoneType', 'NoneType']
- D['Exception', 'Exception']
Pourquoi: Avec return_exceptions=True, gather renvoie les exceptions comme valeurs au lieu de les lever.
pas5Que se passe-t-il ici ?
async def slow():
await asyncio.sleep(5)
return 1
async def main():
try:
return await asyncio.wait_for(slow(), timeout=0.1)
except asyncio.TimeoutError:
print("timeout")
asyncio.run(main())- AAffiche "timeout"✓ Bonne réponse
- BRenvoie 1
- CBloque 5 s
- DLève ValueError
Pourquoi: wait_for annule la coroutine interne au dépassement et lève asyncio.TimeoutError.
pas6Que fait asyncio.create_task ?
- APlanifie une coroutine sur la boucle et renvoie une Task✓ Bonne réponse
- BExécute la coroutine et bloque
- CCrée une nouvelle boucle
- DLance un thread OS
Pourquoi: create_task encapsule une coroutine dans une Task et la planifie sur la boucle active.
pas7Quel est le bug ?
async def f():
return 7
async def main():
print(f())
asyncio.run(main())- Aawait manquant — affiche un objet coroutine✓ Bonne réponse
- Basyncio.run manquant
- Cf doit être sync
- Dmain n'est pas attendu
Pourquoi: Sans await, f() renvoie un objet coroutine au lieu de la valeur, avec un RuntimeWarning.
pas8Qu'est-ce qui est affiché ?
async def gen():
for i in range(3):
yield i
async def main():
total = 0
async for x in gen():
total += x
print(total)
asyncio.run(main())- A3✓ Bonne réponse
- B6
- C0
- DSyntaxError
Pourquoi: gen produit 0, 1, 2 et async for les itère, somme = 3.
pas9Que garantit Semaphore(2) ?
async def main():
sem = asyncio.Semaphore(2)
async def worker(i):
async with sem:
await asyncio.sleep(0.1)
return i
return await asyncio.gather(*(worker(i) for i in range(4)))
print(asyncio.run(main()))- AAu plus 2 workers dans le bloc à la fois✓ Bonne réponse
- BExactement 2 exécutions au total
- CDélai de 2 secondes
- DDeux tentatives
Pourquoi: Semaphore(2) limite à deux les détenteurs simultanés; les autres attendent un créneau.
pas10Qu'est-ce qui est affiché ?
async def main():
q = asyncio.Queue()
await q.put(1)
await q.put(2)
print(await q.get(), await q.get())
asyncio.run(main())- A1 2✓ Bonne réponse
- B2 1
- CNone None
- DInterblocage
Pourquoi: asyncio.Queue est FIFO par défaut, les éléments sortent dans l'ordre d'insertion.
pas11Quand asyncio est-il inadapté ?
- ACalculs intensifs CPU✓ Bonne réponse
- BNombreuses requêtes réseau concurrentes
- CWebsockets long-poll
- DRequêtes DB lentes via driver async
Pourquoi: asyncio brille pour l'I/O; les tâches CPU bloquent la boucle et nécessitent processus ou executors.
pas12Qu'est-ce qui s'affiche en dernier ?
async def hb():
while True:
print("tick")
await asyncio.sleep(1)
async def main():
t = asyncio.create_task(hb())
await asyncio.sleep(0.05)
t.cancel()
try:
await t
except asyncio.CancelledError:
print("done")
asyncio.run(main())- Adone✓ Bonne réponse
- Btick
- CCancelledError
- DRien
Pourquoi: Annuler la tâche injecte CancelledError; le await la relance, except la traite et affiche done.
pas13Que fait asyncio.shield(coro) ?
- AProtège la tâche interne contre l'annulation propagée via shield✓ Bonne réponse
- BAvale silencieusement les exceptions
- CForce une exécution synchrone
- DÉpingle la tâche à un CPU
Pourquoi: shield protège la tâche interne contre l'annulation de l'attendant; elle s'achève normalement.
pas14Pourquoi async with pour httpx.AsyncClient ?
async def main():
async with httpx.AsyncClient() as client:
r = await client.get("https://api.example.com/x")
return r.status_code- AGarantit la fermeture du pool de connexions✓ Bonne réponse
- BRequis pour tout await
- CDésactive la vérification TLS
- DRend les requêtes synchrones
Pourquoi: Le gestionnaire de contexte garantit aclose(), libérant le pool de connexions.
pas15Différence entre httpx.AsyncClient et requests ?
- Ahttpx prend en charge async/await; requests est uniquement synchrone✓ Bonne réponse
- Brequests est toujours plus rapide
- CAPI identiques
- Dhttpx ne fait pas de POST
Pourquoi: httpx propose clients sync et async; requests n'a pas de support asyncio natif.
pas16Quand sort-on du async with TaskGroup ?
async def main():
async with asyncio.TaskGroup() as tg:
tg.create_task(asyncio.sleep(0.01))
tg.create_task(asyncio.sleep(0.02))
print("ok")
asyncio.run(main())- AAprès que toutes les tâches enfants finissent✓ Bonne réponse
- BJuste après création
- CUniquement à l'annulation
- DAprès 1 s
Pourquoi: TaskGroup attend la fin de toutes les tâches enfants; si l'une échoue, les autres sont annulées.
pas17À quoi sert run_in_executor ici ?
import time
def heavy(n):
time.sleep(n); return n
async def main():
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, heavy, 0.1)
print(asyncio.run(main()))- AExécute du code bloquant hors boucle dans un thread✓ Bonne réponse
- BAccélère les coroutines
- CRemplace asyncio.run
- DActive un pool de processus
Pourquoi: run_in_executor délègue un appel bloquant à un pool, la boucle reste réactive.
pas18Qu'affiche-t-il ?
async def f():
return 1
async def main():
t = asyncio.create_task(f())
print(isinstance(t, asyncio.Task), asyncio.iscoroutine(f()))
asyncio.run(main())- ATrue True✓ Bonne réponse
- BTrue False
- CFalse True
- DFalse False
Pourquoi: create_task renvoie une Task; appeler f() à nouveau crée une nouvelle coroutine.
pas19Quel est l'ordre de sortie ?
from contextlib import asynccontextmanager
@asynccontextmanager
async def conn():
print("open")
yield "c"
print("close")
async def main():
async with conn() as c:
print(c)
asyncio.run(main())- Aopen / c / close✓ Bonne réponse
- Bc / open / close
- Copen / close / c
- Dclose / c / open
Pourquoi: Le code avant yield s'exécute à l'entrée, la valeur va à as, le code après yield à la sortie.
pas20Que constate-t-on ?
async def f():
raise RuntimeError("oops")
async def main():
asyncio.create_task(f())
await asyncio.sleep(0.05)
print("ok")
asyncio.run(main())- AAffiche ok et journalise une exception non récupérée✓ Bonne réponse
- BLève RuntimeError immédiatement
- CReste bloqué
- DAffiche "oops"
Pourquoi: Une tâche fire-and-forget masque l'exception jusqu'au GC, où asyncio journalise un avertissement.
pas21Qu'est-ce qui est affiché ?
async def gen():
yield 1
yield 2
async def main():
it = gen()
print(await anext(it))
print(await anext(it))
asyncio.run(main())- A1 puis 2✓ Bonne réponse
- B2 puis 1
- CStopAsyncIteration
- DDeux objets coroutine
Pourquoi: anext attend __anext__ de l'itérateur asynchrone et renvoie chaque valeur dans l'ordre.
pas22En quoi seq et par diffèrent-ils ?
async def task(n):
await asyncio.sleep(0.01)
return n
async def seq():
a = await task(1)
b = await task(2)
return a + b
async def par():
a, b = await asyncio.gather(task(1), task(2))
return a + b- Aseq séquentiel (~0.02 s); par concurrent (~0.01 s)✓ Bonne réponse
- BComportement identique
- Cpar séquentiel, seq concurrent
- Dpar nécessite des threads
Pourquoi: Attendre l'une puis l'autre est séquentiel; gather les exécute en parallèle, durée du plus lent.
pas23Qu'est-ce qui est affiché ?
async def main():
ev = asyncio.Event()
async def waiter():
await ev.wait()
return "go"
t = asyncio.create_task(waiter())
await asyncio.sleep(0.01)
ev.set()
print(await t)
asyncio.run(main())- Ago✓ Bonne réponse
- BNone
- CBloque indéfiniment
- DTimeoutError
Pourquoi: Event.set() débloque les attendants, wait() se résout et la tâche renvoie "go".
pas24Que contrôle timeout=2.0 ?
async def fetch():
async with httpx.AsyncClient(timeout=2.0) as c:
r = await c.get("https://slow.example")
return r.text- ATimeout par requête par défaut pour ce client✓ Bonne réponse
- BDurée de vie max du client
- CNombre de tentatives
- DTaille du pool de connexions
Pourquoi: httpx applique le timeout numérique aux phases connect, read, write et pool de chaque requête.
pas25Différence entre coroutine, Task et Future ?
- ACoroutine: résultat d'appel; Task: coroutine planifiée; Future: placeholder bas niveau✓ Bonne réponse
- BLes trois sont identiques
- CFuture synchrone, Task dans un thread
- DCoroutine dans un thread, Task dans un processus
Pourquoi: Task est une sous-classe de Future qui pilote une coroutine; Future est le primitif de résultat awaitable.
python-fastapi · FastAPI + httpx · 25
pf1Que fait FastAPI pour la requête GET /items/abc ?
from fastapi import FastAPI
app = FastAPI()
@app.get('/items/{item_id}')
def read(item_id: int):
return {'id': item_id}- ARetourne 200 avec id="abc"
- BRetourne 422 erreur de validation✓ Bonne réponse
- CRetourne 404 not found
- DLève ValueError
Pourquoi: Le paramètre est typé int ; "abc" ne peut être converti, donc FastAPI renvoie 422 avec détails.
pf2Quel appel utilise les deux valeurs par défaut ?
from fastapi import FastAPI
app = FastAPI()
@app.get('/search')
def search(q: str = 'all', limit: int = 10):
return {'q': q, 'limit': limit}- A/search?q=&limit=
- B/search✓ Bonne réponse
- C/search?q=all
- D/search?limit=10
Pourquoi: Omettre les deux paramètres conserve les valeurs par défaut q="all" et limit=10.
pf3Quel statut HTTP produit GET /u/-1 ?
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get('/u/{i}')
def get(i: int):
if i < 0:
raise HTTPException(status_code=404, detail='nope')
return {'i': i}- A200
- B400
- C404✓ Bonne réponse
- D500
Pourquoi: HTTPException(status_code=404) est levé ; FastAPI renvoie une réponse 404.
pf4Le body { "name": "x" } renvoie quel statut ?
from fastapi import FastAPI
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(min_length=2)
app = FastAPI()
@app.post('/i')
def add(it: Item):
return it- A200
- B400
- C422✓ Bonne réponse
- D500
Pourquoi: Field(min_length=2) rejette "x" (longueur 1) ; Pydantic lève, FastAPI renvoie 422.
pf5Que reçoit le client dans le JSON ?
from fastapi import FastAPI
from pydantic import BaseModel
class Out(BaseModel):
name: str
app = FastAPI()
@app.get('/me', response_model=Out)
def me():
return {'name': 'Ada', 'token': 'secret'}- A{"name":"Ada","token":"secret"}
- B{"name":"Ada"}✓ Bonne réponse
- C{"token":"secret"}
- DErreur 500
Pourquoi: response_model filtre la sortie aux champs déclarés ; "token" est retiré avant sérialisation.
pf6Quel code statut renvoie cet endpoint ?
from fastapi import FastAPI, status
app = FastAPI()
@app.post('/items', status_code=status.HTTP_201_CREATED)
def create():
return {'ok': True}- A200
- B201✓ Bonne réponse
- C202
- D204
Pourquoi: Le paramètre status_code du décorateur remplace 200 par défaut, renvoyant 201 Created.
pf7Que renvoie GET /i?q=hi ?
from fastapi import FastAPI, Depends
app = FastAPI()
def common(q: str = ''):
return {'q': q}
@app.get('/i')
def list_items(c: dict = Depends(common)):
return c- A{"q":""}
- B{"q":"hi"}✓ Bonne réponse
- CErreur 422
- D{"c":{"q":"hi"}}
Pourquoi: Depends(common) appelle common(q="hi") et injecte le dict renvoyé en JSON.
pf8Quand le bloc finally s'exécute-t-il ?
from fastapi import FastAPI, Depends
app = FastAPI()
def get_db():
db = {'x': 1}
try:
yield db
finally:
db.clear()
@app.get('/')
def r(d=Depends(get_db)):
return d- AAvant l'endpoint
- BAprès envoi de la réponse✓ Bonne réponse
- CJamais, sauf erreurs
- DAu démarrage
Pourquoi: Les dépendances yield exécutent le nettoyage après envoi de la réponse, libérant les ressources.
pf9Pourquoi async def est-il correct au lieu de def ?
from fastapi import FastAPI
import httpx
app = FastAPI()
@app.get('/proxy')
async def proxy():
async with httpx.AsyncClient() as c:
r = await c.get('https://api.example.com')
return r.json()- Adef ne peut pas renvoyer JSON
- Bawait exige une fonction async✓ Bonne réponse
- Chttpx est sync uniquement
- DFastAPI exige async
Pourquoi: await n'est valide que dans une fonction async ; httpx.AsyncClient doit être awaité.
pf10Quand log() s'exécute-t-il par rapport à la réponse ?
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def log(msg: str):
open('log.txt', 'a').write(msg)
@app.post('/notify')
def notify(bt: BackgroundTasks):
bt.add_task(log, 'sent')
return {'ok': True}- AAvant l'envoi
- BAprès l'envoi✓ Bonne réponse
- CEn parallèle de la réponse
- DÀ la prochaine requête
Pourquoi: BackgroundTasks exécute les tâches après que la réponse a été renvoyée au client.
pf11Que fait ce middleware ?
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware('http')
async def add_header(request: Request, call_next):
resp = await call_next(request)
resp.headers['X-App'] = 'demo'
return resp- ABloque toutes les requêtes
- BAjoute X-App à chaque réponse✓ Bonne réponse
- CRedirige les requêtes
- DJournalise les bodies
Pourquoi: Le middleware attend la réponse, ajoute un en-tête puis la renvoie.
pf12Quelle origine peut appeler cette API depuis un navigateur ?
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['https://x.com'],
allow_methods=['*'],
)- AToutes
- BSeulement https://x.com✓ Bonne réponse
- CSeulement même origine
- DSeulement http://x.com
Pourquoi: allow_origins est une liste stricte ; seule l'origine listée reçoit les en-têtes CORS.
pf13Quel content-type le client doit-il utiliser ?
from fastapi import FastAPI, UploadFile
app = FastAPI()
@app.post('/up')
async def upload(file: UploadFile):
data = await file.read()
return {'size': len(data), 'name': file.filename}- Aapplication/json
- Bapplication/x-www-form-urlencoded
- Cmultipart/form-data✓ Bonne réponse
- Dtext/plain
Pourquoi: UploadFile lit un body multipart/form-data ; FastAPI requiert python-multipart.
pf14Que faut-il appeler avant send_text/receive_text ?
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket('/ws')
async def ws(websocket: WebSocket):
await websocket.accept()
msg = await websocket.receive_text()
await websocket.send_text(f'echo:{msg}')- Awebsocket.connect()
- Bwebsocket.accept()✓ Bonne réponse
- Cwebsocket.open()
- DRien, optionnel
Pourquoi: Le handshake WebSocket se termine via accept() ; les méthodes échouent avant.
pf15Pourquoi utiliser StreamingResponse ?
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
def gen():
for i in range(3):
yield f'chunk{i}\n'
@app.get('/s')
def stream():
return StreamingResponse(gen(), media_type='text/plain')- ACompresse automatiquement
- BEnvoie le body en flux sans bufferisation✓ Bonne réponse
- CRequis pour JSON
- DAjoute CORS
Pourquoi: StreamingResponse itère un générateur et envoie les chunks au fur et à mesure.
pf16Quel est le chemin complet de list_users ?
from fastapi import FastAPI, APIRouter
router = APIRouter(prefix='/v1', tags=['users'])
@router.get('/users')
def list_users():
return []
app = FastAPI()
app.include_router(router)- A/users
- B/v1/users✓ Bonne réponse
- C/users/v1
- D/list_users
Pourquoi: Le prefix d'APIRouter est ajouté ; tags ne sert qu'au regroupement OpenAPI.
pf17Comment le token doit-il arriver ?
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
oauth2 = OAuth2PasswordBearer(tokenUrl='token')
app = FastAPI()
@app.get('/me')
def me(token: str = Depends(oauth2)):
return {'token': token}- AQuery ?token=
- BCookie token
- CEn-tête Authorization: Bearer✓ Bonne réponse
- DChamp body token
Pourquoi: OAuth2PasswordBearer extrait le credential depuis l'en-tête Authorization (Bearer).
pf18Que fait TestClient en interne ?
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get('/ping')
def ping():
return {'pong': True}
client = TestClient(app)
r = client.get('/ping')- AUne vraie socket réseau
- BDes appels ASGI in-process via httpx✓ Bonne réponse
- CUn processus uvicorn séparé
- DSelenium
Pourquoi: TestClient enveloppe httpx avec un transport ASGI, appelant l'app sans port réseau.
pf19Que remplace ce pattern dans FastAPI moderne ?
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
print('startup')
yield
print('shutdown')
app = FastAPI(lifespan=lifespan)- ADécorateur middleware
- B@app.on_event("startup"/"shutdown")✓ Bonne réponse
- CInjection de dépendances
- DBackgroundTasks
Pourquoi: Le lifespan remplace on_event déprécié, unifiant démarrage et arrêt.
pf20Pour {"name":"ada"}, que vaut User().name ?
from pydantic import BaseModel, field_validator
class User(BaseModel):
name: str
@field_validator('name')
@classmethod
def upper(cls, v: str) -> str:
if not v:
raise ValueError('empty')
return v.upper()- A"ada"
- B"ADA"✓ Bonne réponse
- CValidationError
- DNone
Pourquoi: field_validator renvoie la valeur transformée ; "ADA" remplace l'entrée d'origine.
pf21Que se passe-t-il pour {"name":"a","age":1} ?
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
model_config = ConfigDict(extra='forbid')
name: str- AAccepté, age ignoré
- BValidationError✓ Bonne réponse
- Cage stocké
- DAvertissement silencieux
Pourquoi: extra="forbid" fait que Pydantic v2 rejette les champs non déclarés.
pf22Que fait asyncio.gather pour ces requêtes ?
import asyncio, httpx
from fastapi import FastAPI
app = FastAPI()
@app.get('/agg')
async def agg():
async with httpx.AsyncClient() as c:
a, b = await asyncio.gather(
c.get('https://x/1'), c.get('https://x/2'))
return {'a': a.status_code, 'b': b.status_code}- ASéquentiellement
- BEn concurrence, attend les deux✓ Bonne réponse
- CCrée des threads OS
- DCache la 2e réponse
Pourquoi: gather planifie les deux coroutines sur la boucle ; le temps total est le max, pas la somme.
pf23Comment s'appelle ce pattern ?
from fastapi import FastAPI, Depends
def auth(token: str = ''):
return token == 'ok'
def admin(ok: bool = Depends(auth)):
return ok
app = FastAPI()
@app.get('/a')
def page(is_admin: bool = Depends(admin)):
return is_admin- ASous-dépendances✓ Bonne réponse
- BChaîne de middleware
- CTâches en arrière-plan
- DLifespan hooks
Pourquoi: admin dépend d'auth, la route dépend d'admin — FastAPI résout la chaîne en cascade.
pf24Pourquoi sync def est-il acceptable malgré le blocage ?
from fastapi import FastAPI
import time
app = FastAPI()
@app.get('/work')
def work():
time.sleep(2)
return 'done'- AFastAPI ignore le blocage
- BExécuté dans un threadpool, pas la boucle✓ Bonne réponse
- Ctime.sleep ne bloque pas
- DBloque la boucle fatalement
Pourquoi: Les endpoints sync sont envoyés dans un threadpool anyio, sans bloquer la boucle.
pf25Pourquoi yield plutôt que return pour la session ?
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
def get_session():
s = Session()
try:
yield s
finally:
s.close()
app = FastAPI()
@app.get('/u')
def list_u(db: Session = Depends(get_session)):
return db.execute('SELECT 1').all()- Areturn interdit
- BPour fermer la session après la réponse✓ Bonne réponse
- Cyield est plus rapide
- DRequis par SQLAlchemy
Pourquoi: Yield permet à FastAPI d'exécuter finally après la réponse, fermant toujours la session.