Salle d'étude Karinoya

Qualifications · Labo réussite Cloud / AI / Python

Structures de contrôle et de données

Les questions et les explications sont disponibles en Français. Les cours (articles explicatifs) n'existent qu'en japonais.

Voir la version japonaise (avec les cours) →

Q1 | for/else

En exécutant le code suivant, qui associe une clause else à un for, que s'affiche-t-il ?

for n in range(4):    if n == 9:        breakelse:    print("done")print(n)
  1. Seul 3 s'affiche, done ne s'affiche pas
  2. Seul done s'affiche, 3 ne s'affiche pas
  3. 3 puis done s'affichent dans cet ordre
  4. done puis 3 s'affichent dans cet ordre
RéponseD. done puis 3 s'affichent dans cet ordre

La clause else associée à un for s'exécute lorsque la boucle se termine sans avoir rencontré de break. range(4) ne contenant pas 9, break ne se produit jamais ; la boucle se déroule donc jusqu'au bout, puis le print du else s'exécute, affichant done. Le print(n) suivant affiche ensuite la dernière valeur restée dans n au moment de sortir de la boucle, soit 3. Inverser l'ordre de sortie ferait apparaître 3 en premier, et croire à tort que else est toujours sauté à la fin de la boucle ferait disparaître done. Ne pas remarquer que n reste défini après la sortie de la boucle ferait disparaître 3.

Q2 | break et else

On vérifie comment la clause else est traitée lorsqu'on sort d'une boucle avec break. Quel est le résultat du code suivant ?

for c in "abc":    if c == "b":        break    print(c)else:    print("no break")
  1. a b c et no break s'affichent tous
  2. a puis no break s'affichent dans cet ordre
  3. a et b s'affichent dans cet ordre, soit 2 éléments
  4. a s'affiche, no break ne s'affiche pas
RéponseD. a s'affiche, no break ne s'affiche pas

Au premier tour, c vaut a, donc print(c) s'exécute et affiche a. Au deuxième tour, c devient b, et break arrête la boucle. La règle voulant que la clause else d'un for ne s'exécute que si la boucle se termine sans break, elle est ici sautée et no break ne s'affiche pas. Croire que else s'exécute toujours ferait apparaître aussi no break, et mal lire que print(c) se trouve avant le break ferait apparaître aussi b. Négliger le break lui-même ferait apparaître a b c en entier.

Q3 | else même vide

On se demande ce que devient la clause else d'un for lorsque l'objet parcouru est vide. Quel est le résultat de l'exécution du code suivant ?

items = []for it in items:    print(it)else:    print("finished")
  1. La clause else est ignorée et une TypeError survient
  2. Comme il y a 0 itération, une StopIteration survient
  3. Rien ne s'affiche, et la boucle se termine ainsi
  4. finished s'affiche une seule fois
RéponseD. finished s'affiche une seule fois

La condition pour que la clause else d'un for s'exécute est que « la boucle se soit terminée sans break », et non « qu'il y ait eu au moins une itération ». items étant vide, le corps ne s'exécute pas une seule fois, mais comme il n'y a pas eu de break non plus, else s'exécute normalement et finished s'affiche. Penser que else est aussi sauté quand c'est vide ferait croire que rien ne s'affiche. Passer une liste vide à un for est une syntaxe parfaitement correcte en soi, qui ne lève donc pas d'exception ; ni TypeError ni StopIteration ne sont levées.

Q4 | while/else

On associe une clause else à un while, et on considère le cas d'un break en cours de route. Quel est le résultat du code suivant ?

n = 5while n > 0:    n -= 1    if n == 2:        breakelse:    print("loop ended")print(n)
  1. 0 s'affiche, loop ended ne s'affiche pas
  2. 2 s'affiche, loop ended ne s'affiche pas
  3. loop ended puis 2 s'affichent dans cet ordre
  4. loop ended puis 0 s'affichent dans cet ordre
RéponseB. 2 s'affiche, loop ended ne s'affiche pas

n diminue de 1 en 1 à partir de 5, passe par 4 et 3, et au moment où il vaut 2, break s'exécute. La clause else d'un while, comme celle d'un for, ne s'exécute que si la boucle se termine sans break ; ici elle est donc sautée et loop ended ne s'affiche pas. Il ne reste que le print(n) affichant 2. Croire qu'else s'exécute toujours ferait apparaître aussi loop ended, et négliger le break en lisant à tort que la boucle continue jusqu'à ce que la condition devienne fausse ferait croire que n vaut 0.

Q5 | continue

En exécutant le code suivant, qui mélange continue et break dans une même boucle, quelle est la liste finalement affichée ?

out = []for i in range(6):    if i == 3:        continue    if i == 5:        break    out.append(i)print(out)
  1. [0, 1, 2, 4, 5]
  2. [0, 1, 2, 3]
  3. [0, 1, 2, 4]
  4. [0, 1, 2, 3, 4]
RéponseC. [0, 1, 2, 4]

Lorsque i vaut 0, 1 ou 2, les deux if sont traversés sans effet et l'élément est ajouté par append. Lorsque i vaut 3, continue s'active, sautant le reste de ce tour, c'est-à-dire l'append. Lorsque i vaut 4, l'append s'exécute de nouveau, et lorsque i vaut 5, break arrête la boucle, si bien que l'append n'a pas lieu. Le résultat est donc [0, 1, 2, 4]. Croire que l'élément du tour de break est aussi ajouté ferait apparaître la liste jusqu'à 5, et traiter continue comme pass ferait apparaître 3 dans le résultat.

Q6 | range en ordre inverse

