Aller au contenu

1NSI : Cours Fonctions & Procédures. Modules⚓︎

Fonctions, Procédures & Routines⚓︎

Fonctions⚓︎

Une fonction (au sens de Python) est une séquence d'instructions (une succession continue de plusieurs lignes de codes), isolées (d'un certain point de vue) du reste du code, et qui renvoie en retour une valeur/un objet.

Procédures⚓︎

Une procédure (au sens de Python) est une séquence d'instructions (une succession continue de plusieurs lignes de codes), isolées (d'un certain point de vue) du reste du code, mais NE renvoyant PAS en retour de valeur/d'objet.

Routines⚓︎

Une routine est soit une fonction, soit une procédure.

Comment définir une fonction? une procédure?⚓︎

Comment définir une fonction?⚓︎

def nomFonction(parametres/arguments):
  """
  Spécifications de la fonction:
  ce que fait la fonction
  le type des paramètres/arguments en entrée
  le type des paramètres/arguments en sortie
  """
  # Corps de la fonction qui réalise un certain traitement
  return valeur

REMARQUES

  • Les définitions de fonctions se placent en haut du fichier contenant le script.
  • Dans le cas d'une procédure, il ne doit y avoir AUCUN return
  • Dans le cas d'une fonction, Il PEUT y avoir plusieurs return dans le corps de la fonction. Dans ce cas, c'est le premier return rencontré lors de l'exécution de la fonction qui fait sortir définitivement de la fonction. Dans ce cas, les autres return ne seront donc jamais rencontrés durant cette exécutin particulière (mais ils le seront peut-être lors d'autres exécutions)

Notion de Type Hinting (depuis Python 3.5)⚓︎

Il est fortement préconisé/recommandé d'utiliser du Type Hinting, c'est-à-dire de donner des indications sur le type des données passées en argument en entrée, ainsi que le type des données renvoyées en sortie:

# TYPE HINTING
def carre(x:float)->float:
  """Fonction qui calcule le carré d'un nombre
  x : float est un paramètre OBLIGATOIRE
  """
  return x**2

Notion de Signature⚓︎

La Signature (de type) d'une fonction, ou d'une procédure, est composée de: * du nom de ses paramètres * du type des données des paramètres en entrée, * du type de la valeur de retour (quel type de données renvoie le return ?)

Comment définir une procédure?⚓︎

Idem mais sans le (sans aucun) return

# TYPE HINTING
def saluer(nom:str)->None:
  """Fonction qui salue quelqu'un
  nom : str est un paramètre OBLIGATOIRE
  """
  print("Bonjour ",nom," !")

Utilisation d'une fonction/procédure⚓︎

Une fonction, ou une procédure, N'est JAMAIS appelée AUTOMATIQUEMENT par l'algorithme, c'est-à-dire exécutée, SAUF si elle est spécifiquement appelée à une certaine ligne.

def saluer(nom:str)->None:
  """Fonction qui salue quelqu'un
  nom : str est un paramètre OBLIGATOIRE
  """
  print("Bonjour ",nom," !")
# ICI, AUCUN APPEL DE LA PROCÉDURE...

La procédure saluer() N'est jamais appelée, donc jamais exécutée (le code à l'intérieur n'est jamais exécuté)

REMARQUE Les appels de procédures se placent après les définitions des fonctions/procédures.

Ci-dessous, la procédure saluer() est appelée

def saluer(nom:str)->None:
  """Fonction qui salue quelqu'un
  nom : str est un paramètre OBLIGATOIRE
  """
  print("Bonjour ",nom," !")
# REMARQUE : Une procédure ne renvoie AUCUNE valeur,
# donc il n'y a rien/aucune valeur à stocker lors de son appel...
# APPEL DE LA PROCÉDURE saluer()
saluer('Pauline')

def carre(x:float)->float:
  """Fonction qui calcule le carré d'un nombre
  x : float est un paramètre OBLIGATOIRE
  """
  return x**2
# ICI, AUCUN APPEL DE LA FONCTION carre()...

La fonction carre() N'est jamais appelée, donc jamais exécutée.

REMARQUE Les appels de fonctions se placent après les définitions des fonctions/procédures.

Ci-dessous, la procédure saluer() est appelée

def carre(x:float)->float:
  """Fonction qui calcule le carré d'un nombre
  x : float est un paramètre OBLIGATOIRE
  """
  return x**2
# Le résultat d'une fonction DOIT être stocké dans une variable
# car une fonction renvoie TOUJOURS une valeur
resultat = carre(2)
print("Résultat : {}".format(resultat))

