---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
kernelspec:
  display_name: Python 3 (ipykernel)
  language: python
  name: python3
learning:
  objectives:
    understand: ["d\xE9finition de fonction", valeur de retour, documentation, test,
      assertion]
    apply: [appel de fonction, "param\xE8tre formel", "expression bool\xE9enne", conditionnelle,
      print]
  prerequisites:
    understand: [expression, affectation, print]
---

+++ {"nbgrader": {"grade": false, "grade_id": "cell-a741e2baae8a5295", "locked": true, "schema_version": 3, "solution": false}}

# Les fonctions

:::{todo}
Synchroniser avec Semaine4/01-fonctions.md; notamment la MySTification des exercices
:::

+++ {"nbgrader": {"grade": false, "grade_id": "cell-1a4b7dd4e98dab2b", "locked": true, "schema_version": 3, "solution": false, "task": false}}

## Documentation d'une fonction

Lorsqu'on écrit une fonction, est important de la *documenter*, c'est-à-dire de fournir
des informations claires et précises sur ce que fait la fonction, comment l'utiliser
correctement, et ce à quoi s'attendre en termes de comportement et de résultats. Cette
documentation est généralement écrite sous forme de commentaires dans le code source de
la fonction.

**Exemple :**

```python

def factorielle(n):
    """ Fonction qui calcule la factorielle
       Paramètre n : un nombre entier positif
       Renvoie n!
     """
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-6e322c521d85f837", "locked": true, "schema_version": 3, "solution": false, "task": false}}

**Une bonne documentation :**

- Est concise et précise
- Donne les *préconditions* sur les paramètres
- Décrit le résultat (ce que fait la fonction)

+++ {"nbgrader": {"grade": false, "grade_id": "cell-8aea19a2f56de316", "locked": true, "schema_version": 3, "solution": false, "task": false}}

**Astuce pour être efficace :**

- *Toujours commencer par écrire la documentation*\
  De toute façon il faut réfléchir à ce que la fonction va faire!

+++ {"nbgrader": {"grade": false, "grade_id": "cell-069684ebbdbd5e3c", "locked": true, "schema_version": 3, "solution": false, "task": false}}

## Tests d'une fonction

En informatique, il est important d'apprendre à écrire des tests qui permettent de
valider le bon fonctionnement des ses fonctions et programmes.

Il existe en Python un mot-clé, `assert`, qui permet de tester une expression booléenne.
Vous l'avez déjà rencontré dans les TPs précédents. Si cette expression s’évalue à True,
il ne se passe rien.

**Astuces pour être efficace :**

- *Commencer par écrire les tests d'une fonction*\
  De toute façon il faut réfléchir à ce qu'elle va faire!

+++ {"nbgrader": {"grade": false, "grade_id": "cell-ac1e780e9b736ea1", "locked": true, "schema_version": 3, "solution": false, "task": false}}

- Tester les cas particuliers

- Tant que l'on est pas sûr que la fonction est correcte :

  - Faire des essais supplémentaires
  - Capitaliser ces essais sous forme de tests

- Si l'on trouve un bogue (ou bug en anglais, une erreur) :

  - Ajouter un test caractérisant le bogue

+++ {"nbgrader": {"grade": false, "grade_id": "cell-e27186737f6367c6", "locked": true, "schema_version": 3, "solution": false, "task": false}}

:::{tip} Les fonctions préexistantes en python
En Python, on peut appeler de nombreuses fonctions préexistantes. Vous en avez déjà vu
quelques unes : `type()`, `print()`, `range()`, etc. Nous en découvrirons d'autres au fur
et à mesure des TPs.
:::

+++ {"nbgrader": {"grade": false, "grade_id": "cell-8b56bb2323e686a8", "locked": true, "schema_version": 3, "solution": false}}

## Exercice 1 : renvoyer versus afficher

1. Les deux cellules suivantes définissent respectivement les fonctions `abs` et
   `afficheAbs`; notez les différences entre ces deux fonctions, puis exécutez les
   cellules.

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-adbf3ea334252de8
  locked: true
  schema_version: 3
  solution: false
---
def abs(a):
    """ Une fonction qui calcule la valeur absolue
      Paramètres : a, un entier
      Renvoie : la valeur absolue de a
    """
    if (a > 0):
        return a
    else:
        return -a
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-7d7a9fa58c13128d
  locked: true
  schema_version: 3
  solution: false
---
def afficheAbs(a):
    """ Une fonction qui affiche la valeur absolue
      Paramètres : a, un entier
    """
    if (a > 0):
        print(a)
    else :
        print(-a)
```

+++ {"nbgrader": {"grade": true, "grade_id": "cell-ebbfec0d5cb0e626", "locked": false, "points": 1, "schema_version": 3, "solution": true}}

2. Observez les appels suivants; y a-t-il une différence entre `abs` et `afficheAbs`?

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-96a8003bde332ef4
  locked: true
  schema_version: 3
  solution: false
---
abs(-3)
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-acc035c120b5e1a5
  locked: true
  schema_version: 3
  solution: false
  task: false
