- Apprendre
- Informatique et numérique
- Informatique
- Informatique : données, algorithmes, Internet et IA
- Langages et paradigmes de programmation
Langages et paradigmes de programmation
DébutantUnité 5 sur 7 de la formation Formation : Informatique : données, algorithmes, Internet et IA
Ce que tu vas apprendre
- Suis le parcours d’un programme, du code écrit par une personne aux instructions binaires exécutées par un processeur, et compare les façons de réaliser cette traduction : à l’avance, pendant l’exécution du programme ou en combinant les deux.
- Compare le code machine, le langage d’assemblage et les langages de haut niveauDécris les trois niveaux auxquels les programmes peuvent être écrits, montre comment une ligne de code de haut niveau correspond à plusieurs instructions machine et explique pourquoi la plupart des logiciels sont écrits au niveau le plus élevé.
- Explique le rôle d’un compilateurDécris comment un compilateur traduit à l’avance un programme entier, nomme ses principales étapes et explique ce qu’il advient des erreurs et de l’exécutable terminé.
- Explique les interpréteurs, le bytecode et la compilation à la voléeDécris comment un interpréteur exécute un programme, comment des langages comme Java et Python combinent compilation et interprétation, et comment évaluer les compromis entre ces approches.
- Compare le code machine, le langage d’assemblage et les langages de haut niveau
- Distingue les règles d’écriture du code de celles qui définissent ce qu’il fait, reconnais les éléments communs à la plupart des langages de haut niveau et suis de courts programmes pour prévoir leurs résultats.
- Distingue la syntaxe de la sémantiqueExplique la différence entre la forme d’un programme et son sens, distingue les erreurs de syntaxe, d’exécution et de logique, et repère les cas où des langages donnent un sens différent à un code d’apparence identique.
- Suis l’exécution des structures communes aux programmes de haut niveauReconnais les structures présentes dans presque tous les langages de haut niveau (variables, expressions, contrôle du déroulement et fonctions avec leur portée) et suis un court programme étape par étape pour prévoir sa sortie.
- Distingue la syntaxe de la sémantique
- Décris comment les langages classent et vérifient les valeurs selon leur type, où un programme conserve ses données pendant son exécution et comment la mémoire est réservée et libérée, par la personne qui programme ou automatiquement, avec les compromis de sûreté et de vitesse associés à chaque choix.
- Compare la manière dont les langages utilisent et vérifient les types de donnéesExplique ce qu’est un type de données, compare le typage statique et dynamique et les règles de conversion strictes et souples, puis prévois comment les langages courants traitent les mélanges de types et les limites des nombres.
- Décris où un programme conserve ses données pendant son exécutionExplique que les variables correspondent à des emplacements mémoire nommés, décris les rôles de la pile d’appels et du tas, et distingue la copie d’une valeur du partage d’une référence.
- Compare la gestion manuelle et la gestion automatique de la mémoireExplique comment la mémoire est libérée dans les langages où la personne qui programme la gère et dans ceux qui utilisent le ramassage des miettes ou des règles de possession, et relie les erreurs mémoire aux plantages et aux failles de sécurité.
- Compare la manière dont les langages utilisent et vérifient les types de données
- Décris les programmes comme des séquences de commandes qui modifient l’état enregistré, puis comme des ensembles d’objets qui réunissent données et comportement, et utilise les classes, l’encapsulation, l’héritage et le polymorphisme pour organiser le code.
- Décris les styles impératif et procéduralExplique que la programmation impérative utilise des commandes successives qui modifient l’état d’un programme, montre comment les procédures et un contrôle du déroulement structuré organisent ces commandes, et reconnais les risques liés à un état partagé qui change.
- Modélise des données et des comportements à l’aide de classes et d’objetsExplique comment la programmation orientée objet regroupe les données et les opérations qui les concernent dans des objets, lis et conçois une classe simple, et utilise l’encapsulation pour protéger les données d’un objet.
- Réutilise et étends des classes grâce à l’héritage, au polymorphisme et à la compositionUtilise l’héritage pour construire des classes spécialisées, explique comment le polymorphisme permet à un même appel de se comporter différemment selon les objets, et détermine quand combiner des objets au lieu d’utiliser l’héritage.
- Décris les styles impératif et procédural
- Crée des programmes à partir de fonctions qui renvoient de nouvelles valeurs au lieu de modifier les anciennes, et écris des programmes qui indiquent le résultat voulu tandis qu’un moteur décide comment le calculer.
- Crée des programmes à partir de fonctions pures et de données immuablesExplique les fonctions pures, l’immuabilité, les fonctions de première classe et d’ordre supérieur et la récursivité, puis réécris une boucle sous forme d’une chaîne de
map,filteretreduce. - Décris le résultat attendu à l’aide de langages déclaratifsExplique que la programmation déclarative consiste à indiquer le résultat voulu plutôt que la manière de l’obtenir, et lis des exemples dans un langage de requête de base de données, un langage logique et d’autres notations déclaratives.
- Crée des programmes à partir de fonctions pures et de données immuables
- Résous le même problème dans différents styles, reconnais que la plupart des langages modernes mélangent les paradigmes et évalue les facteurs techniques et pratiques qui déterminent si un langage convient à un projet.
- Résous un même problème selon plusieurs paradigmesÉcris la même petite tâche dans les styles impératif, orienté objet, fonctionnel et déclaratif, compare ce que chaque version facilite et reconnais les langages multiparadigmes.
- Choisis un langage de programmation adapté à une tâcheÉvalue les facteurs qui déterminent si un langage convient à un projet (plateforme, domaine, performances, sûreté, écosystème et personnes) et justifie un choix pour un scénario concret.
- Résous un même problème selon plusieurs paradigmes
Langages et paradigmes de programmation : cours
1Du code source aux instructions exécutées
Un processeur exécute de petites instructions binaires, tandis que les humains écrivent généralement des programmes dans des langages construits autour du problème à résoudre. Des traducteurs relient ces deux niveaux. Suis un même calcul à travers ces couches, puis compare la construction d’un programme avant son exécution avec son interprétation ou la compilation de certaines parties pendant son exécution. Le chemin choisi influe sur la portabilité, le temps de démarrage, la vitesse et le moment où une erreur peut apparaître.
1.1 Trois niveaux de code de programme
Une ligne comme x = a + b désigne un calcul sans mentionner de registre du processeur. L’abstraction rend cela possible : un langage masque les détails de niveau inférieur pour que la personne qui programme puisse se concentrer sur le travail que le programme doit accomplir.
Ce que le processeur exécute
Le code machine se compose d’instructions codées sous forme de nombres binaires. L’architecture du jeu d’instructions d’un processeur définit ses instructions et leur encodage. x86-64 est courant dans les PC et les serveurs ; ARM est courant dans les téléphones, les tablettes et de nombreux ordinateurs portables. Le code construit pour une architecture doit être traduit ou émulé pour fonctionner sur une autre.
L’hexadécimal permet d’afficher les octets des instructions de façon plus courte. Sur x86-64, B8 01 00 00 00 place 1 dans le registre EAX. Les octets 01 D8 ajoutent la valeur d’EBX à EAX. Les registres sont de petits emplacements de stockage à l’intérieur du processeur. Chaque instruction effectue une petite action, comme déplacer des données, additionner, comparer ou effectuer un saut. Le cycle de lecture-décodage-exécution exécute ces instructions.
Des noms pour les mêmes petites actions
Le langage d’assemblage remplace les octets des instructions par des noms lisibles appelés mnémoniques. Les deux instructions x86-64 peuvent s’écrire :
mov eax, 1
add eax, ebx
Un assembleur convertit le langage d’assemblage en code machine. Une ligne d’instruction ordinaire produit normalement une instruction machine. Les noms de registres et les instructions restent propres à une architecture du jeu d’instructions : utiliser des mots au lieu d’octets ne rend donc pas le langage d’assemblage portable.
Décrire un problème en moins de lignes
Un langage de haut niveau fournit des variables, des expressions, des conditions, des boucles et des fonctions. Python, C, Java et JavaScript permettent de décrire des opérations sans sélectionner de registres. Une instruction nécessite généralement plusieurs instructions machine. Une traduction simplifiée de x = a + b effectuerait les étapes suivantes :
- Charger la valeur de
adans un registre. - Charger la valeur de
bdans un autre registre. - Additionner les valeurs des registres.
- Stocker le résultat pour
x.
Un compilateur réel peut combiner ou réorganiser ces étapes tout en conservant le comportement requis.
| Niveau | Ce que la personne qui programme écrit | Qui gère les détails du matériel ? |
|---|---|---|
| Code machine | Instructions codées du processeur | La personne qui programme ou le générateur de code |
| Langage d’assemblage | Mnémoniques et registres | La personne qui programme, avec un assembleur qui code les instructions |
| Code de haut niveau | Variables et opérations sur les données du problème | Un compilateur, un interpréteur ou une combinaison des deux |
Le code source de haut niveau est plus facile à lire, à modifier et à vérifier. Un code source C portable peut être compilé une première fois pour un système x86-64, puis pour un système ARM. Chaque version construite nécessite tout de même une prise en charge adaptée du système d’exploitation et des bibliothèques. La portabilité du code source ne rend pas un exécutable natif universel.
Pourquoi les niveaux inférieurs restent utiles
Le langage d’assemblage reste utile dans le code de démarrage, certaines routines des systèmes d’exploitation et des pilotes, les petits systèmes embarqués et l’analyse de sécurité des programmes dont le code source n’est pas disponible. Pour le code applicatif courant, les compilateurs modernes produisent généralement du code machine dont les performances sont comparables à celles du langage d’assemblage écrit à la main.
Le passage vers des niveaux plus élevés s’est fait progressivement. Le langage d’assemblage s’est répandu à la fin des années 1940 et au début des années 1950. Le système A-0 de Grace Hopper, en 1952, a contribué à faire progresser la traduction automatique. IBM a publié FORTRAN en 1957 ; COBOL a suivi en 1959 et Lisp est apparu vers 1958-1960. « Haut niveau » désigne l’éloignement des détails du matériel, et non la difficulté d’apprentissage du langage.
Points clés
- Un processeur exécute les instructions machine définies par son architecture du jeu d’instructions.
- Un assembleur code les instructions en langage d’assemblage ; ce langage reste lié à une architecture du jeu d’instructions.
- Les instructions de haut niveau masquent les étapes propres au matériel et deviennent généralement plusieurs instructions machine.
- Un code source portable peut être traduit pour différentes cibles ; le code machine natif est propre à une cible.
- Le code de bas niveau reste utile lorsque l’accès au matériel ou l’analyse l’exige.
1.2 Comment un compilateur construit un programme
Un compilateur traduit le code source en code cible. La cible peut être du code machine natif ou une forme intermédiaire comme le bytecode. La compilation anticipée a lieu avant l’exécution du programme ; la compilation à la volée a lieu pendant son exécution.
Du texte aux instructions
Pour une construction anticipée, plusieurs étapes transforment un fichier source en instructions :
- L’analyse lexicale découpe le texte en unités lexicales : des éléments porteurs de sens comme les noms, les opérateurs et les nombres.
- L’analyse syntaxique vérifie la grammaire et construit une structure qui montre comment les éléments s’assemblent.
- L’analyse sémantique vérifie les règles liées au sens, notamment les déclarations et les combinaisons de types autorisées.
- L’optimisation trouve une façon plus efficace d’effectuer le travail demandé.
- La génération de code produit les instructions cibles.
Considère l’instruction C total = price * 3;. Ses six unités lexicales sont total, =, price, *, 3 et ;. Les noms sont des identifiants, 3 est un littéral entier et le point-virgule termine l’instruction. L’analyse syntaxique indique que la multiplication fait partie de l’affectation : calculer price * 3, puis stocker sa valeur dans total.
Ces étapes ont des rôles différents. Une suite d’unités lexicales correctement écrites peut tout de même enfreindre la grammaire. Une instruction grammaticalement correcte peut tout de même employer un type de manière incorrecte. Trouver les unités lexicales ne suffit pas à établir qu’un programme est valide.
Effectuer le même travail à moindre coût
Supposons que le code source contienne seconds_per_day = 60 * 60 * 24. Les facteurs sont des constantes, donc le compilateur peut multiplier 60 par 60 pour obtenir 3 600, puis multiplier par 24 pour obtenir 86 400 pendant la construction. Il peut placer la valeur finale dans le résultat produit au lieu de répéter la multiplication à l’exécution. C’est le repliement des constantes.
Une optimisation doit préserver le comportement exigé par le langage. Elle ne peut pas remplacer un calcul par un autre plus rapide qui donnerait des résultats différents de ceux requis. L’objectif est de réduire le travail, le temps ou la mémoire nécessaires tout en conservant le sens du programme.
Assembler les éléments
Les grands programmes sont généralement construits à partir de nombreux fichiers. Un compilateur peut produire un fichier objet pour chaque fichier source. Un fichier objet contient du code traduit qui peut encore nécessiter des liens avec d’autres codes. Un éditeur de liens réunit les éléments et résout les appels aux routines de bibliothèques, comme les fonctions d’affichage ou de calcul.
| Méthode d’édition de liens | Origine du code de bibliothèque à l’exécution |
|---|---|
| Édition de liens statique | Code inclus dans l’exécutable pendant la construction |
| Édition de liens dynamique | Bibliothèque partagée compatible chargée au démarrage ou pendant l’exécution |
L’exécutable obtenu est construit pour une plateforme cible, notamment une architecture du jeu d’instructions et des conventions de système d’exploitation. L’utilisateur n’a pas besoin du compilateur anticipé pour l’exécuter. Des bibliothèques partagées ou des composants d’exécution peuvent toutefois rester nécessaires.
Comprendre ce que prouve une construction réussie
En Java, int count = "five"; échoue à une vérification de type avant l’exécution. Du texte ne peut pas initialiser cette variable entière. Le programme doit réussir les vérifications requises à la compilation avant qu’une version exécutable soit disponible.
D’autres défauts subsistent après la compilation. Une division par un zéro saisi par l’utilisateur dépend des données d’exécution. Un programme censé effectuer une soustraction mais qui effectue une addition peut s’exécuter et renvoyer une valeur incorrecte. La compilation vérifie les règles du langage ; les tests doivent aussi vérifier que le résultat correspond à la tâche prévue.
Points clés
- La compilation traduit le code source en code cible, qui n’est pas nécessairement du code machine.
- Les étapes du compilateur vérifient les unités lexicales, la structure et le sens avant de produire les instructions cibles.
- L’optimisation préserve le comportement requis ; le repliement des constantes évalue les constantes pendant la construction.
- Un éditeur de liens relie les fichiers objets et le code des bibliothèques.
- Une construction réussie peut encore contenir des erreurs d’exécution ou de logique.
1.3 Interprétation, bytecode et compilation à la volée
Un programme n’a pas nécessairement besoin de devenir un exécutable natif avant qu’un utilisateur le lance. Un interpréteur exécute le programme par l’intermédiaire d’un autre programme en cours d’exécution. De nombreuses implémentations combinent interprétation et compilation ; la question utile est donc de savoir comment le système considéré exécute le code.
Exécuter au moyen d’un interpréteur
Un interpréteur doit être disponible chaque fois qu’il exécute le programme. Une REPL interactive répète quatre actions : lire une entrée, l’évaluer, afficher un résultat et recommencer pour l’entrée suivante. Elle fournit un retour rapide lorsqu’on essaie de petites expressions.
Interpréter ne signifie pas nécessairement lire le code source brut une ligne à la fois. CPython, l’implémentation standard de Python, compile d’abord un fichier en bytecode. Une machine virtuelle logicielle exécute ensuite ces instructions internes. Le bytecode appartient à cet environnement d’exécution ; il ne s’agit pas du jeu d’instructions natif du processeur.
Pourquoi certaines erreurs apparaissent plus tôt
Compare deux fichiers Python. Celui-ci a une syntaxe valide :
print("start")
x = 10
print(x + "5")
Il affiche « start », associe x à 10, puis déclenche TypeError. L’addition de cet entier à une chaîne de caractères échoue uniquement lorsque l’expression est exécutée. Une opération similaire dans une branche non parcourue peut passer inaperçue jusqu’à l’exécution de cette branche.
Supprime maintenant la parenthèse fermante de la dernière ligne. CPython ne peut pas compiler le fichier, car sa syntaxe est incomplète. Il signale une erreur de syntaxe avant l’exécution de toute instruction du fichier, donc « start » n’est pas affiché. La vérification syntaxique du fichier entier et la vérification à l’exécution d’une opération exécutée ont lieu à des moments différents.
Partager le bytecode, spécialiser pendant l’exécution
Java illustre une approche hybride courante. javac Hello.java produit Hello.class, qui contient du bytecode. java Hello lance une machine virtuelle Java compatible. Différentes implémentations de cette machine virtuelle permettent à ce bytecode de s’exécuter sur différentes familles de processeurs. Le bytecode est portable entre environnements d’exécution compatibles ; chaque environnement doit lui-même fonctionner sur sa plateforme hôte. C# cible de la même façon un langage intermédiaire utilisé par .NET.
Un compilateur à la volée produit du code machine pendant l’exécution du programme. L’environnement d’exécution HotSpot de Java commence généralement par interpréter le code et compile les méthodes fréquemment utilisées. Les moteurs JavaScript des navigateurs combinent aussi des techniques d’exécution, dont la compilation à la volée. Compiler les parties très sollicitées peut améliorer la vitesse sur la durée, mais utilise du temps et de la mémoire pendant l’exécution.
| Approche | Avantage utile | Coût à considérer |
|---|---|---|
| Construction native anticipée | Travail traduit avant le lancement ; aucun compilateur nécessaire pour l’utilisateur | Versions compatibles distinctes pour les plateformes cibles |
| Interprétation | Retour rapide après modification et exécution avec un interpréteur installé | Le travail répété à l’exécution peut ralentir le code riche en calculs |
| Bytecode avec machine virtuelle et compilation à la volée | Code intermédiaire portable avec optimisation à l’exécution | Installation de l’environnement, temps de mise en régime et mémoire supplémentaires |
Une boucle qui additionne un milliard de valeurs s’exécute généralement beaucoup plus vite en C compilé que dans CPython : CPython effectue le travail d’interprétation et vérifie les types autour de nombreuses opérations. Pour un script qui renomme 20 fichiers, l’effort de développement peut compter bien davantage que cette différence. Mesure la charge de travail avant de te servir de la désignation d’un langage pour prévoir sa vitesse.
Compilé et interprété décrivent des choix d’implémentation. CPython utilise l’interprétation du bytecode ; PyPy utilise des techniques de compilation à la volée. Un langage peut avoir plusieurs implémentations, et une implémentation peut utiliser plusieurs méthodes d’exécution.
Points clés
- Un interpréteur exécute un programme et doit être présent pour le faire fonctionner.
- CPython compile un fichier en bytecode avant de l’exécuter.
- Une erreur de syntaxe peut empêcher le démarrage d’un fichier ; une erreur d’exécution apparaît lorsque l’opération concernée s’exécute.
- Un compilateur à la volée crée du code natif pendant l’exécution, souvent pour les parties fréquemment utilisées.
- Les méthodes d’exécution dépendent des implémentations et les performances dépendent de la charge de travail.
2Lis de courts programmes pour comprendre ce qu’ils font
Lire du code soulève deux questions : sa forme respecte-t-elle les règles du langage et que signifient ses opérations ? Une expression d’apparence identique peut se comporter différemment dans deux langages. Apprends à classer les erreurs, puis suis les affectations, les décisions, les boucles et les appels de fonctions dans leur ordre d’exécution. Un petit tableau des valeurs qui changent rend visible la différence entre ce que la personne qui programme voulait obtenir et ce que le programme fait réellement.
2.1 Syntaxe, sens et trois types d’erreurs
La syntaxe fixe les règles qui déterminent si le texte d’un programme est valide. La sémantique donne son sens à ce texte. Un programme peut respecter toutes les règles de grammaire et tout de même calculer un résultat incorrect : vérifier son apparence n’est donc que la première étape de sa lecture.
Reconnaître les éléments et la grammaire
Les unités lexicales comprennent les mots-clés comme if, les identifiants comme total, les littéraux comme 3 ou "hello", les opérateurs comme + et ==, et la ponctuation. Une grammaire décrit comment ces éléments peuvent former des expressions et des instructions.
Python exige des deux-points après if x > 3: et utilise l’indentation pour marquer les instructions contrôlées par cette condition. C, Java et JavaScript utilisent des accolades pour les blocs ; C et Java exigent aussi des points-virgules après de nombreuses instructions. Dans ces langages, l’indentation aide le lecteur humain, mais ne détermine pas le bloc.
Des syntaxes différentes peuvent exprimer une action similaire. Ces instructions affichent un message de salutation :
print("hello")
printf("hello\n");
System.out.println("hello");
La séquence d’échappement C \n ajoute un saut de ligne. Connaître l’idée de l’affichage aide à apprendre la façon dont chaque langage l’exprime.
Les mêmes symboles peuvent avoir des sens différents
| Expression et langage | Résultat | Règle |
|---|---|---|
7 / 2 en Python ou JavaScript | 3.5 | La division produit un résultat fractionnaire |
7 / 2 avec des opérandes entiers en C ou Java | 3 | La division entière tronque vers zéro |
-7 // 2 en Python | -4 | La division avec arrondi inférieur arrondit vers le bas |
-7 / 2 avec des entiers en C | -3 | La troncature se fait vers zéro |
"3" * 4 en Python | "3333" | Une chaîne de caractères est répétée |
"3" * 4 en JavaScript | 12 | Le texte est converti en nombre pour la multiplication |
L’apparence familière d’un symbole ne détermine pas son sens d’un langage à l’autre.
L’ordre d’évaluation compte aussi. La priorité fait que la multiplication et la division sont effectuées avant l’addition. Avec a = 4 et b = 6, average = a + b / 2 divise d’abord 6 par 2, puis ajoute 4, ce qui produit 7,0. Les parenthèses dans average = (a + b) / 2 font effectuer la somme en premier et donnent 5,0.
Distinguer les erreurs
Une erreur de syntaxe enfreint la grammaire, comme des deux-points manquants en Python. Une erreur d’exécution se produit lors d’une opération exécutée, comme une division par zéro en Python. Une erreur de logique laisse l’exécution se dérouler, mais donne une réponse non voulue, comme dans le calcul incorrect de la moyenne.
Certaines expressions valides sautent volontairement une opération. En Python, x != 0 and 10 / x > 2 utilise l’évaluation en court-circuit. Lorsque x vaut zéro, la première condition est fausse, donc la partie droite n’est pas évaluée et aucune division n’a lieu. Inverser ces conditions supprimerait cette protection. Python écrit and, or et not ; C, Java et JavaScript utilisent &&, || et !.
L’affectation et la comparaison constituent une autre distinction importante. = stocke une valeur ; == teste l’égalité. C autorise if (x = 5) : cette instruction affecte 5 et traite cette valeur non nulle comme vraie. Python rejette if x = 5: comme une syntaxe invalide. La même erreur peut donc être détectée à une étape différente selon le langage.
Points clés
- La syntaxe définit la forme valide ; la sémantique définit le sens.
- Les opérateurs peuvent avoir des sens différents selon les langages.
- La priorité et les parenthèses déterminent les opérations regroupées.
- Les erreurs de syntaxe, d’exécution et de logique nécessitent des vérifications différentes.
- L’évaluation en court-circuit peut éviter une opération dangereuse.
- L’affectation stocke une valeur ; la comparaison d’égalité teste des valeurs.
2.2 Suis les valeurs à travers le déroulement du programme et les appels
Un court programme est plus facile à comprendre lorsque tu suis une instruction exécutée à la fois. Garde les valeurs actuelles visibles au lieu de passer directement de la première ligne à un résultat supposé.
Expressions et affectations
Une expression produit une valeur : price * 3, x > 10 et len(name) en sont des exemples. Une instruction effectue une action, comme affecter une valeur ou contrôler un bloc. Dans total = price * 3, l’expression de droite est évaluée avant que son résultat soit affecté à total.
Une variable est un nom qui fait référence à une valeur. L’affectation donne au nom sa valeur actuelle ; elle n’affirme pas, comme une équation mathématique, que les deux membres ont toujours été égaux. Si count contient 4, count = count + 1 lit d’abord 4, ajoute 1 et associe count à 5.
Suivre la prochaine instruction exécutée
Trois structures organisent le déroulement du programme :
- La séquence exécute les instructions dans l’ordre.
- La structure conditionnelle utilise une condition pour choisir un chemin, par exemple avec
ifetelse. - L’itération répète un bloc au moyen d’une boucle
forouwhile.
Le théorème de la programmation structurée, associé à Böhm et Jacopini en 1966, montre que ces structures peuvent exprimer n’importe quel algorithme. Une boucle for parcourt généralement les éléments d’une collection. Une boucle while se répète tant que sa condition reste vraie.
Considère cette boucle Python :
total = 0
for n in [3, 8, 5]:
total = total + n
L’affectation indentée s’exécute une fois pour chaque élément. Un tableau de suivi d’exécution indique l’état après chaque mise à jour :
| Moment | n actuel | total |
|---|---|---|
| Avant la boucle | Pas encore d’élément | 0 |
| Première mise à jour | 3 | 3 |
| Deuxième mise à jour | 8 | 11 |
| Troisième mise à jour | 5 | 16 |
Chaque nouveau total utilise le précédent. Réinitialiser total à l’intérieur de la boucle produirait un calcul différent, car les additions précédentes seraient perdues.
Entrer dans une fonction et en sortir
Une fonction regroupe une tâche sous un nom. Un paramètre est un nom dans la définition ; un argument est une valeur fournie par un appel :
def area(width, height):
return width * height
result = area(3, 4)
L’appel associe width à 3 et height à 4. La multiplication produit 12 et return le transmet à l’appelant. L’exécution reprend à l’affectation, donc result reçoit 12. Ces noms décrivent une tâche qui peut être réutilisée avec d’autres arguments.
Un nom a une portée
La portée détermine où un nom peut être utilisé. Les noms créés dans une fonction sont normalement locaux à cet appel. Un nom défini au niveau principal est global à son module. Des appels distincts ont des associations locales distinctes, même lorsqu’ils utilisent des noms identiques.
count = 7
def reset():
count = 0
reset()
print(count)
Ce code affiche 7. L’affectation dans reset crée une variable locale count ; elle ne remplace pas l’association globale. Une fonction peut plutôt renvoyer un résultat et laisser l’appelant l’affecter.
La portée et la durée de vie répondent à des questions différentes. Un nom local n’est pas accessible au code extérieur simplement parce que l’objet auquel il faisait référence existe encore. Les objets renvoyés peuvent rester en vie, et une fonction imbriquée renvoyée peut conserver l’accès aux variables de l’appel qui l’englobe ; cet environnement conservé fait partie d’une fermeture lexicale.
Points clés
- Évalue l’expression de droite avant de mettre à jour la cible d’une affectation.
- Suis la séquence, les structures conditionnelles et les itérations dans leur ordre réel d’exécution.
- Les paramètres sont des noms dans la définition ; les arguments sont les valeurs fournies.
- Une valeur de retour transmet un résultat à l’appelant.
- Les noms locaux et globaux peuvent avoir des associations distinctes ; la portée diffère de la durée de vie.
3Types, références et mémoire
Les valeurs ont besoin d’un type, d’un emplacement en mémoire et d’une durée de vie. Ces choix expliquent pourquoi un texte qui ressemble à un nombre peut refuser les opérations arithmétiques, pourquoi deux noms peuvent donner accès à la même liste modifiée et pourquoi des données inutilisées restent parfois allouées. Compare les vérifications effectuées par les langages, suis un appel de fonction en mémoire et examine trois façons de libérer de l’espace. Chaque approche modifie l’équilibre entre contrôle, facilité d’utilisation et sûreté.
3.1 Les types déterminent quelles opérations ont un sens
Un type de données définit les valeurs possibles et les opérations qu’elles permettent. Un entier permet les opérations arithmétiques sur les nombres entiers ; un booléen représente vrai ou faux. Les chaînes de caractères, les listes, les enregistrements et les objets combinent ou organisent d’autres valeurs. Les types déterminent aussi la façon dont les valeurs sont représentées en mémoire, même si la taille d’une valeur peut dépendre de cette valeur et de l’implémentation.
Quand les types sont-ils vérifiés ?
| Approche | Moment des vérifications | Exemple |
|---|---|---|
| Typage statique | Avant l’exécution | Java rejette int count = "five"; |
| Typage dynamique | Pendant l’exécution | Python vérifie si une opération exécutée convient à ses opérandes |
En Python, count = 5 suivi de count = "five" est valide. Le nom fait désormais référence à une chaîne de caractères. L’évaluation de count + 1 déclenche alors TypeError. Le typage dynamique donne des types définis aux valeurs ; il ne supprime pas les types.
Le typage statique n’exige pas une indication de type visible sur chaque variable. L’inférence de types permet à un compilateur de déduire un type à partir du code environnant. Kotlin déduit un entier pour val n = 5, et Rust peut déduire un entier pour let n = 5;. Les annotations de type facultatives de Python sont différentes : des outils distincts comme mypy peuvent les vérifier, mais l’exécution ordinaire de Python ne les impose pas.
Quand les valeurs sont-elles converties ?
Le moment de la vérification et la rigueur des conversions sont deux questions distinctes. Python rejette "3" + 4 ; la conversion explicite dans int("3") + 4 donne 7. JavaScript applique différentes règles de conversion automatique selon les opérateurs :
"3" + 4donne la chaîne"34"."3" * 4donne le nombre12."3" - 4donne le nombre-1.
La conversion automatique est appelée conversion implicite. Les termes typage « fort » et « faible » sont des façons informelles de parler de la rigueur des conversions, et non d’autres noms pour le typage statique et dynamique. Python est dynamique et strict pour l’addition de types différents ci-dessus ; C est statique et autorise tout de même de nombreuses conversions numériques implicites.
La fonction input() de Python renvoie une chaîne de caractères. Si une personne saisit « 30 », le résultat est toujours de type str. age + 1 échoue, tandis que int(age) + 1 donne 31 lorsque age contient ce texte. Ce qui apparaît à l’écran ne suffit pas à déterminer le type.
La représentation des nombres a des limites
Un entier signé sur 32 bits représente les valeurs de −2 147 483 648 à 2 147 483 647. Le type int de Java reboucle de la valeur maximale à la valeur minimale lorsqu’on ajoute 1. En C, le dépassement de capacité d’un entier signé est un comportement indéfini : il ne faut donc pas supposer que le même rebouclage se produit. Les entiers Python grandissent autant que la mémoire le permet : 2 ** 100 vaut exactement 1 267 650 600 228 229 401 496 703 205 376.
Les nombres à virgule flottante représentent la plupart des fractions décimales de manière approximative en binaire. En Python et JavaScript, 0.1 + 0.2 produit 0.30000000000000004. Comparer directement ce résultat à 0.3 donne faux.
Lorsqu’un calcul permet une petite erreur, compare les valeurs à l’aide d’une tolérance choisie pour la tâche, par exemple abs(a - b) < 1e-9. Une tolérance absolue aussi petite ne convient qu’à certaines échelles. Pour une comptabilité décimale exacte, utilise des nombres entiers dans la plus petite unité ou un type décimal adapté. Modifier la comparaison ne peut pas rendre exacte une représentation approximative.
Points clés
- Un type définit des valeurs et les opérations qu’elles permettent.
- Le typage statique et dynamique décrit le moment des vérifications, tandis que la conversion implicite concerne les règles de conversion.
- L’inférence de types prend en charge le typage statique ; les annotations Python nécessitent des outils de vérification distincts.
- Les entiers de taille fixe peuvent subir un dépassement de capacité, dont le comportement dépend du langage.
- L’arrondi en virgule flottante influe sur les tests d’égalité et le choix de la représentation des nombres.
3.2 Suis les noms, les objets et les appels de fonctions en mémoire
La mémoire principale stocke les données d’un programme pendant son exécution. Chaque octet possède une adresse. Les langages fournissent des noms pour permettre aux personnes qui programment de manipuler des valeurs sans écrire d’adresses brutes à chaque opération.
Les appels utilisent des blocs d’activation ; les objets ont une durée de vie
En C, un int occupe généralement 4 octets et &x obtient l’adresse de x. Les langages de plus haut niveau masquent souvent les adresses derrière des références. Il faut distinguer l’association qui nomme un objet de l’objet lui-même.
La pile d’appels assure le suivi des appels de fonctions actifs. Chaque appel possède un bloc d’activation contenant des informations comme les paramètres, les associations locales et l’endroit où l’exécution doit reprendre après le retour. Si main appelle area(3, 4), un bloc pour area est ajouté au-dessus de celui de l’appelant. La fonction calcule 12, se termine et son bloc est retiré. Pour les appels imbriqués ordinaires, l’appel le plus récent se termine en premier.
La récursivité ajoute un appel pour chaque niveau non terminé. Si les appels continuent de s’accumuler, l’espace disponible dans la pile peut s’épuiser. Python déclenche normalement RecursionError après environ 1 000 appels imbriqués, la limite dépendant de ses paramètres. Une condition d’arrêt doit être atteinte pour qu’un calcul récursif fini se termine.
Le tas contient les objets alloués dynamiquement, comme une liste qui grandit ou un objet qui survit à la fonction qui l’a créé. L’allocation mémoire réserve de l’espace de stockage. Une liste renvoyée peut rester sur le tas après la fin de l’appel qui l’a créée.
Copier une référence partage un objet
Une référence désigne un objet. Un pointeur en C ou C++ représente explicitement une adresse utilisée pour accéder à des données. Copier une référence copie l’accès à l’objet, et non toutes les données de l’objet.
a = [1, 2]
b = a
b.append(3)
Après la première ligne, il existe une liste. La deuxième ligne lui associe un autre nom. La dernière ligne modifie cette liste : a et b montrent donc toutes deux [1, 2, 3]. Ce partage est appelé aliasing. Utiliser b = a.copy() créerait une liste externe distincte ; pour cette liste d’entiers, ajouter un élément à la nouvelle liste laisserait a inchangée.
Compare l’affectation et la mutation :
x = 5
y = x
y = y + 1
L’entier 5 est immuable. L’addition produit 6, et seul y est associé à une nouvelle valeur. x fait toujours référence à 5. Les nombres, chaînes de caractères et tuples de Python sont immuables ; les listes et les dictionnaires sont mutables. Le contenu fixe d’un tuple peut comprendre une référence à une liste mutable : l’immuabilité du conteneur externe ne fige donc pas tous les objets qu’il contient.
Appliquer les mêmes règles aux arguments
Lorsque Python transmet une liste à une fonction, le paramètre fait référence au même objet. Appeler items.append(4) dans la fonction modifie cette liste. L’affectation items = [] associe uniquement le nom du paramètre local à une nouvelle liste. Le nom de l’appelant reste associé à l’objet d’origine.
C transmet les valeurs des arguments par valeur. Pour permettre à une fonction de modifier une variable de l’appelant par son adresse, transmets un pointeur vers cette variable. Dans chaque langage, pose-toi deux questions distinctes pendant le suivi : « À quel objet ce nom fait-il référence ? » et « Cette opération modifie-t-elle cet objet ou associe-t-elle un nom à autre chose ? »
Points clés
- La pile d’appels suit les appels non terminés ; le tas contient les objets alloués dynamiquement.
- Un objet peut survivre à la fonction qui l’a créé.
- Copier une référence partage un objet ; cela ne duplique pas ses données.
- Une mutation modifie un objet, tandis qu’une nouvelle association change l’objet auquel un nom fait référence.
- Le passage d’arguments suit la même distinction entre objets partagés et associations locales.
3.3 Libère la mémoire sans perdre les données encore utilisées
La mémoire allouée doit finir par devenir réutilisable. La difficulté consiste à libérer les données après leur dernière utilisation tout en conservant chaque objet dont le programme a encore besoin. Les langages confient cette responsabilité à la personne qui programme, à un système d’exécution ou à des règles de possession vérifiées par le compilateur.
Gestion manuelle et erreurs possibles
En C, malloc(n) demande n octets et renvoie, en cas de succès, un pointeur vers le bloc. free(p) libère un bloc obtenu par allocation. La personne qui programme doit l’utiliser au bon moment et exactement une fois. C++ prend aussi en charge la gestion manuelle, même si ses outils standards peuvent automatiser la libération grâce aux durées de vie des objets.
Plusieurs erreurs ont des causes différentes :
| Problème | Ce qui s’est mal passé |
|---|---|
| Fuite mémoire | Un espace alloué devenu inutile n’est pas libéré |
| Utilisation après libération | Un pointeur est utilisé après la libération de l’espace vers lequel il pointe |
| Double libération | Le même bloc alloué est libéré deux fois |
| Débordement de tampon | Une écriture dépasse les bornes d’un bloc de mémoire |
Un pointeur qui continue de faire référence à un espace libéré est un pointeur pendant. L’espace peut être réutilisé pour autre chose : y accéder peut donc corrompre des données ou faire planter le programme.
Pour un tableau C int scores[10], les positions valides vont de 0 à 9. Écrire dans scores[10] dépasse les bornes du tableau, et C ne fournit aucune vérification automatique de ces bornes. Python déclenche IndexError lorsque l’indice 10 est utilisé sur une liste de dix éléments, ce qui empêche cet accès d’écraser la mémoire adjacente.
Suivre l’accessibilité ou compter les références
Un ramasse-miettes par traçage part de racines actives, comme les références en cours d’utilisation, suit les liens entre les objets et peut récupérer les objets inaccessibles. Java, C#, Go et JavaScript utilisent des approches par traçage. Le ramassage demande du travail et de la mémoire à l’exécution ; selon le ramasse-miettes, il peut entraîner des pauses.
Le comptage de références suit les références à un objet. Pour une nouvelle liste Python, la séquence simplifiée est la suivante : a = [1, 2] fournit une référence ; b = a en fournit une deuxième ; del a en supprime une ; b = None supprime l’autre. CPython récupère normalement cette liste ordinaire lorsque son compteur atteint zéro.
Un groupe d’objets peut maintenir les compteurs de chacun au-dessus de zéro même lorsqu’aucune partie active du programme ne peut atteindre le groupe. C’est un cycle de références. CPython complète le comptage de références par un ramassage des cycles. Le comptage de références et le traçage sont deux techniques de gestion automatique de la mémoire.
Vérifier la possession avant l’exécution
Rust donne à chaque valeur un propriétaire et vérifie les règles d’emprunt des références. Lorsque la possession prend fin, le code de nettoyage généré libère la valeur. Le Rust sûr rejette les utilisations de références susceptibles de survivre à la validité de leur cible, sans nécessiter de ramasse-miettes par traçage. Swift utilise un comptage automatique de références inséré par le compilateur.
La sûreté mémoire ne concerne pas seulement les plantages. Microsoft a indiqué en 2019 qu’environ 70 % des vulnérabilités auxquelles l’entreprise attribuait chaque année des CVE concernaient la sûreté mémoire ; Chromium a indiqué une proportion comparable de bogues graves en 2020. En décembre 2023, la CISA, la NSA, le FBI et des partenaires internationaux ont encouragé les éditeurs de logiciels à prévoir une transition vers des langages à sûreté mémoire.
La récupération automatique conserve tout de même les objets accessibles. Une liste globale qui ne cesse de grandir peut garder tous les anciens enregistrements en vie même après que l’application a fini de les utiliser. Un ramasse-miettes ne peut pas décider que ces enregistrements accessibles sont devenus inutiles à la tâche ; le programme doit cesser de les conserver.
Points clés
- L’allocation manuelle exige une libération au bon moment.
- Les fuites, l’utilisation après libération, la double libération et le débordement de tampon ont des causes distinctes.
- Le traçage trouve les objets inaccessibles ; le comptage de références réagit à la disparition de références.
- CPython ajoute le ramassage des cycles au comptage de références.
- La possession peut organiser la libération sans ramasse-miettes par traçage.
- Le ramassage automatique ne peut pas récupérer les objets que le programme conserve encore.
4Organise les changements d’état et la coopération entre objets
Une façon de décrire un programme consiste à présenter les commandes qui mettent à jour des valeurs. Une autre regroupe ces valeurs avec les opérations qui les contrôlent. Ces points de vue fonctionnent souvent ensemble : une méthode d’objet peut contenir une boucle ordinaire. Suis d’abord les changements d’état, puis construis un petit modèle de compte pour comprendre comment les classes protègent les règles et comment l’héritage, le polymorphisme et la composition permettent à des objets apparentés de partager une interface sans leur attribuer des responsabilités inadaptées.
4.1 Commandes, procédures et état partagé
Un paradigme de programmation est une façon d’organiser le code et de penser le calcul. En programmation impérative, un programme donne des commandes qui modifient son état : les valeurs actuellement contenues dans les variables et les objets.
Rendre les changements d’état visibles
Un total cumulé est un calcul impératif simple :
total = 0
for n in [3, 8, 5]:
total = total + n
L’état initial est total = 0. L’ajout de 3 le fait passer à 3. L’ajout de 8 le fait passer à 11. L’ajout de 5 le fait passer à 16. Chaque affectation utilise l’état laissé par l’étape précédente : l’ordre fait donc partie de la méthode.
Ce style correspond aux modifications successives que le processeur apporte aux registres et à la mémoire. Il convient aussi aux algorithmes qui mettent volontairement à jour une collection en place, comme une routine de tri qui échange des éléments.
Donner des noms aux tâches
La programmation procédurale organise le code impératif en fonctions ou procédures. Un programme de rapport peut appeler read_scores(), average(scores) et print_report(avg). Ces noms permettent de voir la tâche globale sans suivre immédiatement chaque ligne interne. Chaque procédure peut être comprise et testée séparément.
C, Pascal, Fortran et COBOL sont des langages procéduraux classiques. Python prend aussi en charge ce style : un script peut utiliser des fonctions nommées sans définir de classes.
La programmation structurée organise le déroulement du programme à l’aide de séquences, de structures conditionnelles, de boucles et d’appels de procédures. Elle évite les sauts arbitraires qui rendent difficile la prévision de la prochaine instruction exécutée. La lettre d’Edsger Dijkstra de 1968, « Go To Statement Considered Harmful », a contribué à populariser cette approche. Les sorties contrôlées comme break, continue et return restent utiles : elles permettent de quitter une itération donnée, une boucle ou une fonction.
Les effets peuvent dépasser la valeur de retour
Un effet de bord modifie quelque chose au-delà du résultat renvoyé par une fonction. Afficher du texte, enregistrer un fichier, envoyer un message réseau et modifier des données partagées sont des effets de bord. Les programmes ont besoin de ces actions pour interagir avec le monde.
Des problèmes apparaissent lorsque des parties du code sans lien apparent modifient le même état sans le signaler. Supposons que apply_discount() réduise une variable globale price de 10 % et que add_shipping() ajoute 5. En partant de 100, appliquer d’abord la remise donne 90, puis l’ajout des frais de livraison donne 95. Ajouter d’abord les frais donne 105, puis la remise donne 94,5. Les deux procédures effectuent correctement leurs propres étapes, mais le résultat combiné dépend de leur ordre.
Transmettre des valeurs et renvoyer de nouveaux résultats peut rendre cet ordre visible :
price = add_shipping(apply_discount(100))
L’appel intérieur s’exécute en premier : remise, puis frais de livraison. Une imbrication différente exprime une règle différente. Une circulation claire des données aide à voir si la remise doit aussi s’appliquer aux frais de livraison.
| Organisation | Question principale à la lecture |
|---|---|
| Commandes impératives | Quelle valeur change ensuite ? |
| Procédures nommées | Quelle tâche cet appel effectue-t-il ? |
| Mises à jour globales partagées | Quelle action précédente a modifié cette valeur ? |
Le code impératif reste courant dans les méthodes d’objets et les gestionnaires d’événements modernes. Choisir une conception globale orientée objet ou fonctionnelle n’empêche pas une petite partie d’utiliser des commandes directes lorsque ces étapes rendent l’algorithme clair.
Points clés
- Le code impératif décrit des étapes qui modifient l’état enregistré.
- Les procédures donnent des noms et des limites à des tâches cohérentes.
- Un déroulement structuré rend les chemins d’exécution plus faciles à suivre.
- Les effets de bord comprennent les modifications des données partagées et les interactions avec le monde extérieur.
- Les mises à jour partagées peuvent rendre les résultats dépendants de l’ordre des appels.
4.2 Les classes réunissent les données et leurs règles
La programmation orientée objet modélise un programme sous la forme d’objets qui coopèrent. Chaque objet associe des données aux opérations sur ces données. Un compte peut contenir son titulaire et son solde tout en proposant des opérations de dépôt et de retrait.
Distinguer le modèle de ses instances
Une classe définit les attributs et les méthodes que ses objets fournissent. Un objet, ou une instance, est un élément particulier de cette classe. BankAccount est le modèle ; le compte d’Ana avec 150 et celui de Ben avec 40 sont des instances distinctes. Leurs soldes peuvent changer indépendamment.
Un attribut stocke des données associées à un objet. Une méthode est une opération appelée par l’intermédiaire d’un objet, comme acct.deposit(50). Dans une méthode d’instance Python, self fait référence à l’objet qui reçoit l’appel.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
self._balance = self._balance + amount
L’appel BankAccount("Ana", 100) construit une nouvelle instance. Sa méthode __init__ initialise les données de l’instance. Un constructeur est le processus de préparation d’un nouvel objet ; Python sépare la création de cette étape d’initialisation. L’appel acct.deposit(50) sur ce compte fait passer son solde de 100 à 150.
Placer la règle au point de mise à jour
L’encapsulation réunit les données et les opérations qui garantissent le respect de leurs règles. Supposons qu’un compte ne doive jamais avoir un solde négatif. La méthode de retrait vérifie la modification proposée avant de l’effectuer :
def withdraw(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if amount > self._balance:
raise ValueError("insufficient funds")
self._balance = self._balance - amount
Cette méthode appartient à BankAccount. À partir d’un solde de 150, withdraw(200) déclenche une erreur et laisse 150. withdraw(30) réussit et laisse 120. L’appel qui échoue n’effectue pas d’abord la soustraction pour découvrir ensuite le problème ; la vérification a lieu avant la mise à jour.
Les autres parties du code doivent utiliser ces méthodes au lieu d’affecter un solde sans vérification. Java et C# peuvent limiter l’accès direct avec private. En Python, le trait de soulignement initial dans _balance signale par convention un détail interne ; il n’impose pas la confidentialité.
Le masquage de l’information va plus loin : les appelants dépendent des opérations proposées par une classe sans avoir besoin de connaître sa représentation interne. Le solde pourrait être stocké différemment par la suite, tandis que les opérations publiques de dépôt et de retrait conserveraient leur sens.
Laisser le problème suggérer les responsabilités
Un système de bibliothèque comporte des livres, des adhérents et des prêts. Ces noms suggèrent des classes ou des attributs possibles ; des actions comme emprunter et renouveler suggèrent des méthodes. Choisis une responsabilité claire pour chaque classe. Une classe de compte qui envoie aussi tous les courriels et met en forme tous les rapports devient plus difficile à modifier, car des tâches sans lien sont mélangées.
Les classes et les objets sont apparus dans Simula 67 en 1967 ; Smalltalk a contribué à diffuser cette approche dans les années 1970. Java, C#, C++ et Python prennent en charge la programmation orientée objet, mais l’utilisation de l’un de ces langages n’impose pas d’organiser chaque programme en classes. La question de conception utile est de savoir quelles données et quelles règles vont ensemble.
Points clés
- Une classe définit des attributs et des méthodes ; chaque instance possède ses propres données.
- Les méthodes agissent sur l’objet qui reçoit l’appel.
- L’encapsulation réunit la validation et les changements d’état.
- En Python, les traits de soulignement signalent par convention les attributs internes ; certains langages imposent un accès privé.
- Le masquage de l’information permet aux appelants de dépendre des opérations publiques d’une classe plutôt que de ses détails internes.
- Une classe doit avoir une responsabilité cohérente.
4.3 Spécialise les comportements et combine les objets
Les objets ont souvent besoin de partager des opérations tout en différant dans les détails. L’héritage, le polymorphisme et la composition résolvent différentes parties de ce problème de conception.
Spécialiser un type d’objet existant
L’héritage définit une sous-classe à partir d’une superclasse. En Python, class SavingsAccount(BankAccount): donne à la classe spécialisée accès au comportement hérité du compte. Elle peut ajouter add_interest(rate) : un solde de 1 000 auquel on ajoute 2 % d’intérêts devient 1 020.
La relation recherchée est est un. Un compte d’épargne reste un compte bancaire et doit respecter les règles attendues des comptes bancaires. Réutiliser quelques lignes n’est pas une raison suffisante pour affirmer qu’un type d’objet en est un autre.
Une sous-classe peut redéfinir une méthode avec sa propre implémentation. Supposons qu’un compte courant facture des frais de 1 pour chaque retrait réussi. Avec un solde de 100, retirer 30 coûte 31 et laisse 69. Pour préserver la règle du compte, la méthode doit vérifier le retrait plus les frais avant de modifier le solde. Si ce coût total dépasse le solde, elle refuse toute l’opération.
Faire fonctionner un même appel avec différents objets
Le polymorphisme permet à une même opération de fonctionner sur différents types d’objets. Un cercle et un rectangle peuvent tous deux fournir area() :
for shape in shapes:
print(shape.area())
La boucle n’a pas besoin d’un if pour chaque classe de forme. Chaque objet fournit sa propre implémentation. Le choix de cette implémentation pendant l’exécution est la liaison dynamique.
Pour Circle(2), l’aire est d’environ 12,57 unités carrées. Pour Rectangle(3, 4), elle est de 12 unités carrées. Leur aire totale est d’environ 24,57 unités carrées. Une classe Triangle ajoutée plus tard peut rejoindre la collection en fournissant une opération area() compatible ; la boucle elle-même n’a pas besoin d’une branche supplémentaire.
Une interface indique les opérations qu’un type doit proposer. L’interface Java interface Shape { double area(); } impose aux classes qui l’implémentent de fournir la méthode de calcul de l’aire. Une classe abstraite peut fournir une partie de l’implémentation commune tout en laissant d’autres méthodes aux sous-classes. Python utilise souvent le typage canard : un objet peut fonctionner dans la boucle s’il fournit l’opération requise, même sans superclasse commune.
Contenir un composant au lieu de le devenir
La composition stocke d’autres objets comme composants. Une voiture possède un moteur et des roues. Elle peut appeler la méthode start() de son moteur sans hériter de Engine.
| Relation | Conception adaptée | Raison |
|---|---|---|
| Un compte d’épargne est un compte | Héritage | Il préserve le comportement attendu d’un compte |
| Une voiture possède un moteur | Composition | Le moteur est un composant, pas la catégorie de la voiture |
| Différentes formes proposent le calcul de l’aire | Opération ou interface partagée | L’appelant peut demander l’opération de manière uniforme |
Les longues chaînes d’héritage peuvent propager les effets d’une modification de superclasse à de nombreuses classes. Une sous-classe qui doit désactiver un comportement hérité peut révéler une relation « est un » inadaptée. La composition rend souvent ces dépendances moins importantes et plus claires.
Les quatre piliers habituellement cités de la programmation orientée objet s’articulent ici : l’encapsulation protège les données au moyen d’opérations, l’abstraction masque les détails de l’implémentation, l’héritage spécialise un type et le polymorphisme permet à un appelant d’utiliser une opération partagée entre plusieurs implémentations.
Points clés
- L’héritage modélise une relation « est un » et doit préserver le comportement attendu.
- La redéfinition remplace l’implémentation d’une méthode dans une classe spécialisée.
- Le polymorphisme permet à une opération d’appeler différentes implémentations.
- Une interface indique les opérations requises ; le typage canard s’appuie sur les opérations fournies par un objet.
- La composition modélise des objets qui contiennent d’autres objets comme composants.
5Calcule de nouvelles valeurs et décris les résultats voulus
Un programme peut se concentrer sur les transformations de valeurs ou sur le résultat qu’un moteur doit produire. Les fonctions pures rendent les dépendances explicites, tandis que les données immuables évitent les modifications par l’intermédiaire de références partagées. Les requêtes déclaratives laissent un moteur spécialisé choisir les étapes d’exécution. Suis une chaîne de traitement de données et une requête de base de données pour voir ce que ces approches facilitent et où les entrées, les sorties et les instructions machine concrètes interviennent toujours.
5.1 Fonctions pures et chaînes de traitement des données
La programmation fonctionnelle construit des résultats en combinant des fonctions. Son principe central consiste à produire de nouvelles valeurs à partir des entrées tout en limitant les mutations partagées. Lisp a introduit des idées fonctionnelles largement utilisées vers 1958 ; Haskell, Erlang, Clojure, F# et OCaml développent ces idées de différentes façons.
Savoir ce qui rend une fonction pure
Une fonction pure donne le même résultat pour les mêmes arguments et n’a aucun effet de bord. def with_tax(price): return price * 1.2 illustre cette idée : with_tax(50) donne 60,0. Une fonction qui ajoute aussi un élément à une liste globale ou affiche un message a un effet au-delà de sa valeur de retour. Un résultat qui dépend de l’heure actuelle dépend lui aussi de plus que des arguments.
Un appel pur peut être remplacé par sa valeur de retour sans modifier le comportement du programme. Cette propriété est la transparence référentielle. Elle rend les tests isolés simples et permet à des appels indépendants de s’exécuter en parallèle sans chercher à mettre à jour le même état partagé.
L’immuabilité maintient une valeur existante inchangée. [n + 1 for n in nums] crée une nouvelle liste et laisse nums intacte. Un programme peut toujours représenter des informations qui changent en produisant de nouvelles versions ; il n’a pas besoin d’écraser l’ancienne version.
Transmettre un comportement comme une donnée
Les fonctions sont de première classe lorsqu’elles peuvent être affectées à des noms, transmises comme arguments et renvoyées comme valeurs. Une fonction d’ordre supérieur reçoit une fonction ou en renvoie une. Une expression lambda est une expression de fonction sans nom, comme lambda n: n * n.
Trois opérations d’ordre supérieur courantes forment une chaîne de traitement :
filterconserve les éléments qui satisfont un test.maptransforme chaque élément restant.reducecombine les éléments en un seul résultat à l’aide d’un accumulateur.
Pour calculer la somme des carrés des nombres pairs de 1 à 6, le filtrage laisse 2, 4 et 6 ; leur élévation au carré donne 4, 16 et 36 ; leur somme donne 56.
sum(map(lambda n: n * n,
filter(lambda n: n % 2 == 0, range(1, 7))))
Ici, % 2 == 0 teste si un nombre est pair, et range(1, 7) fournit les nombres de 1 à 6. Haskell exprime le même calcul par sum (map (^2) (filter even [1..6])).
Avec reduce importé de functools en Python, reduce(lambda acc, n: acc + n, [3, 8, 5], 0) combine 0 et 3 pour obtenir 3, puis ce résultat et 8 pour obtenir 11, puis ce résultat et 5 pour obtenir 16. La chaîne décrit des transformations sans boucle autour qui mettrait à jour un total partagé.
Conserver un environnement ou appeler la fonction sur un problème plus petit
def multiplier(k): return lambda x: k * x renvoie une fonction qui conserve l’accès à k. Ainsi, multiplier(3)(10) renvoie 30 même après la fin de l’appel extérieur. La fonction renvoyée et l’environnement conservé forment une fermeture lexicale.
La récursivité consiste pour une fonction à s’appeler elle-même sur un problème plus petit. La factorielle renvoie 1 lorsque n == 0 ; sinon, elle renvoie n * factorial(n - 1). Pour 5, les produits donnent 120. Un calcul fini nécessite un cas de base accessible. En Python, les appels imbriqués non résolus finissent par déclencher RecursionError ; les langages qui prennent correctement en charge les appels terminaux peuvent réutiliser l’espace de pile pour les appels en position finale.
Utiliser des fonctions ne suffit pas à rendre le code fonctionnel : les fonctions procédurales peuvent modifier l’état. Les programmes fonctionnels réels lisent toujours des entrées et produisent des sorties, en gardant généralement les effets près des frontières du programme et les calculs centraux purs.
Points clés
- Les fonctions pures dépendent uniquement de leurs arguments et n’ont aucun effet de bord.
- Les mises à jour immuables créent de nouvelles valeurs.
- Les fonctions de première classe peuvent être transmises et renvoyées ; les fonctions d’ordre supérieur reçoivent ou renvoient des fonctions.
- Le filtre sélectionne, la transformation modifie chaque élément et la réduction combine les éléments.
- Une fermeture lexicale conserve son environnement de création.
- Un calcul récursif fini nécessite un cas de base qu’il atteint effectivement.
5.2 Énonce un résultat et laisse un moteur le trouver
La programmation déclarative décrit le résultat voulu tout en laissant les étapes d’exécution à un moteur de langage. Le code fonctionnel est généralement regroupé sous cette idée générale, aux côtés des langages de requête et de la programmation logique.
Lire une requête comme une demande
Une base de données relationnelle stocke les données dans des tables. Supposons que students contienne les lignes de noms et de notes Ana 92, Ben 75, Chen 80 et Dara 64 :
SELECT name
FROM students
WHERE grade >= 80
ORDER BY name;
FROM indique la table, WHERE donne la condition, SELECT choisit le champ renvoyé et ORDER BY demande l’ordre alphabétique. Ana et Chen satisfont la condition, y compris avec la note de Chen, exactement égale à 80. Le résultat contient leurs noms dans cet ordre.
Un programme impératif créerait une liste de résultats, parcourrait les lignes, testerait chaque note, ajouterait les noms répondant au critère et trierait la liste. SQL indique à la base de données le résultat attendu sans écrire cette boucle.
Un optimiseur de requêtes choisit un plan d’exécution. Il peut parcourir la table ou utiliser un index disponible, une structure de recherche qui peut accélérer les recherches adaptées. De nombreux index utilisent des structures triées ; d’autres utilisent des organisations différentes. L’ajout d’un index peut modifier le plan sans modifier la requête. Davantage de données implique toujours du travail : déclarer le résultat ne garantit pas que chaque requête soit peu coûteuse.
Demander ce qui découle de faits et de règles
La programmation logique décrit des faits et des relations, puis recherche les valeurs qui répondent à une question. En Prolog :
parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
La règle signifie que X est un grand-parent de Z lorsqu’il existe un Y pour lequel les deux relations de parenté sont vérifiées. La requête ?- grandparent(tom, Who). trouve Who = ann : Tom est le parent de Bob et Bob est le parent d’Ann. Les noms commençant par une majuscule représentent des variables ; les noms en minuscules désignent des personnes particulières.
Reconnaître les outils déclaratifs autour de toi
Une formule de tableur =SUM(B2:B5) énonce une dépendance. Le tableur la recalcule lorsque les cellules concernées changent. L’expression régulière [0-9]{3}-[0-9]{4} décrit trois chiffres, un trait d’union et quatre chiffres, comme dans « 555-0199 ». Un moteur de recherche de correspondances textuelles effectue la recherche.
CSS définit l’apparence avec une règle comme h1 { color: navy; } ; HTML balise la structure d’une page. Ce sont des notations déclaratives, généralement classées comme langages de style et de balisage. Leur rôle diffère de celui d’un langage généraliste qui peut exprimer une grande variété de calculs.
| Type de langage | Tâche visée | Exemples |
|---|---|---|
| Langage dédié | Un type particulier de travail | SQL, expressions régulières, CSS |
| Langage généraliste | De nombreux types de programmes | Python, Java, Haskell |
Le code déclaratif peut rester proche de l’énoncé d’un problème et laisser un moteur améliorer sa méthode. Il donne aussi à la personne qui programme moins de contrôle direct sur chaque étape, ce qui peut rendre un plan lent plus difficile à diagnostiquer. Des instructions concrètes s’exécutent toujours en dessous : « déclaratif » décrit la façon dont la personne exprime la tâche, et non un processeur qui fonctionne sans étapes.
Points clés
- Le code déclaratif énonce un résultat voulu ; un moteur choisit les étapes d’exécution.
- SQL sélectionne des lignes et des champs à l’aide de conditions et d’un ordre demandé.
- Un optimiseur peut modifier un plan de requête sans modifier la requête.
- La programmation logique déduit des réponses à partir de faits et de règles.
- Les langages dédiés visent des tâches particulières ; les langages généralistes prennent en charge de nombreux types de programmes.
6Choisis un style et un langage adaptés à la tâche
Différents styles de programmation peuvent produire la même réponse tout en organisant le travail différemment. Les comparer sur une même tâche rend leurs compromis plus faciles à voir. Le choix d’un langage ajoute ensuite des contraintes pratiques : la plateforme cible, les bibliothèques disponibles, les limites de mémoire et de vitesse, les besoins de sûreté et les personnes qui maintiennent le code. Un projet composé de plusieurs parties peut utiliser pour chacune un langage différent et un mélange de styles différent.
6.1 Une tâche dans plusieurs styles de programmation
Un paradigme change la façon d’exprimer une solution, pas la réponse numérique qu’une solution correcte doit produire. Considère des commandes de montants [120, 45, 300, 80] : trouve le total des montants strictement supérieurs à 100.
Suivre les changements d’état
La version impérative rend visibles la boucle et l’accumulateur :
total = 0
for amount in amounts:
if amount > 100:
total = total + amount
Le premier montant fait passer total à 120. Le deuxième le laisse inchangé. Le troisième le fait passer à 420, et le quatrième le laisse à cette valeur. Le programme rend visibles l’ordre du test et celui de la mise à jour.
Transformer une collection
Une expression Python compacte de style fonctionnel est sum(a for a in amounts if a > 100). Elle sélectionne 120 et 300 et les combine pour obtenir 420. Une autre version est sum(filter(lambda a: a > 100, amounts)). Ici, le prédicat décrit quels éléments sont retenus et sum combine les valeurs sélectionnées.
Une expression génératrice ou un appel à filter utilise les fonctionnalités multiparadigmes de Python ; sa présence ne rend pas pures toutes les parties de l’application qui l’entourent.
Associer les règles aux objets
Dans une conception orientée objet, un objet Order stocke un amount et propose is_large(), qui renvoie self.amount > 100. Un objet OrderBook contient des objets représentant les commandes et propose large_total(). Cette méthode peut parcourir les commandes, demander à chacune si elle satisfait le critère et additionner les montants correspondants.
Le total reste 420. La différence tient à la répartition des responsabilités : la règle définissant une commande importante appartient à Order, et le travail portant sur la collection appartient à OrderBook. D’autres fonctionnalités peuvent réutiliser la règle sans copier son test de seuil.
Interroger la base de données
Si les données se trouvent déjà dans une base de données relationnelle, SQL peut demander le total :
SELECT SUM(amount)
FROM orders
WHERE amount > 100;
La base de données choisit le plan de récupération et d’agrégation. La requête ne demande pas à un appelant de parcourir chaque ligne ou de conserver un accumulateur local.
| Style | Ce que l’exemple met en avant |
|---|---|
| Impératif | Tests et mises à jour ordonnés d’un total |
| Orienté objet | Objets représentant les commandes et la collection, avec des responsabilités définies |
| Fonctionnel | Sélection et combinaison de valeurs |
| SQL déclaratif | Résultat requis à partir des données stockées |
Combiner les styles lorsqu’ils sont utiles
Python prend en charge les techniques procédurales, orientées objet et fonctionnelles. JavaScript fournit des objets, des fonctions de première classe et des fermetures lexicales. C++, C#, Kotlin, Scala, Swift et Rust mélangent aussi les styles. Java est fortement associé à la programmation orientée objet, mais prend en charge les expressions lambda depuis Java 8 en 2014 ; Haskell est purement fonctionnel.
Une application web peut utiliser des classes pour les utilisateurs et les commandes, des transformations fonctionnelles pour les données des rapports, des mises à jour impératives dans les méthodes et SQL pour les requêtes de base de données. Ces choix interviennent à différentes couches.
La programmation événementielle ajoute une autre idée d’organisation : enregistrer un gestionnaire qui s’exécute lorsqu’un événement arrive. En JavaScript, button.addEventListener("click", showMenu) transmet showMenu comme une valeur de fonction. Un clic ultérieur l’appelle. Le gestionnaire peut lui-même utiliser des objets, des transformations pures ou des étapes impératives. Son rôle événementiel indique quand il s’exécute, pas le seul style que son corps peut utiliser.
Points clés
- Des solutions correctes selon différents paradigmes peuvent calculer le même résultat.
- Un style modifie l’endroit où apparaissent les étapes, l’état, les règles et les responsabilités.
- Les langages modernes prennent généralement en charge plusieurs paradigmes.
- Un gestionnaire d’événement s’exécute en réponse à un événement et peut utiliser d’autres styles en interne.
6.2 Choisis en fonction des contraintes et des compromis
Aucun langage de programmation ne convient mieux que tous les autres à chaque projet. Les langages généralistes sont généralement Turing-complets : avec un temps et une mémoire sans limites, ils peuvent exprimer la même classe de calculs. Dans la pratique, le choix dépend de l’adéquation de la solution à la plateforme, aux ressources, aux outils et aux personnes concernés.
Commencer par ce qui doit fonctionner
La plateforme est souvent une contrainte incontournable. Les navigateurs exécutent JavaScript et peuvent exécuter des modules compilés grâce à WebAssembly. TypeScript ajoute une vérification statique et se compile en JavaScript. Swift est un choix courant pour les applications des plateformes Apple ; Kotlin est recommandé pour Android. SQL est le langage habituel des requêtes relationnelles.
Considère ensuite le domaine et l’écosystème. Python possède des bibliothèques de données et d’apprentissage automatique largement utilisées, comme NumPy et pandas. R se concentre sur les statistiques. C et C++ sont courants dans les systèmes d’exploitation, les moteurs de jeux et le code embarqué. Java et C# sont courants dans les grands systèmes d’entreprise ; Bash est utile pour automatiser les tâches du système d’exploitation.
Ce sont des associations pratiques, pas des domaines réservés. Une bibliothèque adaptée peut économiser bien plus de travail de développement qu’une petite différence de syntaxe entre langages.
Examiner les besoins en ressources et en sûreté
Un microcontrôleur doté de quelques kilo-octets de mémoire vive seulement peut nécessiter un contrôle précis de la mémoire et du surcoût à l’exécution. C, C++ ou Rust peuvent convenir lorsque la cible les prend en charge. Les composants exigeants des jeux ou des bases de données favorisent souvent ces langages eux aussi, mais les moteurs et les budgets de ressources diffèrent : Unity utilise C#, et certaines bases de données utilisent Java ou Go.
Avant de choisir en fonction de la vitesse, trouve le goulot d’étranglement : la partie qui limite les performances. Un programme qui attend principalement une réponse du réseau ne deviendra pas beaucoup plus rapide simplement parce qu’une petite boucle locale est compilée plus efficacement. Mesure un travail représentatif sur la plateforme cible.
La sûreté mémoire réduit les erreurs comme les accès mémoire invalides. Le Rust sûr vérifie les règles de possession à la compilation ; les langages à mémoire gérée comme Java, C#, Go et Python assurent la gestion de la mémoire à l’exécution. Le typage statique détecte de nombreuses opérations incompatibles avant l’exécution, ce qui peut aider dans une grande base de code. Ni la sûreté mémoire ni le typage statique ne prouvent que la logique du programme est correcte.
Inclure le coût de maintenance du résultat
Les connaissances de l’équipe, les personnes disponibles pour le développement, les débogueurs, les outils de test, les bibliothèques, les licences et le code existant influent tous sur la décision. Un langage connu de l’équipe et pris en charge peut réduire les coûts de livraison et de maintenance. Réécrire un système fonctionnel entraîne du travail de migration et des risques, même si le nouveau langage possède des fonctionnalités attrayantes.
Pour un réseau météorologique urbain, les différents composants ont des besoins différents :
| Composant | Choix de langage raisonnable | Raison |
|---|---|---|
| Micrologiciel de petits capteurs | C ou Rust | Contrôle de la mémoire et matériel cible pris en charge |
| Relevés stockés | SQL | Filtrage et agrégation dans la base de données |
| Nettoyage et analyse | Python | Bibliothèques de traitement des données |
| Affichage interactif dans le navigateur | JavaScript ou TypeScript | Prise en charge par le navigateur et outils d’interface |
Une justification utile relie chaque choix à un besoin : « L’équipe maintient déjà le service Java et ses bibliothèques prennent en charge la nouvelle fonctionnalité. » Un rang dans un classement de popularité ne peut pas fournir ce raisonnement à lui seul. Les enquêtes peuvent indiquer l’existence d’une grande communauté ou d’un vaste vivier de recrutement ; elles n’établissent pas quel langage convient le mieux à une charge de travail particulière.
Points clés
- Vérifie les contraintes de la plateforme avant de considérer les préférences.
- Les bibliothèques et les outils relient un langage au domaine du problème.
- Mesure le goulot d’étranglement avant de choisir principalement en fonction de la vitesse.
- La sûreté mémoire et la vérification statique réduisent certains défauts, mais la logique doit toujours être testée.
- Les compétences de l’équipe, le code existant et les coûts de maintenance entrent dans la décision.
- Des composants différents peuvent justifier des langages différents.
Langages et paradigmes de programmation : exemples
1Suis un programme du code source à l’exécution
13 étapes
- 1IntroductionUn calcul traverse plusieurs couches
Tu écris un calcul, mais un processeur a besoin de petites instructions codées appelées code machine. Un traducteur fournit les détails manquants.
Suis
x = a + bdepuis le code source vers les niveaux inférieurs, puis suis le programme lors de sa construction et de son exécution. Distingue deux questions : sous quelle forme se trouve le code et quel logiciel l’exécute ? Un compilateur, un interpréteur et un compilateur à la volée répondent différemment à la seconde. Cette distinction expliquera la portabilité et le moment où les erreurs apparaissent. - 2ExempleUn calcul devient plusieurs actions
Pour
x = a + b, imagine quatre actions de niveau inférieur :- Charger la valeur de
adans un registre. - Charger la valeur de
bdans un autre registre. - Additionner les valeurs des deux registres.
- Stocker le résultat pour
x.
L’instruction de haut niveau masque le choix des registres et les détails du stockage. Les instructions réellement générées peuvent différer, car un compilateur peut optimiser le travail. Le calcul requis reste le même.
- Charger la valeur de
- 3ExplicationDes instructions lisibles ciblent toujours un processeur
Le langage d’assemblage utilise des noms d’instructions comme
movetadd. Un assembleur les code en instructions machine.Pour x86-64,
mov eax, 1place 1 dans EAX ;add eax, ebxy ajoute EBX. Ces registres et ces encodages appartiennent à cette architecture du jeu d’instructions. Remplacer les octets par des noms lisibles aide les humains, mais ne transforme pas les instructions en une description portable de haut niveau. - 4DéfiTransfère le programme vers un autre processeur
Une équipe possède un code source C portable et un exécutable natif x86-64. Elle a besoin d’un exécutable natif ARM. Quel élément peut-elle utiliser comme point de départ et quel travail est nécessaire ?
---reveal---
Utilise le code source et compile-le pour la cible ARM, avec la prise en charge nécessaire de la plateforme. L’exécutable x86-64 contient des instructions destinées à une autre architecture du jeu d’instructions. La portabilité du code source permet une nouvelle traduction ; elle ne transforme pas les instructions machine existantes en code ARM natif.
- 5ExempleIdentifie les unités lexicales avant la structure
Considère
total = price * 3;en C.L’analyse lexicale trouve six unités lexicales :
total,=,price,*,3et;. L’analyse syntaxique regroupe ensuiteprice * 3comme l’expression dont le résultat est affecté àtotal.Ce sont des tâches distinctes. L’analyse lexicale identifie les éléments ; l’analyse syntaxique établit comment ils s’assemblent. L’analyse sémantique peut ensuite vérifier, par exemple, si les noms sont déclarés et si les types des opérandes sont compatibles.
- 6ExempleDéplace le calcul des constantes dans la construction
L’expression source
60 * 60 * 24utilise uniquement des constantes. Le repliement des constantes peut l’évaluer avant l’exécution : d’abord 3 600, puis 86 400.La génération de code peut utiliser ce résultat au lieu d’instructions qui répètent les multiplications. C’est une optimisation, car elle change la façon dont le travail est effectué tout en préservant le résultat requis. Remplacer une valeur par une approximation simplement pratique ne serait pas justifié par cette règle.
- 7ExplicationRelie les fichiers traduits aux routines des bibliothèques
La compilation peut produire des fichiers objets distincts. Un éditeur de liens relie ces fichiers et résout les références aux opérations des bibliothèques.
L’édition de liens statique inclut le code des bibliothèques dans l’exécutable construit. L’édition de liens dynamique repose sur une bibliothèque partagée compatible chargée plus tard. Aucune des deux approches ne nécessite la présence du compilateur anticipé sur la machine de l’utilisateur, mais un exécutable lié dynamiquement a toujours besoin de ses dépendances à l’exécution.
- 8DéfiUne construction réussie donne une réponse incorrecte
Un programme C est construit avec succès. Son rapport doit soustraire une quantité utilisée d’une quantité initiale, mais le code les additionne. Le compilateur a-t-il omis de vérifier une règle du langage ?
---reveal---
Non. Une addition peut avoir une syntaxe valide et des types compatibles tout en exprimant une tâche incorrecte. C’est une erreur de logique. Le compilateur vérifie les règles du langage et génère du code ; un test dont le résultat attendu est connu est nécessaire pour révéler l’écart entre le calcul et son objectif.
- 9ExplicationUn environnement d’exécution peut exécuter des instructions intermédiaires
CPython compile un fichier Python valide en bytecode, puis exécute ce bytecode dans sa machine virtuelle. Le processeur exécute les instructions machine de l’environnement d’exécution pendant que celui-ci réalise les opérations Python.
Java utilise aussi du bytecode :
javacproduit des fichiers de classe pour une machine virtuelle Java compatible. Le bytecode appartient à un modèle d’instructions d’un environnement d’exécution ; il ne faut donc pas le confondre avec un jeu d’instructions universel pour processeur. - 10ExempleCompare deux moments où l’exécution peut s’arrêter
Dans ce fichier Python valide, la première ligne s’exécute :
print("start") x = 10 print(x + "5")Elle affiche « start » avant que la dernière ligne ne déclenche
TypeError. Supprime maintenant la parenthèse fermante finale : CPython ne peut pas compiler le fichier, donc aucune instruction de ce fichier ne s’exécute. Une erreur de syntaxe pendant la préparation et une erreur de type pendant une opération exécutée arrêtent le programme à des étapes différentes. - 11ExplicationCompile le code très utilisé pendant l’exécution
Un compilateur à la volée produit du code machine pendant l’exécution du programme. Un environnement d’exécution peut d’abord interpréter le code, observer les méthodes fréquemment utilisées et compiler ces méthodes.
Cela déplace les coûts : la compilation utilise du temps et de la mémoire pendant l’exécution, tandis que les exécutions répétées peuvent devenir plus rapides ensuite. Un service de longue durée peut bénéficier davantage de cette mise en régime qu’un petit script exécuté une seule fois. Le résultat dépend de l’implémentation et de la charge de travail.
- 12DéfiDistingue la portabilité du bytecode de la vitesse du code natif
Un fichier de classe Java s’exécute au moyen d’une machine virtuelle Java compatible sur deux architectures de jeux d’instructions différentes. À chaque exécution, l’environnement compile une méthode fréquemment utilisée. Pourquoi un même fichier de classe peut-il être partagé alors que le code machine généré diffère ?
---reveal---
Le fichier de classe contient du bytecode de machine virtuelle Java, que les deux environnements compatibles comprennent. Chaque compilateur à la volée cible son propre processeur hôte. La portabilité vient du format intermédiaire partagé et des environnements compatibles ; les instructions machine produites pendant l’exécution restent propres à chaque plateforme.
- 13RécapitulatifDistingue les niveaux et les modes d’exécution
Le code machine cible une architecture du jeu d’instructions ; le langage d’assemblage donne des noms lisibles à ses instructions ; le code source de haut niveau masque davantage de détails du matériel.
Un compilateur traduit le code, un interpréteur l’exécute et un compilateur à la volée compile pendant l’exécution. Un environnement d’exécution peut combiner ces méthodes. Pour expliquer un système réel, identifie ses formes source, intermédiaire et native, puis indique quelle étape vérifie la syntaxe et quelle étape exécute l’opération susceptible d’échouer.
2Suis le sens d’un court programme
10 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionLa forme et le sens répondent à des questions différentes
- 2ExempleUn code valide peut regrouper le mauvais calcul
- 3ExplicationLa première condition peut protéger la seconde
- 4DéfiPrévois si la division s’exécute
- 5ExplicationUne affectation lit avant de mettre à jour
- 6ExempleSuis chaque mise à jour de l’accumulateur
- 7ExempleSuis un appel et sa valeur de retour
- 8DéfiUne mise à jour locale peut laisser l’appelant inchangé
- 9DéfiSuis ensemble la grammaire et l’état
- 10RécapitulatifLis le chemin exécuté
3Suis les types et les objets partagés
14 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionSuis les valeurs, les objets et leur stockage
- 2ExplicationDes valeurs typées peuvent être associées à différents noms
- 3Point cléL’inférence et la conversion répondent à des questions différentes
- 4DéfiLis les chiffres comme la valeur réellement renvoyée
- 5ExplicationLa représentation influe sur les résultats numériques
- 6ExplicationUn bloc d’activation peut disparaître avant un objet
- 7ExempleDeux noms pointent vers une seule liste
- 8DéfiUn paramètre peut modifier un objet puis désigner autre chose
- 9ExplicationIdentifie précisément comment l’espace mémoire est mal utilisé
- 10ExempleSuis les références jusqu’à la disparition de la dernière
- 11Point cléUn cycle peut ne plus avoir de point d’entrée actif
- 12ExplicationLa possession vérifie les accès avant l’exécution
- 13DéfiTrouve pourquoi le ramassage ne peut pas récupérer une liste
- 14RécapitulatifSuis trois décisions distinctes
4Protège les règles et choisis les relations entre objets
12 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionLes étapes ont besoin de frontières fiables
- 2ExempleL’ordre des opérations modifie l’état partagé
- 3ExplicationRends utile la délimitation d’une procédure
- 4DéfiPrévois une mise à jour dans un ordre différent
- 5ExplicationDonne à chaque compte son propre état
- 6ExempleVérifie avant de modifier un solde
- 7DéfiRepère une règle contournée
- 8ExempleSpécialise sans rompre le contrat
- 9ExplicationLaisse l’objet choisir sa méthode de calcul d’aire
- 10LienContiens le composant qui effectue le travail
- 11DéfiChoisis deux relations dans une nouvelle conception
- 12RécapitulatifFais correspondre les frontières aux responsabilités
5Transforme une collection et interroge un résultat
13 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionÉnonce une transformation ou une demande
- 2ExplicationLa pureté décrit plus que le nombre renvoyé
- 3ExempleSélectionne d’abord les valeurs paires
- 4ExempleTransforme et combine les valeurs sélectionnées
- 5DéfiApplique la chaîne de traitement à un nouveau cas
- 6ExplicationRenvoie un comportement qui conserve son facteur
- 7ExempleRéduis le problème jusqu’à atteindre un cas direct
- 8ExempleLis SQL comme une demande portant sur des lignes
- 9Point cléConserve le résultat en changeant le plan
- 10ExempleRelie deux faits par une règle
- 11DéfiLis une nouvelle requête en tenant compte de sa valeur limite
- 12DéfiCombine une requête et une transformation pure
- 13RécapitulatifGarde visible la relation entre l’entrée et le résultat
6Compare les styles et justifie un choix de langage
10 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionGarde la même tâche
- 2ExempleGarde la même tâche en changeant de style
- 3ExemplePlace la même règle dans des objets ou une requête
- 4DéfiDétermine si une conception utilise un seul style
- 5ExplicationÉcarte d’abord les cibles non prises en charge
- 6ExempleChoisis pour un système météorologique complet
- 7ConseilMesure ce qui prend du temps
- 8DéfiDéfends un choix face à des contraintes concurrentes
- 9DéfiRelie le choix du langage à l’organisation du programme
- 10RécapitulatifDonne une raison liée au besoin
Langages et paradigmes de programmation : QCM
Série 1 : Lis du code et identifie le mode d’exécution10 Questions Pratiques
Question 1 sur 10
mov et jmp. À quel niveau de langage le programme est-il maintenant écrit ?Afficher la réponseMasquer la réponse
Question 2 sur 10
Afficher la réponseMasquer la réponse
Question 3 sur 10
Afficher la réponseMasquer la réponse
Question 4 sur 10
Afficher la réponseMasquer la réponse
Question 5 sur 10
Quelle valeur finale ce code Python stocke-t-il dans total ?
total = 1
for n in [4, 7, 2]:
total = total + n
Afficher la réponseMasquer la réponse
Question 6 sur 10
label = "18" est suivi de label = 18. Le nom peut changer de type, mais "18" + 2 déclenche une erreur. Comment décrit-on ces deux faits ?Afficher la réponseMasquer la réponse
Question 7 sur 10
Que contient first après l’exécution de ce code Python ?
first = [4, 8]
second = first
second.append(6)
Afficher la réponseMasquer la réponse
second modifie cette liste partagée, donc first montre aussi le 6 ajouté. Conserver [4, 8] nécessiterait une liste distincte, par exemple créée par une copie avant l’ajout.Question 8 sur 10
read_data(), calculate_total() et save_report(), chacun contenant des commandes ordonnées. Quelle organisation cela illustre-t-il ?Afficher la réponseMasquer la réponse
Question 9 sur 10
n == 0 ; sinon, elle s’appelle elle-même avec n + 1. En partant de la valeur positive 2, pourquoi le cas de base écrit ne permet-il pas au calcul de se terminer ?Afficher la réponseMasquer la réponse
Question 10 sur 10
Afficher la réponseMasquer la réponse
Série 2 : Applique les règles des types, des objets et des requêtes10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
a = 8 et b = 12 en Python, quelles valeurs a + b / 2 et (a + b) / 2 produisent-elles respectivement ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
balance et une méthode deposit. Deux comptes sont créés avec des soldes initiaux différents. Où ces soldes distincts se trouvent-ils ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
[1, 4, 7, 10], une chaîne de traitement conserve les valeurs supérieures à 3, soustrait 1 à chaque valeur retenue, puis les additionne. Quel est le résultat ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Que valent x et y après ce code Python ?
x = [12]
y = x
x = x + [3]
y.append(6)
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 3 : Suis les appels, les effets et les contrats des objets10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
minutes = 60 * 12 par la valeur précalculée 720. Quelle optimisation est utilisée et que doit-elle préserver ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Qu’affiche ce programme Python ?
score = 11
def change():
score = 4
return score + 2
print(change())
print(score)
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Que vaut items après l’appel ?
items = [3]
def revise(values):
values.append(8)
values = [1]
values.append(5)
revise(items)
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
showDetails pour un événement de clic. Pourquoi ce même gestionnaire peut-il toujours utiliser des méthodes d’objets et des affectations impératives ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 4 : Raisonne à travers les frontières de l’exécution et de la conception10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
n = 0 en Python, compare n != 0 and 36 / n > 4 et 36 / n > 4 and n != 0. Que se passe-t-il ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Quelle valeur finale est stockée dans total ?
def bump(x):
return x + 2
total = 0
for n in [2, 5, 8]:
if n > 3:
total = total + bump(n)
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
item.render(). Une nouvelle classe sans lien avec les autres fournit une méthode render() compatible. L’appelant Python n’exige aucune superclasse. Pourquoi l’objet peut-il rejoindre la boucle sans branche propre à son type ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Série 5 : Diagnostique les limites et combine les idées10 Questions Pratiques
Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.
Question 1 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Qu’affiche ce programme Python ?
value = 6
def step(value):
value = value + 3
return value
for n in [1, 2]:
result = step(value)
print(value, result)
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
double() la multiplie par 2 ; add_three() ajoute 3. En partant de la même valeur, quelle est toujours la différence entre doubler puis ajouter et ajouter puis doubler ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
readings contient les lignes (level, active) : (50, true), (65, true), (20, false), (85, true) et (90, false). Une requête demande SUM(level) lorsque active = true AND level > 50. Après l’ajout d’un index adapté, quel total est requis et le plan d’exécution peut-il changer ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
parent(lea, noah). et parent(noah, sora)., ainsi que la règle grandparent(X, Z) :- parent(X, Y), parent(Y, Z).. Que trouve grandparent(lea, Who) ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
[50, 65, 20, 85]. Quels doivent être leurs résultats corrects ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Tu bloques sur ton propre exercice ? Aide aux Devoirs
Mots-Clés
A
Abstraction
Affectation
count = count + 1 lit l’ancien compteur avant de le remplacer par la valeur augmentée.Aliasing
b = a, l’aliasing explique pourquoi un ajout par b peut aussi modifier ce que l’on voit par a.Allocation mémoire
Analyse lexicale
price * 3 est séparé en un nom, un opérateur de multiplication et un nombre.Analyse syntaxique
total = price * 3; fait partie de l’affectation.Architecture du jeu d’instructions
Assembleur
add eax, ebx en octets d’instruction x86-64 correspondants.Attribut
B
Bloc d’activation
Bytecode
C
Cas de base
Classe
Loan d’une bibliothèque peut définir l’adhérent, le livre, la date de retour et l’opération de renouvellement pour chaque prêt.Code machine
Compilateur
Compilation à la volée
Compilation anticipée
Composition
Comptage de références
Conversion implicite
D
Débordement de tampon
Dépassement de capacité
Double libération
free sur une allocation déjà libérée peut provoquer une double libération et un comportement imprévisible.É
Écosystème
Éditeur de liens
Évaluation en court-circuit
and.E
Effet de bord
Encapsulation
Erreur d’exécution
Erreur de logique
a + b / 2 pour calculer la moyenne de deux valeurs est une erreur de logique, car la division a lieu avant l’addition.Expression
width * height fournit la valeur renvoyée par une fonction qui calcule l’aire d’un rectangle.Expression lambda
lambda n: n * n fournit une opération de mise au carré à une chaîne de transformation.Expression régulière
[0-9]{3}-[0-9]{4} correspond à un groupe de trois chiffres suivi d’un trait d’union et de quatre chiffres.F
Fermeture lexicale
Fonction d’ordre supérieur
filter reçoit un prédicat qui détermine les valeurs d’entrée à conserver.Fonction de première classe
map comme opération à appliquer à chaque élément d’une collection.Fonction de rappel
showMenu comme fonction de rappel permet à un système de bouton d’appeler cette fonction lorsque l’utilisateur clique.Fonction pure
Fuite mémoire
G
Gestionnaire d’événement
Goulot d’étranglement
H
Héritage
I
Immuabilité
Implémentation
Inférence de types
Instruction
total = price * 3 affecte le résultat d’une multiplication à une variable.Interface
area() tout en laissant chaque forme la calculer différemment.Interpréteur
L
Langage d’assemblage
Langage de haut niveau
Langage dédié
Langage multiparadigme
Liaison dynamique
shape.area() vers l’implémentation du rectangle lorsque l’objet qui reçoit l’appel est un rectangle.M
Machine virtuelle
Masquage de l’information
Méthode
Mutation
N
Nombre à virgule flottante
0.1 + 0.2 et la valeur stockée de 0.3.O
Objet
Optimiseur de requêtes
P
Paradigme de programmation
Paramètre
width reçoit 3 lorsque l’appelant évalue area(3, 4).Pile d’appels
area ajoute une entrée à la pile d’appels et la retire lorsque son résultat revient à l’appelant.Plateforme
Pointeur pendant
Polymorphisme
area() sur des cercles et des rectangles sans branche distincte pour chacun.Portée
Possession
Programmation déclarative
Programmation événementielle
Programmation fonctionnelle
Programmation impérative
Programmation logique
Programmation procédurale
Programmation structurée
R
Ramassage des miettes
Récursivité
Redéfinition de méthode
Référence
REPL
Repliement des constantes
60 * 60 * 24 par 86 400 avant le démarrage du programme.S
Sémantique
SQL
Sûreté mémoire
Syntaxe
if et une indentation pour le bloc contrôlé.T
Tableau de suivi d’exécution
Tas
Turing-complétude
Typage canard
area() compatible.Typage dynamique
Typage statique
Type de données
U
Unité lexicale
Utilisation après libération
- 1Pensée informatique
- 2Binaire et représentation des données
- 3Fonctionnement des ordinateurs
- 4Algorithmes de tri
- 5Langages et paradigmes de programmation
- 6Fonctionnement d’Internet
- 7Informatique, éthique et société
À propos de cette unité
Crée ton propre Matériel d'Étude
Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.