Paramètres des fonctions⚓︎

Sans arguments/paramètres⚓︎

Une fonction PEUT n'admettre AUCUN paramètre/argument.

def saluer():
  """Fonction qui salue"""
  print("Bonjour")
saluer()

Ici, la fonction saluer() est bien appelée (après sa définition)

un argument OBLIGATOIRE⚓︎

def saluer(nom:str)->None:
  """Fonction qui salue quelqu'un
  nom : str est un paramètre OBLIGATOIRE
  """
  print("Bonjour {}".format(nom))
# bonjour() provoque une erreur :
# TypeError: bonjour() missing 1 required positional argument: 'nom'
saluer("Laura")

Plusieurs arguments OBLIGATOIRES⚓︎

def somme(a:float,b:float)->float:
  """Fonction qui calcule la somme de deux nombres
  a : float est un paramètre OBLIGATOIRE
  b : float est un paramètre OBLIGATOIRE
  """
  return a+b
resultat = somme(2,3)
print("Résultat : {}".format(resultat))
GÉNÉRALISATION De même, avec la même sytanxe, on peut définir une fonction avec une quantité quelconque de paramètres obligatoires.

arguments avec valeurs par défaut⚓︎

def bonjour(prenom,age=18):
  """Procédure qui salue une personne par son prénom et son âge
  prenom : str est un paramètre OBLIGATOIRE
  age : int est un paramètre AVEC UNE VALEUR PAR DÉFAUT 18
  """
  print("Bonjour {}, vous avez {} ans".format(prenom,age))

bonjour("marie")
bonjour("paul",29)

REMARQUE Les arguments avec des valeurs par défaut doivent OBLIGATOIREMENT être écrits à la fin de la liste des arguments, dans la définition d'une fonction.

Un nombre variable d'arguments avec *args⚓︎

La syntaxe *args (on peut remplace args par le nom que vous souhaitez), permet d'indiquer, lors de la définition d'une fonction, que notre fonction peut accepter un nombre variables d'arguments. Ces arguments args sont passés sous forme d'un tuple

def somme(*valeurs):
  s = 0
  for valeur in valeurs:
    s += valeur
  return s
resultat1 = somme(2,3)
resultat2 = somme(2,3,4)
resultat3 = somme(2,3,4,5)
print("Résultat 1 : {}".format(resultat1))
print("Résultat 2 : {}".format(resultat2))
print("Résultat 3 : {}".format(resultat3))

Un nombre variable d'arguments avec **kwargs⚓︎

La syntaxe **kwargs (on peut remplace kwargs par le nom que vous souhaitez), permet d'indiquer, lors de la définition d'une fonction, que notre fonction peut accepter un nombre variables d'arguments, mais cette fois-ci les arguments devront être passés sous la forme d'un dictionnaire Python: Ces arguments kwargs sont passés sous forme d'un dict

def personne(**caracteristiques):
  for i,j in caracteristiques.items():
    print(i,j)
personne(prenom="Angelina",age=29)
personne(nom="Brad",age=31)

REMARQUE On utilise la méthode items() qui permet de faire du tuple-unpacking c'est-à-dire de récupérer les différentes paires clés-valeurs d'un dictionnaire.

Séparer les données afin de les passer à une fonction⚓︎

Les syntaxes *args et **kwargs peuvent être utilisées pour réaliser les opérations inverses de celles présentées ci-dessus, c'est-à-dire séparer des données regroupées dans un conteneur pour les passer séparéement à la fonction.

def somme(a,b,c):
  s = a+b+c
  return s
x = [2,4,5]
resultat = somme(*x)
print("Résultat : {}".format(resultat))

Fonctions anonymes lambda⚓︎

Définir une fonction lambda⚓︎

Python permet la définition de mini-fonctions, définies à la volée, en une seule ligne, sans leur donner de noms particulier.

>>> lambda x: 2*x
<function __main__.<lambda>(x)>

prouve que la fonction lambda, c'est-à-dire une fonction sans nom, a bien été créér donc définie, mais elle n'a PAS été appelée.

Ceci est l'équivalent de la notation mathématique suivante:

\(x\mapsto 2x \quad\) comprendre "la fonction qui à \(x\) associe \(2x\)".

Appeler une fonction lamda⚓︎

méthode 1: stocker la fonction lambda dans une variable⚓︎

>>> f = lambda x: 2*x
>>> f(2)
4
>>> f(3)
6