On s'interroge sur la séquence obtenue en passant un pas négatif à range. Quel est le résultat du code suivant ?

print(list(range(10, 0, -3)))
  1. [9, 6, 3, 0]
  2. [7, 4, 1, -2]
  3. [10, 7, 4, 1, 0]
  4. [10, 7, 4, 1]
RéponseD. [10, 7, 4, 1]

range(start, stop, step) commence à start, avance de step en step, et s'arrête juste avant d'atteindre stop. Ici, en partant de 10 et en soustrayant 3 à chaque fois, on obtient 10, 7, 4, 1, et la valeur suivante, -2, n'est pas retenue car elle est inférieure ou égale à 0. stop, qui vaut 0, n'étant pas inclus, la séquence ne se termine pas par 0. Croire à tort que start n'est pas inclus ferait commencer la séquence à 7, et penser qu'on compte à partir d'une position avant start ferait commencer à 9. Le fait que stop ne soit pas inclus est identique au cas d'un pas positif.

Q7 | Type de range

Quelle description de l'objet obtenu en appelant la fonction native range est appropriée ?

  1. Un objet de type generator, qui devient vide après un seul parcours
  2. Un objet de type range, qui devient une liste en le passant à list()
  3. Un objet de type tuple, dont les éléments ne peuvent pas être remplacés
  4. Un objet de type list, qui s'affiche directement avec des crochets
RéponseB. Un objet de type range, qui devient une liste en le passant à list()

Ce que renvoie range est un objet de type range, qui calcule les entiers nécessaires au fur et à mesure. Ne conservant pas d'abord tous les éléments comme une liste, il ne consomme pas de mémoire même pour une grande plage. Pour vérifier son contenu en le voyant aligné, on le fait passer par list(). type(range(3)) vaut range, ni list ni tuple, et son affichage direct ne montre pas la suite des éléments mais range(0, 3). De plus, comme il n'est pas à usage unique et peut être parcouru autant de fois que voulu, ce n'est pas non plus un generator. Notons que le fait d'être accessible par indice sans pouvoir remplacer les éléments est identique au tuple, si bien que cette seule propriété ne permet pas de le distinguer d'un tuple.

Q8 | Évaluation en court-circuit

On vérifie ce que renvoient les opérateurs logiques or et and. Quel est le résultat du code suivant ?

print([] or 0 or "z")print(1 and 0 and 2)
  1. True puis False s'affichent dans cet ordre
  2. False puis 0 s'affichent dans cet ordre
  3. z puis 0 s'affichent dans cet ordre
  4. z puis 2 s'affichent dans cet ordre
RéponseC. z puis 0 s'affichent dans cet ordre

or évalue de gauche à droite et renvoie directement la première valeur qui devient vraie. Une liste vide comme 0 étant fausses, c'est finalement "z" qui est renvoyé. and, à l'inverse, renvoie la première valeur qui devient fausse ; 1 étant vrai, c'est au 0 suivant que le résultat faux est déterminé, et 0 est renvoyé, sans que le 2 à l'extrémité droite ne soit évalué. Ce qui est renvoyé n'est pas une valeur booléenne mais la valeur au moment où l'évaluation s'est arrêtée, donc ni True ni False ne peuvent apparaître. and ne renvoie le 2 de l'extrémité droite que si tous les éléments intermédiaires étaient vrais.

Q9 | Comparaison enchaînée

En exécutant le code suivant, qui combine une comparaison enchaînée et un opérateur ternaire, qu'affiche-t-il ?

x = 3print(1 < x < 5)print("yes" if x % 2 else "no")
  1. True puis yes s'affichent dans cet ordre
  2. True puis no s'affichent dans cet ordre
  3. False puis no s'affichent dans cet ordre
  4. False puis yes s'affichent dans cet ordre
RéponseA. True puis yes s'affichent dans cet ordre

1 < x < 5 équivaut à (1 < x) and (x < 5) ; si x vaut 3, les deux conditions sont vérifiées, donc le résultat est True. L'opérateur ternaire s'écrit valeur si vrai if condition else valeur si faux, et la condition est ici x % 2, soit 1. En Python, tout entier différent de 0 est traité comme vrai, donc yes est choisi. En lisant à tort la comparaison enchaînée en l'associant depuis la droite, comme 1 < (x < 5), on obtiendrait x < 5 égal à True, soit 1, puis 1 < 1 donnerait False par erreur. De même, considérer 1 comme faux mènerait à choisir no.

Q10 | Le rôle de pass

Quelle description de l'instruction pass de Python est appropriée ?

  1. Indiquer d'arrêter la boucle elle-même et d'en sortir
  2. Indiquer, là où la syntaxe exige un bloc, qu'il ne se passe rien
  3. Indiquer de sauter le reste de la boucle et de passer à l'itération suivante
  4. Indiquer la fin de l'exécution d'une fonction et le renvoi d'une valeur à l'appelant
RéponseB. Indiquer, là où la syntaxe exige un bloc, qu'il ne se passe rien

Python représentant les blocs par l'indentation, il n'est pas possible d'écrire un bloc vide. pass sert à remplir cet emplacement lorsqu'on prévoit d'écrire le contenu d'une classe ou d'une fonction plus tard, ou lorsqu'on souhaite volontairement ne rien faire dans un cas conditionnel donné. Il n'a aucun effet sur le déroulement du traitement. Passer à l'itération suivante est le rôle de continue, arrêter la boucle celui de break, et renvoyer une valeur à l'appelant celui de return ; leurs rôles diffèrent tous de celui de pass.

Q11 | enumerate

En exécutant le code suivant, qui passe un second argument à enumerate, qu'affiche l'écran ?

