tp en python sur affectation et grammaire des fonctions

Affectation

Pendant l’exécution d’un programme, les données que ce dernier manipule sont stockées en mémoire centrale. En nommant ces données, on peut les manipuler beaucoup plus facilement. Les variables nous permettent donc de manipuler ces données sans avoir à se préoccuper de l’adresse explicite qu’elles occuperont effectivement en mémoire. Pour cela, il suffit de leur choisir un nom ou identificateur.
L’interpréteur est un programme qui traduit les lignes de code en langage Python en un langage directement compréhensible par l'ordinateur : le langage machine. L'interpréteur s’occupe d’attribuer une adresse à chaque variable et de superviser tous les éventuels futurs changements d’adresse.

Une variable peut-être assimilée à une boîte aux lettres portant un nom.

Par exemple, on veut stocker le nombre 5 dans une variable notée x. Nous avons noté en pseudo-code ceci :

x ← 5

Ceci signifie : « à l’adresse mémoire référencée par x - autrement dit, dans la boîte aux lettres dénommée x -, se trouve la valeur 5 ». Nous utilisons plus fréquemment l’abus de langage « la variable x vaut 5 ».

illustration plus réaliste du concept de variable

En langage Python, l'affectation sera notée à l'aide du symbole égal : =. On note ainsi l'affectation de 5 à x : x = 5

L'instruction (en mode console) de cette affectation s'écrit donc :

>>>x = 5

Attention ! cette instruction n'a pas la même signification qu'en mathématiques. Il s'agit d'une affectation et non pas un symbole d'égalité. Ainsi, l'instruction

>>>x = x+1
est très fréquente en informatique en revanche, elle est inacceptable en mathématiques.

Sous Python, on peut affecter une valeur à plusieurs variables simultanément :

>>>a=b=2
>>>a
2
>>>b
2
>>>

Affichage et saisie

Affichage

La fonction print permet d'afficher dans la console les éléments mis entre parenthèses.

>>>print("Salut tout le monde !")
Salut tout le monde !	
					

La fonction print peut prendre plusieurs arguments séparés par une virgule ,.

>>>a=25
>>>print("Cette année, Noël sera le",a,"décembre 2020 !")
Cette année, Noël sera le 25 décembre 2020 !	
									

Vous avez dû remarquer avec l'exemple prédécent qu'à l'exécution, un espace ' ' ou " " est automatiquement ajouté entre chaque argument de la fonction print.
Ce comportement peut être modifié par l'ajout d'un argument identifié par le nom sep, de type chaîne de caractères (str en Python), de la façon suivante :

>>>val=3.14159
>>>print("Mots",'et','valeur',val,"à afficher.",sep="_")
Mots_et_valeur_3.14159_à afficher.
			

Le chaîne de caractères \n sert à passer à la ligne lors de l'affichage. Ainsi, dans l'utilisation suivante, les arguments affichés sont placés les uns en dessous des autres :

>>>val=3.14159
>>>print("Mots",'et','valeur',val,"à afficher.",sep="\n")
Mots
et
valeur
3.14159
à afficher.
								

Saisie

La fonction input permet d'obtenir une saisie depuis le clavier.

  1. Saisir dans la console le code suivant :
  2. annee=input("Quelle année sommes-nous ?")
    				
  3. Saisir dans la console le code suivant :
  4. type(annee)
    					
  5. Saisir dans la console le code suivant :
  6. print("L'an 2050 est dans",2050-annee,"ans.")
    					
    Pourquoi cet affichage ?

Attention ! La fonction input renvoie toujours une chaîne de caractères (son type est bien str) même si un nombre (entier ou réel) a été saisi. Pour pouvoir utiliser le renvoi dans un calcul, il est parfois nécessaire de changer son typage à l'aide des fonctions int ou float qui permettent de transformer respectivement une chaîne de caractères correspondant à un nombre en nombre entier ou nombre réel (un flottant pour être précis).

  1. Saisir dans la console le code suivant :
  2. annee=int(input("Quelle année sommes-nous ?"))
    				
  3. Saisir dans la console le code suivant :
  4. type(annee)
    					
  5. Saisir dans la console le code suivant :
  6. print("L'an 2050 est dans",2050-annee,"ans.")
    					
    Pourquoi cet affichage ?