On a ainsi: * stocké une fonction sans nom dans la variable f, puis * appelé f comme d'habitude, avec un argument pour x

méthode 2: appel direct de lambda avec des ()⚓︎

>>> (lambda x:x*2)(3)
6

Variables Locales vs variables Globales⚓︎

Variables locales⚓︎

Les variables définies à l'intérieur d'une fonction n'existent, donc ne sont accessibles, qu'à l'intérieur de cette fonction:

def f(x):
  a = 2
  return 2*x
resultat = f(3)
print("Résultat = ",resultat)
print("a = ",a)

Dans l'exemple précédent, la variable a n'existe qu'à l'intérieur de la fonction f. La ligne 6 renvoie donc l'erreur suivante : NameError: name 'a' is not defined

On dit dans ce cas que : * a est une variable locale, et/ou que * la variable a admet une portée 🇫🇷 locale (/ un scope :uk: local)

La portée 🇫🇷 ou le scope :uk: d'une variable locale est un espace de noms restreint à la fonction, par opposition à l'espace de noms global (qui contient des noms d'objets définis globalement, au niveau de l'ensemble de l'algorithme).

L'espace de noms d'une fonction contient donc les variables locales, ainsi que le nom des paramètres définissant la fonction.

Les variables locales sont détruites après l'appel de la fonction. (ramasse miettes / garbage collector)

Variables Globales⚓︎

L'algorithme précédent prouve qu'une variable locale n'est pas accessible depuis l'extérieur de la fonction. Selon les contextes, cela peut être considéré comme une qualité, ou comme un problème :

Comment résoudre ce problème ?⚓︎

Dans le corps d'une fonction, on peut utiliser le mot-clé global pour définir une variable en tant que variable globale, au lieu d'être définie comme variable locale, comme cela devrait être le cas par défaut.

On dit que la variable admet une portée 🇫🇷 globale (/un scope global :uk: )

def f(x):
  global a
  a = 2
  return 2*x
resultat = f(3)
print("Résultat = ",resultat)
print("a = ",a)

Champ Global accessible depuis le Champ Local⚓︎

Une variable définie dans le champ global, existe également dans le champ local (à manipuler avec précaution):

def f(x):
  print("a = ",a)
  return 2*x
# A L'EXTERIEUR DE LA FONCTION
a=2
resultat = f(3)
print("Résultat = ",resultat)
# affiche bien "a=2" dans le corps de la fonction

Conflits Global vs Local⚓︎

En cas de conflit potentiel, entre une variable globale et une variable locale portant le même nom, c'est la portée de la variable qui résout le problème: * Dans le champ local (c'est-à-dire dans le corps de la fonction), la variable en doublon est vue comme une variable locale * Dans le champ global, la variable en doublon est vue comme une variable globale Il n'y a donc pas de conflits.

Modules⚓︎

Création de Modules⚓︎

Un module est un fichier python en .py ne contenant QUE des définitions de fonctions/procédures, éventuellement des constantes, destinées à être utilisées ailleurs, comme par exemple dans d'autres fichiers.

"""
nom de ce Fichier : 'monModule.py'
Module inutile qui affiche des bonjours internationaux :)
"""

# CONSTANTES
PI = 3.141592653

# FONCTIONS
def bonjour(nom:str)->str:
    """Dit Bonjour."""
    return "Bonjour " + nom

def ciao(nom:str)->str:
    """Dit Ciao."""
    return "Ciao " + nom

def hello(nom:str)->str:
    """Dit Hello."""
    return "Hello " + nom

Méthodes d'import de Modules⚓︎

Pour utiliser le module monModule : * dans un interpréteur Python * ou bien dans un (autre) fichier Python

Tous les exemples qui suivent, contiennent des codes qui ont été placés dans un fichier test.py lui-même placé dans le même dossier que 'monModule.py'

# Import uniquement de la fonction 'bonjour()' du module 'monModule'
from monModule import bonjour
bonjour("Paul")

# Import uniquement de la fonction 'bonjour()' du module 'monModule'
from monModule import bonjour, ciao
bonjour("Paul")
ciao("Paula")

# Import de toutes les fonctions du module 'monModule'
from monModule import *
bonjour('Karl')
ciao('Bella')
hello('John')

# Import de toutes les fonctions du module 'monModule' d'une autre manière
import monModule
monModule.bonjour('Karl')
monModule.ciao('Bella')
monModule.hello('John')

# Import de toutes les fonctions du module 'monModule' avec un alias
import monModule as mod
mod.bonjour('Karl')
mod.ciao('Bella')
mod.hello('John')