---
abs(-3)
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-525744fbd9aca5dd
  locked: true
  schema_version: 3
  solution: false
---
afficheAbs(-3)
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-525744fbd9aca5de
  locked: true
  schema_version: 3
  solution: false
---
afficheAbs(-3)
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-6f19e46ffe7ff87b", "locked": true, "schema_version": 3, "solution": false, "task": false}}

:::{admonition} Indication : Explication
:class: hint

`abs` **renvoie** une valeur alors que `afficheAbs` l'**affiche**. On notera ci-dessus
que, avec `abs(3);`, Jupyter ne montre pas du tout la valeur alors que, avec `abs(3)`,
Jupyter montre la valeur avec son type (`int`). Inversement `afficheAbs` affiche la
valeur mais ne la renvoie pas.
:::

+++ {"nbgrader": {"grade": false, "grade_id": "cell-d95a34f5ac81eb33", "locked": true, "schema_version": 3, "solution": false}}

:::{admonition} Exercice 1 (suite)
3. Essayez de deviner le résultat des appels suivants puis exécutez pour vérifier :
:::

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-6a55898a16557a96
  locked: true
  schema_version: 3
  solution: false
---
abs(-5) + abs(3)
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-866c4a0c9a4a6ab8
  locked: true
  schema_version: 3
  solution: false
tags: [raises-exception]
---
afficheAbs(-5) + afficheAbs(3)
```

+++ {"nbgrader": {"grade": true, "grade_id": "cell-6c7a39b6a3ef0358", "locked": false, "points": 1, "schema_version": 3, "solution": true}}

La seconde cellule vous donne une **erreur**, comprenez-vous pourquoi?

:::{admonition} Explication
:class: hint dropdown

`afficheAbs` ne fait qu'afficher une valeur sans la renvoyer (type de retour `void`); il
n'est donc pas possible de réutiliser cette valeur par la suite, par exemple pour faire
un calcul ou un test.
:::

+++ {"nbgrader": {"grade": false, "grade_id": "cell-5699e6d9d0f3d16a", "locked": true, "schema_version": 3, "solution": false}}

## Exercice 2 : une fonction utilisant une fonction

+++ {"nbgrader": {"grade": false, "grade_id": "cell-b9cda32e468d6d8b", "locked": true, "schema_version": 3, "solution": false}}

Complétez la fonction ci-dessous dont on donne la **documentation**.

⚠️ **Vous devez utiliser un appel à l'une des deux fonctions précédentes, `afficheAbs` ou
`abs`; laquelle faut-il choisir ? Pourquoi ?** ⚠️

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-ef47de23dc65918c
  locked: false
  schema_version: 3
  solution: true
---
def distance(a, b):
    """ Distance de deux points sur une droite
      Paramètre : a un entier: la coordonnée du premier point
      Paramètre : b un entier: la coordonnée du deuxième point
      Renvoie : la valeur absolue de la différence entre a et b
    """
    ### BEGIN SOLUTION
    return abs(b-a)
    ### END SOLUTION
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-cf373e1ce7210f3a", "locked": true, "schema_version": 3, "solution": false}}

Vérifiez le comportement de votre fonction `distance` sur l'exemple ci-dessous; essayez
sur quelques autres exemples; corrigez la fonction si nécessaire :

```{code-cell} ipython3
distance(5,3)
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-e85af7775f763ef9", "locked": true, "schema_version": 3, "solution": false, "task": false}}

Les tests ci-dessous automatisent partiellement la vérification du comportement de votre
fonction; comme la valeur attendue est spécifiée, il ne reste qu'à vérifier que l'on
obtient bien `true` à chaque fois :

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-1468d3b77d12dfbb
  locked: true
  schema_version: 3
  solution: false
---
distance(5,3) == 2
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-fb7ffc1acf3cf457
  locked: true
  schema_version: 3
  solution: false
---
distance(-4,2) == 6
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-973b66f0d812df05
  locked: true
  schema_version: 3
  solution: false
---
distance(2,-5) == 7
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-f389d530bfbe35e0", "locked": true, "schema_version": 3, "solution": false}}

## Exercice 3 : tests automatiques

Dans l'exercice précédent, on a testé la fonction `distance`. Cependant, il a fallu
exécuter les trois cellules de test séparément et s'assurer pour chacune d'entre elles
que `true` était affiché et pas `false`. Pour n'être averti que s'il y a un problème et
savoir d'où il provient, nous allons rassembler les tests en une seule cellule à l'aide
de l'infrastructure d'automatisation des tests aperçue dans les TPs précédents.

Essayez les exemples suivants. .

:::{admonition} Astuce
:class: tip

À chaque fois que vous redémarrez le noyau, allez ensuite dans `Cellule` ->
`Exécuter toutes les précédentes`. Cela exécute toutes les cellules qui sont avant celle
où vous vous trouvez. Vous éviterez ainsi de perdre du temps à chercher où sont les
cellules qu'il faut ré-exécuter.
:::

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-409ed522d0d7ce28
  locked: true
  schema_version: 3
  solution: false