>>>input("Quelle année sommes-nous !")
Salut tout le monde !	

Les fonctions

Notion

En informatiques, les fonctions servent à mieux structurer votre code. Par exemple, elles permettent d'éviter de répéter plusieurs fois des portions de codes identiques. Ainsi, une fonction peut être vu comme un «petit» programme :

  • à qui on donne des paramètres en entrée,
  • puis qui effectue alors un traitement sur ces paramètres,
  • qui renvoie enfin un résultat en sortie.
Une fonction qui modifie des variables mais sans renvoyer de résultat est appellée une procédure. Le langage Python ne fait pas de différence dans la syntaxe entre fonction et procédure.

vision naîve d'une fonction en informatique

En Python

En Python, une fonction peut s'écrire en suivant toujours le même formalisme :

  1. Commencer par le mot clé def,
  2. Poursuivre sur la même ligne par l'entête constituée des 3 éléments successifs suivants :
    1. le nom de la fonction
    2. entre parenthèses, de 0 à N paramètres avec pour chacun un nom
  3. Terminer obligatoirement la première ligne par deux points :
  4. En desous, écrire le blocs des instructions. Attention il faut indenter (=décaler) ce bloc.
  5. Finir en dernière ligne par le mot clé return suivi de ce que renvoie la fonction (ou None si la fonction ne retourne rien). Attention, cette ligne est indentée également et marque la fin de la fonction.

Voici visuellement la structure d'une fonction en Python :

def nomFonction(liste des arguments):
	blocs des instructions
	return résultat

Voici la fonction carrée :

def carree(a: float) -> float:
	return a**2 		# renvoie l'image de a par la fonction carree 
			
  • En Python, l'exponentiation (=puissance) se note avec **. Exemple : 5**2 correspond à 5^2.
  • Commentaires : le symbole # apparaîtra à maintes reprises. Il marque le début d’un commentaire que la fin de la ligne termine. Autrement dit, un commentaire est une information aidant à la compréhension du programme mais n’en faisant pas partie.
  • Une fonction est utilisée comme une instruction quelconque. Un appel de fonction est constitué du nom de la fonction suivi entre parenthèses des valeurs des paramètres d'entrée. Cet appel peut être fait :

    • Soit par un appel dans la console :
      >>>carree(3)
      9
      					
    • Soit dans le corps d'un programme :
      def carree(a):
      	return a**2 		# renvoie l'image de a par la fonction carree
      
      a = carree(3)			# a stocke la valeur 9
      b = a - carree(2)		# b stocke la valeur 5
      					
  • La notion de fonction en informatique relève du même concept qu'une fonction mathématique, c'est-à-dire qu'on définit une fonction puis on l'applique à différentes valeurs.

  • Vous remarquerez le symbole : très important en Python qui marque le début d'un bloc en dessous.

    C'est l'indentation qui délimite le bloc d'instructions.

  • La fonction se termine avec une instruction return. Ce qui suit le return est l'image des entrées par la fonction. Dès que la fonction rencontre un return, elle renvoie ce qui suit le return et stoppe son exécution.

Vocabulaire :
  • Lorsqu'on définit la fonction carree(), a est appelé paramètre de la fonction.

  • Lorsqu'on appelle la fonction avec une valeur explicite pour a, comme dans carree(3), on dira plutôt que 3 est un argument de la fonction. Ainsi, en appelant la fonction carree() d'argument 3, on obtient 9.

Bonnes pratiques de programmation

Préciser le typage de chacun des paramètres

Le langage Python est plus aisé pour démarrer la programmation pour la concision des codes écrits et pour la gestion automatique du typage par l'interpréteur. Cependant, le but est que vous puissiez à terme être capable de faire basculer vos compétences acquises en NSI sur Python vers d'autres langages de programmation.

Comme la plupart des langages de programmation nécessitent la spécification du typage des variables, à terme, on vous demandera d'écrire en Python, une fonction en précisant le type de chaque antrée et sortie en suivant le même formalisme ci-dessous :

def nomFonction(liste des arguments: type) -> typeRetour:
		blocs des instructions
		return résultat
	