for i, name in enumerate(["gu", "choki", "pa"], 1):    print(i, name)
  1. 3 lignes s'affichent : 1 gu / 2 choki / 3 pa
  2. 3 lignes s'affichent : 0 gu / 1 choki / 2 pa
  3. 3 lignes s'affichent : gu 1 / choki 2 / pa 3
  4. Seules 2 lignes s'affichent : 1 gu / 2 choki
RéponseA. 3 lignes s'affichent : 1 gu / 2 choki / 3 pa

enumerate renvoie successivement des tuples (numéro, élément), et son second argument start permet de préciser la valeur à partir de laquelle compter. Ici, 1 étant passé, les numéros sont 1, 2, 3. Confondre cela avec le début par défaut à 0 (start omis) ferait croire que la numérotation commence à 0. Le tuple renvoyé plaçant le numéro avant l'élément, l'ordre dans print(i, name) suit également cet ordre, et le nom n'apparaît jamais en premier. Les 3 éléments étant tous extraits, l'affichage ne s'arrête pas non plus après 2 lignes.

Q12 | Longueur de zip

On passe à zip deux listes de longueurs différentes. Quel est le résultat de l'exécution du code suivant ?

print(list(zip([1, 2, 3], [10, 20])))
  1. [(1, 10), (2, 20)]
  2. [(1, 10), (2, 20), (3,)]
  3. [(1, 10), (2, 20), (3, 0)]
  4. [(10, 1), (20, 2)]
RéponseA. [(1, 10), (2, 20)]

zip extrait un élément à la fois de chacun des itérables passés pour former des tuples, et s'arrête dès que le plus court est épuisé. La seconde liste ayant ici 2 éléments, seuls 2 tuples sont créés, et le 3 restant est discrètement abandonné. Il n'y a pas d'erreur, le manquant n'est pas comblé par 0, et aucun tuple partiel n'est créé. Le contenu des tuples suit l'ordre des arguments passés ; il n'y a donc pas d'inversion comme 10 et 1.

Q13 | Décomposer avec zip

On repasse à zip, avec un astérisque, des données déjà mises en tuples. Quel est le résultat du code suivant ?

pairs = [(1, 'a'), (2, 'b'), (3, 'c')]nums, chars = zip(*pairs)print(nums)print(chars)
  1. [1, 2, 3] s'affiche, suivi de ['a', 'b', 'c']
  2. (1, 2, 3) s'affiche, suivi de ('a', 'b', 'c')
  3. ((1, 'a'),) s'affiche, suivi de ((2, 'b'),)
  4. (1, 'a') s'affiche, suivi de (2, 'b')
RéponseB. (1, 2, 3) s'affiche, suivi de ('a', 'b', 'c')

Avec l'astérisque, chaque tuple de la liste est transmis à zip comme un argument séparé. Cela revient donc à appeler zip((1, 'a'), (2, 'b'), (3, 'c')), qui regroupe les premiers éléments de chaque tuple entre eux, et les seconds entre eux, donnant deux séries : (1, 2, 3) et ('a', 'b', 'c'). C'est la technique classique pour revenir des données mises en tuples à leurs deux séries d'origine ; passer deux fois par zip revient au point de départ. Ce qui est renvoyé étant des tuples et non des listes, il n'y a pas de crochets. En oubliant l'astérisque et en écrivant zip(pairs), c'est le tuple lui-même qui est transmis comme un seul argument, donnant des tuples à un seul élément comme ((1, 'a'),).

Q14 | dict et zip

On construit un dictionnaire à partir d'une série de clés et d'une série de valeurs. Quel est le résultat de l'exécution du code suivant ?

keys = ["a", "b", "c"]vals = [10, 20, 30]print(dict(zip(keys, vals)))
  1. [('a', 10), ('b', 20), ('c', 30)]
  2. {10: 'a', 20: 'b', 30: 'c'}
  3. {'a', 'b', 'c', 10, 20, 30}
  4. {'a': 10, 'b': 20, 'c': 30}
RéponseD. {'a': 10, 'b': 20, 'c': 30}

zip(keys, vals) renvoie successivement des tuples (clé, valeur), et dict() prend cette série de tuples pour construire un dictionnaire. Le premier élément d'un tuple devient la clé et le second la valeur ; les nombres ne peuvent donc pas devenir des clés dans ce cas. Passé par dict(), le résultat n'est pas non plus une liste de tuples. Même affiché avec des accolades, un dictionnaire se distingue par le fait que les paires sont reliées par des deux-points, contrairement à un ensemble où les valeurs sont simplement alignées.

Q15 | Compréhension de liste

En exécutant le code suivant, utilisant une compréhension de liste, quelle liste s'affiche ?

print([n * n for n in range(1, 5)])
  1. [0, 1, 4, 9]
  2. [2, 4, 6, 8]
  3. [1, 4, 9, 16]
  4. [1, 4, 9, 16, 25]
RéponseC. [1, 4, 9, 16]

range(1, 5) renvoie 1, 2, 3, 4 ; la valeur 5 passée comme stop n'est pas incluse. En élevant chacun au carré, on obtient 1, 4, 9, 16. Supposer à tort que range commence à 0 ferait débuter la séquence à 0, et croire que stop est inclus ferait apparaître 25. Lire n * n comme n * 2 ferait apparaître une progression de 2 en 2. Il est utile de retenir qu'une compréhension évalue l'expression de gauche pour chaque élément, et construit une nouvelle liste en alignant les résultats.

Q16 | Compréhension de dictionnaire

On exécute une compréhension écrite avec des deux-points à l'intérieur d'accolades. Quel est le résultat du code suivant ?

