Control Flow
Structures de contrôle
Blocs
Les blocs regroupent plusieurs instructions et retournent la valeur de la dernière expression.
# Bloc simple
resultat = {
x = 10
y = 20
x + y # Valeur retournée: 30
}
# Bloc vide
vide = {} # Retourne None
Un bloc porte un scope : x et y ci-dessus n'existent plus après l'accolade fermante. C'est vrai aussi des corps de
for et de while, pas des branches d'un if ni des bras d'un match — voir
SCOPES_AND_VARIABLES pour le critère.
Conditions (if/elif/else)
# simple if
if x > 0 {
print("x is positive")
}
# if/else
if x > 0 {
print("positive")
} else {
print("negative or zero")
}
# if/elif/else
if x > 0 {
print("positive")
} elif x < 0 {
print("negative")
} else {
print("zero")
}
# nested conditions
if x > 0 {
if x > 100 {
print("large number")
} else {
print("small number")
}
}
elif est du sucre syntaxique pour un else contenant un if.
if comme expression
if est une expression, pas un statement.
result = if x > 0 { "positive" } else { "non-positive" }
Un bloc { … } a pour valeur la valeur de sa dernière expression.
La valeur d'un if est définie par les règles suivantes.
Forme sans else
if cond { A }
est une expression dont la valeur est :
- si
condest vrai → la valeur deA - si
condest faux →None
value = if False { 123 }
# value == None
Forme avec else
if cond { A } else { B }
est une expression dont la valeur est :
- si
condest vrai → la valeur deA - si
condest faux → la valeur deB
Forme avec elif
if cond1 { A } elif cond2 { B } else { C }
est équivalent à :
if cond1 {
A
} else {
if cond2 {
B
} else {
C
}
}
et suit les mêmes règles de valeur.
Interaction avec les fonctions
Le corps d'une fonction est un bloc expression :
f = (x) => {
if x > 0 {
"positive"
} else {
"non-positive"
}
}
ici, la valeur de la fonction est la valeur de l'expression if.
return expr sort explicitement de la fonction avec la valeur expr.
si aucun return explicite n'est exécuté, la fonction renvoie la valeur de la dernière expression du bloc.
Note pour voyageurs inter-langages
Dans Catnip, if est une expression, et renvoie une vraie valeur.
Et un if sans else renvoie None quand la condition est fausse.
Boucle while
# Boucle while simple
i = 0
while i < 5 {
print("i =", i)
i = i + 1
}
# Condition complexe
somme = 0
n = 1
while somme < 100 {
somme = somme + n
n = n + 1
}
Boucle for
nombres = list(10, 20, 30)
# Itération sur une séquence
for i in range(1, 6) {
print("i =", i)
}
# Itération sur une liste
for nom in list("Capitaine Whiskers", "Docteur Latte", "Agent Photon") {
print("BORN TO SEGFAULT,", nom, "!")
}
# Avec enumerate
for (idx, valeur) in enumerate(nombres) {
print("Index:", idx, "Valeur:", valeur)
}
Valeur d'une boucle
Une boucle est une expression : elle rend la dernière valeur de son corps, comme n'importe quel bloc. Si le corps ne
s'exécute jamais, elle rend None.
for i in list(1, 2, 3) { i * 10 } # 30
for i in list() { i * 10 } # None -- le corps n'a jamais tourné
n = 0
while n < 3 { n = n + 1; n * 2 } # 6
Une itération interrompue ne contribue pas : break et continue sortent avant que la valeur du tour ne soit retenue,
donc c'est celle du dernier tour complet qui reste.
for i in list(1, 2, 3) { if i == 2 { break }; i } # 1 -- le tour 2 n'a rien rendu
for i in list(1, 2, 3) { if i == 1 { break }; i } # None -- aucun tour complet
for i in list(1, 2, 3) { if i == 3 { continue }; i } # 2
Une boucle qui n'a jamais tourné et une boucle interrompue au premier tour rendent la même chose. Le langage ne distingue pas « rien fait » de « rien fini ».
Contrôle de flux dans les boucles : break et continue
break - Sortir d'une boucle
Le mot-clé break permet de sortir immédiatement d'une boucle while ou for.
# Recherche dans une liste
found = False
for i in list(1, 5, 10, 15, 20) {
if i == 10 {
found = True
break # Sort de la boucle dès qu'on trouve
}
}
# Boucle infinie avec break
count = 0
while True {
count = count + 1
if count == 5 {
break # Sort après 5 itérations
}
}
# Dans des boucles imbriquées, break ne sort que de la boucle interne
for i in list(1, 2, 3) {
for j in list(1, 2, 3) {
if j == 2 {
break # Sort uniquement de la boucle interne
}
print(i, j)
}
}
continue - Passer à l'itération suivante
Le mot-clé continue passe directement à l'itération suivante de la boucle, en ignorant le reste du code.
# Ignorer les nombres pairs
for i in range(10) {
if i % 2 == 0 {
continue # Passe à l'itération suivante
}
print(i) # Affiche uniquement les impairs
}
# Filtrage dans une boucle while
i = 0
sum = 0
while i < 10 {
i = i + 1
if i % 3 == 0 {
continue # Ignore les multiples de 3
}
sum = sum + i
}
# Validation de données
ages = list(25, -5, 30, 150, 42)
valid_ages = list()
for age in ages {
if age < 0 or age > 120 {
continue # Ignore les valeurs invalides
}
valid_ages = valid_ages + list(age)
}
Combiner break et continue
# Recherche avec filtrage
numbers = list(10, 20, 35, 40, 55, 60)
result = None
for num in numbers {
if num % 10 == 0 {
continue # Ignore les multiples de 10
}
if num > 50 {
result = num
break # Trouve le premier > 50 (non multiple de 10)
}
}
result # → 55
Notes importantes :
breaketcontinuene fonctionnent que dans les boucles (while,for)- Les écrire en dehors d'une boucle est refusé à la lecture du fichier, avant toute exécution :
SyntaxError: 'break' outside loop - Dans des boucles imbriquées, ils n'affectent que la boucle la plus interne
- Pour sortir d'une fonction, utiliser
return(qui fonctionne même dans une boucle)
Une fonction est sa propre portée de contrôle de flux
Une boucle qui entoure une fonction ne lui prête pas son break, et une boucle qui l'appelle non plus. Le corps de la
fonction est lu comme un territoire séparé : le break n'y trouve aucune boucle à interrompre, donc le fichier est
refusé.
g = () => { break } # SyntaxError: 'break' outside loop
for i in range(0, 3) { g() }
La règle vaut identiquement pour une fonction définie dans le corps de la boucle, pour une méthode de structure et pour un callback de broadcast — toutes sont des corps de fonction.
for i in range(0, 3) {
g = () => { break } # SyntaxError aussi : la boucle englobante n'est pas candidate
g()
}
Un
breakqui traverserait l'appel donnerait à l'appelé le droit d'interrompre une boucle qu'il ne voit pas, écrite dans un fichier qu'il ne connaît pas. Le refus est moins un garde-fou qu'un aveu : personne ne saurait dire de quelle boucle il s'agit.
Un en-tête n'est pas dans ce qu'il introduit
Un bloc est une expression, donc il tient partout où une expression tient : dans une condition de while, dans un
itérable de for, dans une valeur par défaut de paramètre. Ces trois positions sont évaluées par la portée
englobante, pas par la construction qu'elles ouvrent. Un break écrit dans une condition de while ne vise donc
pas ce while :
while { break; True } { 1 } # SyntaxError: 'break' outside loop
Ce n'est pas une interdiction propre aux en-têtes : la condition s'évalue avant que la boucle n'existe, donc il n'y a
rien à interrompre. Le même break dans une boucle englobante est accepté, et interrompt celle-là :
acc = 0
for i in range(0, 3) {
acc = acc + 1
while { break; True } { 1 } # interrompt le for, pas le while
}
acc # → 1
La règle range de la même façon le return d'une valeur par défaut, qui appartient à la signature et non au corps —
voir FUNCTIONS.
Une condition de boucle est lue une fois de plus qu'on ne le croit : avant la première itération, alors que la boucle n'a pas commencé. Elle appartient à ce qui précède, pas à ce qu'elle déclenche.
Exception Handling
Voir SYNTAX.md pour la syntaxe try/except/finally/raise et les types d'exception
disponibles.
Context Managers (with)
Le bloc with garantit l'appel de __exit__ en sortie, qu'elle soit normale ou provoquée par une erreur. La syntaxe
est sans parenthèses, cohérente avec if, while et for :
with f = open("data.csv") {
f.read()
}
Le binding (f) reçoit la valeur retournée par __enter__(). À la sortie du bloc, __exit__() est appelé
automatiquement, même si une exception a été levée.
Multi-binding
Plusieurs context managers sont séparés par des virgules. Le cleanup se fait en ordre inverse :
with a = open("input"), b = open("output") {
b.write(a.read())
}
# b.__exit__() appelé en premier, puis a.__exit__()
Chaque binding est visible pour les suivants : with a = expr1, b = use(a) { ... }.
Suppression d'exception
Si __exit__ retourne une valeur truthy, l'exception est supprimée :
import('contextlib')
with _ = contextlib.suppress(ValueError) {
raise ValueError("ignored")
}
# Pas d'exception : suppress retourne True dans __exit__
Sémantique
Le with est un desugaring pur vers try/except/finally. L'exception active est passée à __exit__ sous forme
(type, value, None) -- le traceback est toujours None (écart documenté avec Python).