La convention PEP8 donne l'habitude de nommer les fonctions (comme les variables) avec des lettres minuscules et des tirets bas (celui du "8") _. Pour clarifier la fonction, il est conseillé d'utiliser un verbe dans son nom (obtenir, donner, get, set, ...).

Documenter ses fonctions

Il est important de documenter vos fonctions, c'est-à-dire de décrire en quelques phrases le rôle de la fonction, de donner des informations sur la fonction, le lien entre les entrées et la sortie.

Pour cela, juste en dessous de la première ligne définissant la fonction, il suffit de mettre ses informations entre """ et """ ; c'est ce que l'on appelle en franglais le docstring de la fonction). En reprenant l'exemple précédent (sans le typage), on peut écrire :

def carree(a):
	""" 
	Fonction permettant de renvoyer le carré du nombre a qui est en paramètre
	"""
	return a**2 		# renvoie l'image de a par la fonction carree
					

L'intérêt de l'auto-documentation d'une fonction par un texte est double :

  • Pour vous : le faire vous oblige à réfléchir au contenu de votre fonction avant de la programmer ; c'est un gain d'efficacité,
  • Pour les utilisateurs de votre code (ou pour vous longtemps après avoir programmé la fonction) : Quand on saisit dans la console, après l'exécution de la fonction, l'instruction help(nom de la fonction), Python affiche le docstring de la fonction ce qui nous permet d'avoir des informations sur la fonction en cas d'oubli.
    >>> help(carree)
    Help on function carree in module __main__:
    				
    carree(a: float) -> float
    	Fonction permettant de renvoyer le carré du nombre a qui est en paramètre		
    >>>

La portée des variables

Il faut faire la différence entre les variables utilisé dans le programme (variables globales) et les variables utilisées dans une fonction (variables locales).

Vous allez comprendre cela à l'aide des exemples de l'exercice suivant :

Ecrire les trois fonctions suivantes puis faire des appels avec différentes valeurs de x pour observer les différences entre ces codes :

Ici, ni les types ni la documentation n'ont été écrites afin de faciliter la vision de la différence entre variable locale et variable globale.

x=-101
def carre(x):
	x=x**2
	return x

x=-101
def carre_2():
	x=x**2
	return x

x=-101
def carre_3():
	global x
	x=x**2
	return x
	

Normalement vous avez dû détecter un problème : il y a une fonction qui ne peut pas être interprétée !

Il faut privilégier les variables locales. La première écriture, celle de carre est la définition à privilégier.

Vous pouvez utiliser des variables globales, comme mais dans carre3, des cas qu'il faudra bien définir en amont.

En résumé, pour l'instant, pas de variables globales.

Précondition et postconditions en Python

Précondition

Voici le code en Python d'une fonction nommée get_unite qui prend comme paramètre un nombre entier et qui renvoie son chiffre des unités.

def get_unite(n: int) -> int:
	"""
	renvoie le chiffre des unités d'un entier n
	"""
	while n>=10 : 		# répétition tant que n est supérieur ou égal à 10
		n = n-10
	return n

Une documentation a été donnée afin d'expliciter le bon usage de la fonction. Mais on ne pas être certain qu'un utilisateur de la fonction respectera les contraintes implicites ou explicites de la documentation et du typage. Voici quelques exemples d'appel de la fonction :


>>> get_unite(4567)
7
>>> get_unite(45.67)
5.670000000000002
>>> get_unite(-6)
-6

On voit que l'appel conduit à une réponse à chaque fois, mais que celle-ci ne correspond pas toujours à ce qui est attendu. Pour rendre "robuste" la fonction précédente, on doit vérifier au début de celle-ci certaines contraintes de bon usage que l'on appelle précondition.

Dans l'exemple précédent, ces préconditions sont :

  • "Précondition 2" : n est de type entier (par exemple, le programme buggera si une chaîne de caractères est saisie come argument),
  • "Précondition 2" : n est positif (sinon le résultat renvoyé est la valeur négative saisie).

Pour cela, le langage Python possède l'instruction assert qui permet un mécanisme d'assertion.
Les deux préconditions précédentes s'ajoutent à la fonction précédente ainsi :

def get_unite(n: int) -> int:
	"""
	renvoie le chiffre des unités d'un entier n
	"""
	assert type(n)==int, "vous devez entrer un nombre entier."  		# "Précondition 1"
	assert n>=0, "le nombre étudié doit être positif ou nul."			# "Précondition 2"
	while n>=10 : 		# répétition tant que n est supérieur ou égal à 10
		n = n-10
	return n