words = ["sun", "moon"]print({w: len(w) for w in words})
  1. {'sun': 3, 'moon': 4}
  2. [('sun', 3), ('moon', 4)]
  3. {3: 'sun', 4: 'moon'}
  4. {'sun', 'moon', 3, 4}
RéponseA. {'sun': 3, 'moon': 4}

Une compréhension avec des deux-points à l'intérieur d'accolades est une compréhension de dictionnaire, où le côté gauche des deux-points devient la clé, et le côté droit la valeur. Ici, on obtient un dictionnaire ayant les mots comme clés et leur longueur comme valeurs. Lire clé et valeur en sens inverse ferait croire que la longueur devient la clé. Entourer d'accolades une compréhension donnerait une liste de tuples, mais ici il s'agit d'accolades, donc ce n'est pas le cas. Sans les deux-points, on obtiendrait une compréhension d'ensemble avec seulement les valeurs alignées, mais la présence des deux-points garantit qu'il s'agit bien d'un dictionnaire.

Q17 | Type des parenthèses

On entoure de parenthèses une expression ressemblant à une compréhension. Quel type s'affiche en exécutant le code suivant ?

g = (n for n in range(3))print(type(g))
  1. <class 'range'>
  2. <class 'list'>
  3. <class 'generator'>
  4. <class 'tuple'>
RéponseC. <class 'generator'>

Il n'existe pas en Python d'écriture appelée « compréhension de tuple » ; cette forme entourée de parenthèses devient une expression génératrice. Son type est generator ; elle ne construit pas d'abord toutes les valeurs, mais les calcule une par une au moment où elles sont demandées, ce qui économise la mémoire, mais en tant qu'itérateur, elle s'épuise après un seul usage. Pour obtenir un tuple, il faut l'envelopper avec tuple(). Avec des crochets, on obtiendrait une compréhension de liste, de type list, et en affectant directement range(3), on obtiendrait un range. tuple ne s'obtient pas avec cette écriture.

Q18 | Compréhension d'ensemble

On compte le nombre d'éléments d'un conteneur créé par compréhension d'ensemble. Quel est le résultat du code suivant ?

print(len({n % 4 for n in range(10)}))
  1. 3 s'affiche
  2. 0 s'affiche
  3. 10 s'affiche
  4. 4 s'affiche
RéponseD. 4 s'affiche

Les restes de la division par 4 de chaque valeur de range(10) sont 0, 1, 2, 3, 0, 1, 2, 3, 0, 1. Une compréhension entre accolades sans deux-points étant une compréhension d'ensemble, les valeurs en double sont regroupées en une seule, et il ne reste que les 4 valeurs distinctes 0, 1, 2, 3. Négliger la disparition des doublons et compter simplement le nombre de valeurs donnerait 10. Négliger que 0 fasse partie des restes et ne compter que 3 valeurs distinctes 1, 2, 3 donnerait 3. 0 correspondrait à croire à tort que la compréhension ne produit rien, mais en parcourant range(10), le contenu n'est jamais vide. L'ordre d'affichage d'un ensemble n'étant pas défini, vérifier par le nombre d'éléments est la méthode la plus sûre.

Q19 | Double compréhension

On s'interroge sur l'ordre d'une compréhension alignant deux for. Quel est le résultat de l'exécution du code suivant ?