tags: [raises-exception]
---
assert ( 1 < 2 )
assert ( 1 > 2 )
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-4f9b5f26274b8d47", "locked": true, "schema_version": 3, "solution": false}}

On remarque que, si la condition en paramètre de `assert` est vraie, il n'y a aucun
affichage. Par contre si la condition est fausse, un message d'erreur indiquant la
condition fausse est affiché.

+++ {"nbgrader": {"grade": false, "grade_id": "cell-c7c30794bfe3d639", "locked": true, "schema_version": 3, "solution": false}}

Avec `assert`, les tests de la fonction `distance` se mettent sous la forme :

```{code-cell} ipython3
---
nbgrader:
  grade: true
  grade_id: cell-30fde9ec08937b80
  locked: true
  points: 1
  schema_version: 3
  solution: false
---
assert ( distance(5,3)  == 2 )
assert ( distance(-4,2) == 6 )
assert ( distance(2,-5) == 7 )
### BEGIN HIDDEN TESTS
assert ( distance(-5,-2) == 3)
### END HIDDEN TESTS
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-5a60b1893097e9fd", "locked": true, "schema_version": 3, "solution": false}}

Exécutez ces tests. Leur exécution ne devrait rien afficher.

+++ {"nbgrader": {"grade": false, "grade_id": "cell-b33477865e6bac73", "locked": true, "schema_version": 3, "solution": false, "task": false}}

Exécutez les deux cellules suivantes pour définir et tester la fonction `maxAbs`.
Corrigez la fonction pour qu'elle passe les tests.

```{code-cell} ipython3
def maxAbs(a, b):
    """ Une fonction qui renvoie la valeur absolue du plus grand des deux entiers 
       en valeur absolue
      Paramètre : a, un entier
      Paramètre : b un entier
      Renvoie : la valeur absolue du plus grand des deux entiers en valeur absolue
     """
    if abs(a) >= abs(b):
        return a
    else :
        return b
```

```{code-cell} ipython3
:tags: [raises-exception]

assert( maxAbs(  6,  2) ==  6 )
assert( maxAbs(-15,  5) == 15 )
assert( maxAbs( -2, -3) ==  3 )
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-d27b0a203d050706", "locked": true, "schema_version": 3, "solution": false}}

## Exercice 4

Mêmes consignes que pour l'exercice 1 :

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-a3cf80e3952bdd34
  locked: false
  schema_version: 3
  solution: true
---
def sommeAbs(a, b):
    """ somme des valeurs absolues (aussi appelée norme L1)
      Paramètre : a un entier
      Paramètre :  b un entier
      Renvoie : la somme des valeurs absolues de a et b
     """
    ### BEGIN SOLUTION
    return abs(a) + abs(b)
    ### END SOLUTION
```

```{code-cell} ipython3
sommeAbs(5,-3)
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-6d67d87937dbe617
  locked: true
  schema_version: 3
  solution: false
---
sommeAbs(5,-3)  == 8
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-1cf910256b7ba798
  locked: true
  schema_version: 3
  solution: false
---
sommeAbs(-4,-2) == 6
```

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-b0072c6876feea78
  locked: true
  schema_version: 3
  solution: false
---
sommeAbs(2,5) == 7
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-6501a3f1941bd272", "locked": true, "schema_version": 3, "solution": false}}

## Exercice 5 : suivre la documentation et les tests

Définissez la fonction `nombreSecondes` dont la documentation est donnée ci-dessous :

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-0d59febaf9233496
  locked: false
  schema_version: 3
  solution: true
---
def nombreSecondes(j, h, m, s):
    """ Renvoie en secondes la durée décrite en jours, heures, minutes, secondes
      Paramètre : j un entier représentant le nombre de jours
      Paramètre : h un entier représentant le nombre d heures
      Paramètre : m un entier représentant le nombre de minutes
      Paramètre : s un entier représentant le nombre de secondes
      Renvoie : la durée totale en secondes
    """
    ### BEGIN SOLUTION
    return s + (60 * (m + 60 * (h + 24*j)))
    ### END SOLUTION
```

+++ {"nbgrader": {"grade": false, "grade_id": "cell-e103b2353b1c2e15", "locked": true, "schema_version": 3, "solution": false}}

Vérifiez son fonctionnement sur les exemples et tests ci-dessous :

```{code-cell} ipython3
---
nbgrader:
  grade: false
  grade_id: cell-ab20ff3783903f6e
  locked: true
  schema_version: 3
  solution: false
  task: false
---
nombreSecondes(1,1,1,1)
```

```{code-cell} ipython3
---
nbgrader:
  grade: true
  grade_id: cell-babf9ae1f3f02491
  locked: true
  points: 1
  schema_version: 3
  solution: false
---
assert( nombreSecondes(0,0,0,1)  == 1      )
assert( nombreSecondes(0,0,1,0)  == 60     )
assert( nombreSecondes(0,1,0,0)  == 3600   )
assert( nombreSecondes(1,0,0,0)  == 86400  )
assert( nombreSecondes(3,2,5,25) == 266725 )
```