Quelques explications :

  • a==b renvoie True si l'égalité "a=b" est vraie (même type et même contenu) et False sinon,
  • int est le type "entier". Il existe les types float pour les flottants, str pour les chaînes de cractères, ...
  • Une telle instruction assert est suivie :
    1. d'une condition (une expression booléenne qui vaut True ou False),
    2. éventuellement suivie d'une virgule , et d'une phrase en langue naturelle, sous forme d'une chaine de caractères.
  • L'instruction assert teste si sa condition. Deux cas possibles :
    1. si la condition est satisfaite, elle ne fait rien (l'interpréteur passe à la ligne suivante)
    2. sinon elle arrête immédiatement l'exécution du programme en affichant la phrase qui lui est éventuellement associée. Ainsi, l'interpréteur arrête l'exécution de la fonction plutôt que de faire planter le programme et affiche un message clair pour corriger l'erreur !

Voici un fonction nommée diviser réalisation la division du premier argument par le second.

def diviser(a: float,b: float) ->float:
	"""
	renvoie le résultat décimal de la division de a par b.
	"""
	return a/b

Quelle précondition, écrite en langage Python, doit-on rajouter afin d'assurer le bon fonctionnement de la fonction ?

Postcondition

Souvent les fonctions sont appelées au cours de programme ; le type et la qualité du résultat renvoyé est important pour ne pas conduire à un plantage. Des contraintes sur la variable renvoyée sont souvent nécessaires : on les appellent les postconditions.

Reprenons l'exemple précédent :

def get_unite(n: int) -> int:
"""
renvoie le chiffre des unités d'un entier n
"""
while n>=10 : 		# répétition tant que n est supérieur ou égal à 10
	n = n-10
return n

Voici le résultat de quelques appels effectués :


>>> get_unite(4567)
7
>>> get_unite(45.67)
5.670000000000002
>>> get_unite(-6)
-6

Comme le résultat renvoyé doit être un nombre entier compris entre 0 et 9, on va rajouter les postconditions suivantes :

  1. "postcondition 1" : n est un entier naturel.
  2. "postcondition 2" : n est positif.
  3. "postcondition 3" : n est strictement inférieur à 10

On utilise encore l'instruction assert, juste avant le return pour écrire ces postconditions comme montré ci-dessous :

def get_unite(n: int) -> int:
	"""
	renvoie le chiffre des unités d'un entier n
	"""
	while n>=10 : 		# répétition tant que n est supérieur ou égal à 10
		n = n-10
	assert type(n)==int, "Le nombre renvoyé devrait être un entier."  		# "Postcondition 1"
	assert n>=0, "le nombre renvoyé doit être positif ou nul."				# "Postcondition 2"
	assert n<10, "le nombre renvoyé doit être inférieur à 10."			# "Postcondition 3"
	return n
	

  • Ces mécanisme d'assertion est une aide au développeur qui permet de repérer des erreurs dans le code.
  • En supprimant toutes les assertions, le programme doit à la fin toujours fonctionner.
  • Normalement, Lors de la finalisation du programme les différentes assertions doivent être ôtées.

Travail à effectuer

L'indice de Masse Corporel est un nombre réel utilisé en médecine. Sa formule est pour une masse en kilos et une taille en mètres : IMC = masse/taille^2.

Écrire en langage Python un programme qui :

  1. Demande la masse de l'utilisateur en kg (nombre réel)
  2. Demande la taille de l'utilisateur en m (nombre flottant).
  3. affiche l'IMC de l'utilisateur.
Testez votre programme avec différentes valeurs.

  1. Écrire une fonction nommée dire_bonjour ayant comme paramètre un mot qui affiche bonjour suivi du mot.
    Exemple : dire_bonjour("Paul") affiche "Bonjour Paul !".
  2. Est-ce une fonction ou une procédure ? Pourquoi ?


Écrire une fonction appelée echanger qui échange les valeurs de deux arguments a et b.
Par exemple, echanger("mot",12) renvoie (12,"mot").

La possibilité de stocker un entier dans une variable stockant initialement une chaîne de caractères est possible en Python car c'est un langage non typé : l'interpréteur gère automatiquement le typage. Attention ! Dans beaucoup d'autres langages de programmation, cela n'est pas possible.

  1. Documenter la fonction suivante en ajoutant un docstring.

    def examen(x,y,z,t):
    	m=(2*x+2*y+z+t)/6
    	if m>=10:
    		print("Le candidat est reçu")
    	else :
    		print("le candidat est refusé")
    	return None
    
  2. Proposer des préconditions écrites sur les variables x, y, z et t en utilisant l'instruction assert qui assurent le bon usage de cette fonction examen.

Voici une fonction cherche_lettre qui affiche si le caractère lettre est présent ou non dans le nom saisi nom, tout deux entrés comme paramètre de la fonction :

def cherche_lettre(nom,lettre):
	if lettre in nom:
		print(lettre," est dans le nom ",nom)
	else:
		print(lettre,"n' est pas dans le nom ",nom)
	return None 

  1. Préciser le type de chacun des paramètres d'entrée.
  2. Est-ce une fonction ou une procédure ?
  3. Proposer des préconditions écrites sur les variables nom et lettre en utilisant l'instruction assert qui assurent le bon usage de cette fonction cherche_lettre.

    La longueur d'une chaîne de caractères (c'est-à-dire le nomnbre de caractères) nommée chaine en Python s'obtient avec la commande len(chaine).