print([(x, y) for x in [1, 2] for y in "ab"])
  1. [(1, 'a'), (2, 'b'), (1, 'b'), (2, 'a')]
  2. [('a', 1), ('b', 1), ('a', 2), ('b', 2)]
  3. [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
  4. [(1, 'a'), (2, 'a'), (1, 'b'), (2, 'b')]
RéponseC. [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]

Lorsqu'une compréhension aligne deux for, celui écrit à gauche devient la boucle extérieure. C'est exactement le même ordre que si l'on avait imbriqué deux instructions for classiques : x est fixé à 1 pendant que y parcourt a, b, puis x passe à 2 et le même parcours recommence. Lire le for de droite comme extérieur ferait avancer x en premier. Le contenu des tuples suit l'ordre écrit dans l'expression (x, y), donc la lettre ne peut jamais venir en premier.

Q20 | L'argument start

Quelle description de l'argument start, second argument de la fonction native enumerate, est appropriée ?

  1. Il précise le numéro à partir duquel compter ; omis, il compte à partir de 0
  2. Il précise à partir de quel élément extraire ; omis, il commence au début
  3. Il précise de combien en sauter à chaque fois ; omis, il avance d'un pas
  4. Il précise jusqu'à combien compter ; omis, il compte jusqu'à la fin
RéponseA. Il précise le numéro à partir duquel compter ; omis, il compte à partir de 0

Le second argument start d'enumerate détermine uniquement à partir de quel nombre commencer la numérotation attribuée. Sa valeur par défaut étant 0, passer 1 donne une numérotation ordinaire adaptée à l'humain. L'essentiel est que seule la manière de numéroter change, sans que la plage ou le nombre d'éléments extraits ne soient affectés. Il n'a aucun effet de limitation du nombre d'éléments ni de saut, et ne décale pas non plus la position de départ de l'extraction. Ces fonctionnalités correspondent à d'autres outils, comme le découpage (slice).

Q21 | Valeur de retour de sort

On récupère dans une variable la valeur de retour de la méthode sort d'une liste. Quel est le résultat du code suivant ?

nums = [5, 3, 9]result = nums.sort()print(result)print(nums)
  1. [3, 5, 9] puis [3, 5, 9] s'affichent dans cet ordre
  2. None puis [5, 3, 9] s'affichent dans cet ordre
  3. [3, 5, 9] puis [5, 3, 9] s'affichent dans cet ordre
  4. None puis [3, 5, 9] s'affichent dans cet ordre
RéponseD. None puis [3, 5, 9] s'affichent dans cet ordre

sort est une méthode qui trie la liste sur place, et sa valeur de retour est None. result contient donc None, et le résultat trié reste du côté de nums. Croire qu'une nouvelle liste triée est renvoyée ferait apparaître une liste dans result. Négliger que sort modifie la liste d'origine ferait croire que nums reste dans son ordre initial. Pour obtenir une liste triée tout en conservant l'originale, on utilise la fonction native sorted.

Q22 | Différence avec sorted

Quelle différence entre la méthode sort d'une liste et la fonction native sorted est appropriée ?

  1. sort renvoie une nouvelle liste, sorted trie sur place et renvoie None
  2. sort trie sur place et renvoie None, sorted renvoie une nouvelle liste
  3. Les deux renvoient une nouvelle liste, sans que la liste d'origine ne change dans un cas comme dans l'autre
  4. Les deux trient sur place, tout en renvoyant la liste triée
RéponseB. sort trie sur place et renvoie None, sorted renvoie une nouvelle liste

sort est une méthode de liste qui trie la liste elle-même et renvoie None comme valeur de retour. sorted est une fonction native qui ne modifie en rien l'ordre d'origine et renvoie une nouvelle liste triée. On choisit l'une ou l'autre selon qu'on souhaite conserver l'original. Inverser les rôles des deux donnerait une description où sort renverrait une nouvelle liste. Considérer les deux comme destructrices ou non de la même façon mènerait à dire que les deux trient sur place, ou que les deux ne modifient pas l'original. Notons que sorted peut aussi recevoir un itérable autre qu'une liste, et son résultat est toujours une liste.

Q23 | Différence d'append

On compare deux façons d'ajouter une liste à une autre liste. Quel est le résultat du code suivant ?

a = [1, 2]a.append([3, 4])b = [1, 2]b.extend([3, 4])print(a, b)
  1. [1, 2, [3, 4]] [1, 2, [3, 4]]
  2. [1, 2, 3, 4] [1, 2, [3, 4]]
  3. [1, 2, 3, 4] [1, 2, 3, 4]
  4. [1, 2, [3, 4]] [1, 2, 3, 4]
RéponseD. [1, 2, [3, 4]] [1, 2, 3, 4]

append ajoute son argument tel quel comme un seul élément à la fin ; en lui passant une liste, elle devient donc imbriquée, et la longueur n'augmente que de 1. extend ajoute un par un les valeurs extraites en itérant sur l'argument, si bien que le contenu se déploie et la longueur augmente de 2. Inverser les rôles des deux inverserait les résultats de a et b. Considérer que les deux fonctionnent de la même façon mènerait soit à ce que les deux imbriquent, soit à ce que les deux aplatissent. Ces deux méthodes renvoyant toutes deux None, il est aussi utile de retenir qu'il ne faut pas récupérer leur résultat dans une variable.

Q24 | Copie superficielle

On duplique une liste imbriquée par découpage (slice), puis on modifie l'intérieur. Quel est le résultat de l'exécution du code suivant ?

orig = [[1, 2], [3, 4]]copy1 = orig[:]copy1[0].append(9)print(orig)
  1. [[1, 2, 9], [3, 4, 9]]
  2. [[1, 2, 9], [3, 4]]
  3. [[1, 2], [3, 4]]
  4. [[1, 2], [3, 4], 9]
RéponseB. [[1, 2, 9], [3, 4]]

La duplication par découpage est une copie superficielle : la liste extérieure est nouvellement créée, mais les éléments qu'elle contient restent partagés avec l'original. copy1[0] et orig[0] pointant vers la même liste, un append à cet endroit se répercute aussi du côté de l'original. Croire qu'elles sont complètement séparées ferait penser que l'original ne change pas. Seule la liste intérieure du début a été modifiée, donc cela ne s'étend pas aux éléments suivants, et comme rien n'a été ajouté à l'extérieur, 9 n'apparaît pas non plus dans la liste extérieure. Pour séparer complètement même les niveaux intérieurs, on utilise copy.deepcopy.

Q25 | Copie profonde

On compare copie profonde et copie superficielle dans un seul code. Que devient base après l'exécution ?

import copybase = [[1, 2], [3, 4]]d = copy.deepcopy(base)s = base[:]d[0].append(8)s[1].append(7)print(base)
  1. [[1, 2], [3, 4]]
  2. [[1, 2, 8], [3, 4, 7]]
  3. [[1, 2, 8], [3, 4]]
  4. [[1, 2], [3, 4, 7]]
RéponseD. [[1, 2], [3, 4, 7]]

copy.deepcopy duplique jusqu'au contenu imbriqué, si bien que d[0] devient une liste distincte de base[0], et un append à cet endroit n'atteint pas base. En revanche, s étant une copie superficielle, s[1] et base[1] sont la même liste, si bien qu'un append à cet endroit se répercute aussi sur base. Seul le second élément s'allonge donc. Croire que les deux atteignent base ferait allonger les deux éléments intérieurs, et croire qu'aucun des deux ne l'atteint laisserait base inchangé. Confondre copie profonde et copie superficielle ferait allonger seulement le premier élément.

Q26 | Le piège de la multiplication

On tente de créer un tableau à deux dimensions par multiplication de liste. Quel est le résultat de l'exécution du code suivant ?

grid = [[0] * 3] * 2grid[0][1] = 5print(grid)
  1. [[0, 0, 0], [0, 5, 0]]
  2. [[5, 0, 0], [5, 0, 0]]
  3. [[0, 5, 0], [0, 0, 0]]
  4. [[0, 5, 0], [0, 5, 0]]
RéponseD. [[0, 5, 0], [0, 5, 0]]

La multiplication extérieure ne crée pas de nouvelles lignes ; elle aligne simplement deux références vers une seule et même liste. grid[0] et grid[1] étant la même entité, modifier la position d'indice 1 de l'un fait apparaître le même changement à la même position dans l'autre. Croire que les lignes sont indépendantes ferait penser qu'une seule ligne change. La position modifiée étant l'indice 1, le premier élément ne devient pas non plus 5. Pour rendre chaque ligne indépendante, on écrit [[0] * 3 for _ in range(2)].

Q27 | Tuple à un élément

On compare le type d'une valeur simplement entourée de parenthèses avec celui d'une valeur suivie d'une virgule finale. Quel est le résultat du code suivant ?

x = (7)y = (7,)print(type(x).__name__, type(y).__name__)
  1. int puis int s'affichent dans cet ordre
  2. tuple puis tuple s'affichent dans cet ordre
  3. tuple puis int s'affichent dans cet ordre
  4. int puis tuple s'affichent dans cet ordre
RéponseD. int puis tuple s'affichent dans cet ordre

Ce qui détermine un tuple, ce n'est pas la parenthèse mais la virgule. (7) n'est qu'un entier entouré de parenthèses indiquant la priorité de calcul, son type est donc int ; ce n'est qu'avec la virgule finale de (7,) qu'on obtient un tuple à un seul élément. Croire qu'entourer de parenthèses suffit à créer un tuple ferait obtenir tuple dans les deux cas ; confondre les deux donnerait un ordre inversé ou les deux en int. Notons que le tuple vide fait exception : il s'écrit () sans virgule.

Q28 | Contenu d'un tuple

On tente d'ajouter un élément à une liste placée à l'intérieur d'un tuple. Quel est le résultat de l'exécution du code suivant ?

t = (1, [2, 3], 4)t[1].append(5)print(t)
  1. Une TypeError est levée
  2. (1, [2, 3], 4, 5)
  3. (1, [2, 3, 5], 4)
  4. (1, [2, 3], 4)
RéponseC. (1, [2, 3, 5], 4)

Ce qu'un tuple fige, c'est l'association vers quel élément il pointe, et non le contenu de ce vers quoi il pointe. t[1] est une liste, et cette liste elle-même étant mutable, on peut y appliquer append. Une écriture comme t[1] = [], qui affecterait directement un élément du tuple, lèverait une TypeError, mais ici on ne remplace pas un élément. Comme l'ajout se fait à l'intérieur de la liste, la longueur du tuple n'augmente pas, et il ne reste pas non plus totalement inchangé.

Q29 | Affectation à un découpage

On affecte à un découpage (slice) d'une liste une liste de longueur différente. Quel est le résultat du code suivant ?

x = [1, 2, 3, 4, 5]x[1:3] = [9]print(x)
  1. [1, 9, 4, 5]
  2. [1, 9, 3, 4, 5]
  3. [1, 9, 9, 4, 5]
  4. [1, 2, 9, 4, 5]
RéponseA. [1, 9, 4, 5]

Affecter à un découpage remplace entièrement la plage indiquée par le contenu du membre droit. x[1:3] désigne les 2 éléments 2 et 3, qui sont remplacés par le seul élément 9, réduisant la longueur totale de 5 à 4. Croire qu'un seul élément est remplacé laisserait la longueur à 5, et croire que le membre droit est étiré pour correspondre à la largeur ferait apparaître 9 deux fois. Lire le début de la plage avec un décalage d'un cran laisserait 2 subsister. La caractéristique de l'affectation à un découpage est que la largeur du membre gauche et la longueur du membre droit peuvent différer.

Q30 | Insertion et suppression

On effectue successivement une affectation à un découpage de largeur 0 puis un del. Quel est le résultat de l'exécution du code suivant ?

y = [1, 2, 3]y[1:1] = [7, 8]del y[0]print(y)
  1. [7, 8, 3]
  2. [1, 7, 8, 3]
  3. [7, 8, 2, 3]
  4. [2, 3, 7, 8]
RéponseC. [7, 8, 2, 3]

y[1:1] désignant une plage de largeur 0, y affecter revient à insérer sans rien supprimer, ce qui donne [1, 7, 8, 2, 3]. Le del y[0] qui suit retire le 1 initial, laissant [7, 8, 2, 3]. Lire que l'insertion supprime aussi un élément d'origine donnerait 7 8 3, et croire que del supprime une autre position laisserait le 1 initial. Mal lire que le résultat s'ajoute à la fin ferait apparaître 2 et 3 en premier. Il est utile de retenir que del est une instruction sans valeur de retour, qui supprime sans extraire de valeur.

Q31 | Valeur par défaut de get

On interroge avec get une clé absente d'un dictionnaire. Quel est le contenu affiché lors de l'exécution du code suivant ?

d = {"a": 1, "b": 2}print(d.get("z"), d.get("z", 0))
  1. None puis 0 s'affichent dans cet ordre
  2. Une KeyError survient au premier appel
  3. None puis None s'affichent dans cet ordre
  4. 0 puis 0 s'affichent dans cet ordre
RéponseA. None puis 0 s'affichent dans cet ordre

get ne lève pas d'exception lorsque la clé est absente : elle renvoie None si le second argument est omis, ou cette valeur si elle est fournie. Le premier résultat est donc None, le second 0. Croire que 0 est renvoyé même sans second argument ferait obtenir 0 dans les deux cas, et croire que le second argument est ignoré ferait obtenir None dans les deux cas. Écrire d["z"] avec des crochets lèverait une KeyError ; get est justement l'outil pour l'éviter.

Q32 | setdefault

On appelle setdefault à la fois sur une clé déjà présente et sur une clé absente. Quel est le résultat du code suivant ?

d = {"a": 1}print(d.setdefault("a", 99))print(d.setdefault("b", 99))print(d)
  1. 99 puis 99, puis {'a': 99, 'b': 99} s'affiche
  2. 1 puis 99, puis {'a': 1, 'b': 99} s'affiche
  3. 1 puis None, puis {'a': 1, 'b': None} s'affiche
  4. 1 puis 99, puis {'a': 1} s'affiche
RéponseB. 1 puis 99, puis {'a': 1, 'b': 99} s'affiche

setdefault, si la clé existe, se contente de renvoyer la valeur existante sans modifier le dictionnaire. Si la clé n'existe pas, elle insère le second argument comme valeur et le renvoie. Le premier appel renvoie donc la valeur existante 1 sans changer le dictionnaire, et le second insère 99 et le renvoie. Croire que même la valeur existante est écrasée donnerait 99 dans les deux cas, et croire qu'aucune insertion ne se produit laisserait le dictionnaire inchangé. En omettant le second argument, la valeur insérée serait None ; l'apparition de None correspond au cas où ce second argument n'a pas été écrit.

Q33 | Ce que in examine

On vérifie ce qu'examine l'opérateur in appliqué à un dictionnaire. Quel est le résultat du code suivant ?

d = {"x": 1, "y": 2}print("x" in d, 1 in d, 1 in d.values())
  1. True False True s'affichent dans cet ordre
  2. True True True s'affichent dans cet ordre
  3. True False False s'affichent dans cet ordre
  4. False True True s'affichent dans cet ordre
RéponseA. True False True s'affichent dans cet ordre

in appliqué à un dictionnaire n'examine que les clés. "x" étant une clé, le résultat est True ; 1 existe en tant que valeur mais pas en tant que clé, donc False. Pour examiner les valeurs, il faut cibler d.values(), qui donne alors True. Croire que in examine aussi les valeurs ferait obtenir True partout, et croire que values() n'examine pas non plus les valeurs ferait obtenir False au dernier résultat. Inverser les rôles de clé et de valeur ferait obtenir False au premier résultat. Vérifier la présence d'une clé avant d'y accéder avec des crochets est un usage classique.

Q34 | Doublon de clé

On écrit deux fois la même clé dans un littéral de dictionnaire. Quel est le résultat de l'exécution du code suivant ?

d = {"a": 1, "b": 2, "a": 3}print(d)
  1. {'a': 3, 'b': 2}
  2. {'a': 1, 'b': 2}
  3. {'b': 2, 'a': 3}
  4. {'a': 3, 'b': 3}
RéponseA. {'a': 3, 'b': 2}

Un littéral de dictionnaire crée les entrées dans l'ordre, de gauche à droite ; si une même clé apparaît deux fois, la valeur ultérieure écrase la précédente. Cela ne provoque ni erreur ni avertissement. Croire que la première valeur écrite subsiste ferait rester la valeur de a à 1. De plus, un dictionnaire conserve l'ordre d'insertion, et un écrasement ne déplace pas la position ; a reste donc en tête de l'ordre. L'ordre des clés ne change donc pas, et la valeur de b n'est pas non plus affectée par ricochet.

Q35 | Façons de construire un dictionnaire

On compare un dictionnaire construit à partir d'arguments nommés avec un dictionnaire construit à partir d'une série de paires. Quel est le résultat du code suivant ?

d1 = dict(x=1, y=2)d2 = dict([("x", 1), ("y", 2)])print(d1 == d2)print(d1)
  1. False, suivi de {'x': 1, 'y': 2}
  2. True, suivi de {1: 'x', 2: 'y'}
  3. True, suivi de {'x': 1, 'y': 2}
  4. True, suivi de [('x', 1), ('y', 2)]
RéponseC. True, suivi de {'x': 1, 'y': 2}

dict peut construire un dictionnaire aussi bien à partir d'arguments nommés qu'à partir d'une série de paires (clé, valeur). Les deux écritures produisant un dictionnaire de contenu identique, la comparaison donne True. Croire que des méthodes de construction différentes donnent des objets différents ferait obtenir False. Le nom d'un argument nommé devenant directement la chaîne de la clé, il n'y a pas d'inversion entre clé et valeur. Une fois passé par dict, l'objet est déjà un dictionnaire ; il ne reste donc pas sous forme de série de paires.

Q36 | Accolades vides

On compare le type d'accolades vides avec celui de set(). Quels sont les deux noms de type affichés lors de l'exécution du code suivant ?

a = {}b = set()print(type(a).__name__, type(b).__name__)
  1. dict puis dict s'affichent dans cet ordre
  2. set puis set s'affichent dans cet ordre
  3. dict puis set s'affichent dans cet ordre
  4. set puis dict s'affichent dans cet ordre
RéponseC. dict puis set s'affichent dans cet ordre

{1, 2} avec des éléments à l'intérieur devient un ensemble, mais si le contenu est vide, on ne peut trancher ni pour un dictionnaire ni pour un ensemble. Python traite ce cas comme un dictionnaire, donc {} est un dictionnaire vide. Pour obtenir un ensemble vide, on doit obligatoirement écrire set(). Croire que des accolades signifient toujours un ensemble ferait obtenir set dans les deux cas, et confondre les deux inverserait l'ordre. Croire que même set() devient un dictionnaire ferait obtenir dict dans les deux cas. Ensembles et dictionnaires utilisant tous deux des accolades, cette distinction prête facilement à confusion.

Q37 | Opérations sur les ensembles

On effectue une intersection et une différence symétrique entre deux ensembles. Quel est le résultat du code suivant ?

s1 = {1, 2, 3, 4}s2 = {3, 4, 5}print(sorted(s1 & s2), sorted(s1 ^ s2))
  1. [3, 4] puis [1, 2, 3, 4, 5] s'affichent dans cet ordre
  2. [1, 2, 5] puis [3, 4] s'affichent dans cet ordre
  3. [3, 4] puis [1, 2] s'affichent dans cet ordre
  4. [3, 4] puis [1, 2, 5] s'affichent dans cet ordre
RéponseD. [3, 4] puis [1, 2, 5] s'affichent dans cet ordre

L'esperluette est l'intersection : elle conserve 3 et 4, présents dans les deux. Le circonflexe est la différence symétrique : elle rassemble les éléments présents dans un seul des deux, conservant donc 1 et 2, propres à s1, et 5, propre à s2, tandis que les éléments communs 3 et 4 disparaissent. Confondre la différence symétrique avec la différence simple ferait disparaître 5, et inverser les deux opérations inverserait l'ordre d'affichage. Confondre la différence symétrique avec l'union ferait inclure aussi 3 et 4. L'ordre d'affichage d'un ensemble n'étant pas défini, on vérifie ici en triant avec sorted.

Q38 | Suppression des doublons

On fait passer une liste contenant des doublons par un ensemble. Quel est le résultat lors de l'exécution du code suivant ?

data = [3, 1, 3, 2, 1, 1]print(len(set(data)), sorted(set(data)))
  1. 6 puis [3, 1, 3, 2, 1, 1] s'affichent dans cet ordre
  2. 6 puis [1, 2, 3] s'affichent dans cet ordre
  3. 3 puis [3, 1, 2] s'affichent dans cet ordre
  4. 3 puis [1, 2, 3] s'affichent dans cet ordre
RéponseD. 3 puis [1, 2, 3] s'affichent dans cet ordre

En passant une liste à set, les doublons sont éliminés ; il ne reste que les 3 valeurs distinctes 1, 2, 3, donc la longueur est 3. En passant un ensemble à sorted, une nouvelle liste triée par ordre croissant est renvoyée. Croire que les doublons subsistent donnerait un compte de 6. sorted trie par ordre de valeur et non par ordre d'apparition d'origine, donc l'ordre ne suit pas non plus celui de la première apparition. L'ordre d'affichage d'un ensemble en lui-même n'ayant pas de sens, on passe par sorted pour l'aligner.

Q39 | Condition sur les éléments

On tente d'ajouter successivement un tuple puis une liste à un ensemble. Que se passe-t-il en exécutant le code suivant ?

s = set()s.add((1, 2))s.add([3, 4])print(len(s))
  1. 1 s'affiche, la liste est convertie en tuple
  2. Une TypeError survient, une liste ne peut pas être placée dans un ensemble
  3. Une ValueError survient, les types d'éléments ne peuvent pas être mélangés
  4. 2 s'affiche, les deux éléments entrent dans l'ensemble
RéponseB. Une TypeError survient, une liste ne peut pas être placée dans un ensemble

Les éléments d'un ensemble comme les clés d'un dictionnaire doivent être hachables. Un tuple, dont le contenu ne peut pas changer, est hachable et peut donc être ajouté ; une liste, dont le contenu peut changer par la suite, n'est pas hachable, ce qui provoque une TypeError avec un message du type unhashable type. Croire que les deux entrent ferait afficher 2, et croire qu'une conversion implicite en tuple a lieu ferait afficher 1. Le mélange de types n'étant pas en soi un problème, la justification par une ValueError est également erronée.

Q40 | Trois extractions

Quelle combinaison correspond correctement à ce que renvoient respectivement les méthodes keys, values et items d'un dictionnaire ?

  1. keys renvoie les paires de valeurs, values les paires de clés, items le nombre d'entrées
  2. keys renvoie les paires clé-valeur, values les clés, items les valeurs
  3. keys renvoie les valeurs, values les clés, items les paires de valeurs
  4. keys renvoie les clés, values les valeurs, items les paires clé-valeur
RéponseD. keys renvoie les clés, values les valeurs, items les paires clé-valeur

keys renvoie uniquement les clés, values uniquement les valeurs, et items renvoie successivement des tuples (clé, valeur). Pour parcourir un dictionnaire, la forme classique est for k, v in d.items() avec deux variables. Inverser les rôles de ces trois méthodes mènerait à des descriptions comme keys renvoyant les valeurs. Ce que renvoie items est la série des entrées, pas leur nombre ; une description disant qu'items renvoie le nombre d'entrées est donc erronée, et pour connaître ce nombre, on utilise len. Notons que passer directement le dictionnaire à un for extrait les clés.

Entraînement : répondez aux questions de cette page

Cet outil d'entraînement pose les questions dans un ordre aléatoire (il fonctionne lorsque JavaScript est activé). Vous pouvez de toute façon lire toutes les questions et explications ci-dessus.

* Les explications sont fournies à titre d'information pour l'étude. Le programme et le système des examens changent selon les années : vérifiez toujours les annonces officielles de l'organisme qui organise l'examen.

Cette page est une traduction du texte original japonais. En cas de différence entre la traduction et l'original, la version japonaise fait foi. Voir l'original en japonais