Travail complémentaire

Les exercices qui suivent nécessitent d'utiliser les instructions if, for ou while ainsi que des listes.

Écrire une fonction get_max qui prend en paramètre une liste et renvoie le max des éléments de celle-ci.

Indications :

  • Une liste en python se note entre crochet [ ] chaque élément étant séparée d'une virgule ,.
  • la longueur d'une liste nommée liste en Python s'obtient avec la commande len(liste).

  1. Écrire une fonction div_euclidienne qui prend en paramètre deux nombres entiers a et b, qui effectue la division euclidienne de a par b et qui renvoie le couple (a,i), respectivement le reste et le quotient de cette division euclidienne.

    Un tel couple est appelé tuple.

  2. Proposer des préconditions sur les paramètres a et b afin d'assurer le bon usage de la fonction div_euclidienne.
  3. Proposer des postconditions sur les deux valeurs renvoyées (a et i) afin d'assurer le bon usage de la fonction div_euclidienne.

Réécriture de l'algorithme obtenu à l'exercice 17 du chapitre A1 sous forme d'une fonction

  1. Écrire une fonction get_max_position qui prend en paramètre une liste et qui renvoie le couple (le tuple) (PG,IG), respectivement la plus grande valeur de la liste et la position de cette valeur maximale dans la liste.

    Indications :

    • Une liste en python se note entre crochet [ ] chaque élément étant séparée d'une virgule.
    • la longueur d'une liste nommée liste en Python s'obtient avec la commande len(liste)
    • Chaque élément d'une liste est positionné à l'aide d'un index compris entre 0 et len(liste)-1.
    • L'élément d'une liste nommée liste positionné à l'index i est obtenu avec : liste[i].

  2. Proposer des postconditions sur la valeur renvoyée IG afin d'assurer le bon usage de la fonction get_max_position.

Savoir faire et Savoir

  • Écrire une affection en Python
  • Afficher à l'aide de l'instruction print
  • Faire saisir depuis le clavier à l'aide de l'instruction input
  • Écrire une fonction en Python
  • Écrire la documentation d'une fonction
  • Écrire une précondition à l'aide de l'instruction assert
  • Écrire une postcondition à l'aide de l'instruction assert

  • Connaître la différence entre variable locale et globale

À retenir !

  • La structure générale d'une fonction :
    def nomFonction(liste des arguments):
    	blocs des instructions
    	return résultat
    				
  • L'indentation permet de définir ce qui fait partie de la fonction de ce qui en est exclu.
  • Toujours terminer une fonction par un returnqui doit être unique.
  • Une fois une fonction définie, pensez à l'appeler ensuite pour l'utiliser.

À maîtriser à terme

  • Documenter ses fonctions : texte explicatif entre triples guillements """ """.
  • Préciser les types des paramètres et du retour dans l'en-tête d'une fonction.