Aller au contenu

Les fonctions en Python

Attention

Ce chapitre présente une notion très importante. Prenez bien votre temps pour comprendre, et n'hésitez pas à poser des questions !

Les logiciels font partie des "objets" les plus complexes jamais conçus par des humains.

Il nous est impossible de visualiser mentalement un logiciel de grande taille, ou même de taille moyenne dans tous les détails de son fonctionnement à la fois.

Voici un aperçu du nombre de lignes (approximatifs) de code (sloc) de certains logiciels1. Les commentaires sont exclus.

Logiciel Usage Ordre de grandeur sloc Principaux langages
OpenFlappyBird Une version Libre de Flappy Bird 600 Java
SuperTux Une version Libre de Super Mario 100K C++
Terasology Une version Libre de Minecraft 130K Java
KDenLive Editeur vidéo non-linéaire 150K C++, C
Audacity Enregistrement et traitement audio 1M Python, C, C++
CPython Interpréteur Python le plus répandu 1.5M Python, C
Blender Modélisation 3D 2.3M C++, C, Python
llvm Suite de compilation pour les langages de la famille du C. 11M C++, C, Python, LLVM IR, ASM, CMake2, Objective C
Firefox Navigateur Web 25M C++, C, Python, JavaScript, ASM, Rust

A titre de comparaison, l'intégrale du Seigneur des Anneaux compte environ 400 000 mots3.

Pour surpasser cette complexité, les langages de programmation fournissent des outils comme les fonctions.

Pour découvrir les fonctions on va créer dans ce chapitre un programme qui fait un dessin à l'aide de Turtle.

Turtle

Turtle est un outils de Python qui permet de dessiner à l'écran.

Pour l'utiliser, il suffit d'ajouter

from turtle import *

Ensuite, vous pouvez simplement utiliser Turtle, par exemple avec ce programme :

from turtle import *

forward(100)

done()

Copiez-collez le dans un fichier Python et lancez-le. Vous devriez voir apparaître une fenêtre avec un dessin du style :

turtle hello world

Erreurs

Il se peut que votre ordinateur ne soit pas bien configuré et que le programme ne marche pas.

Dans ce cas, vous pouvez utiliser Basthon pour ce chapitre.

Diriger la tortue, P1

Pour dessiner avec Turtle, on dirige une tortue graphique. C'est un peu comme un stylo.

Voici un exemple de programme :

from turtle import *

# on avance de 100 
forward(100)
# on tourne de 120 degrés vers la gauche
left(120)

forward(100)

left(120)

forward(100)

#on dit a Python qu'on a fini.
done()

Activité

Dessinez un carré avec la tortue.

Un angle droit fait 90 degrés

from turtle import *

forward(100)

left(90) #90 au lieu de 120
forward(100)

left(90)
forward(100)

#un coté supplémentaire
left(90)
forward(100)

done()

Colorier

On peut changer la couleur du trait avec

pencolor(COULEUR)

Ou COULEUR est le nom d'une couleur :

TODO liste exhaustive couleurs et le rendu

"blue"
"yellow"
"red"

Activité

Changez la couleur du trait de votre carré.

from turtle import *

#on choisit une nouvelle couleur avant de dessiner

pencolor("blue")

forward(100)

left(90) 
forward(100)

left(90)
forward(100)

left(90)
forward(100)

done()

Vous pouvez aussi colorier l'intérieur des formes que vous dessinez :

  • Utilisez fillcolor(COULEUR) pour choisir une couleur de remplissage
  • Utilisez begin_fill() pour avant de dessiner la forme
  • Utilisez end_fill() après avoir dessiné la forme.

Activité

Coloriez l'intérieur du carré.

from turtle import *

pencolor("blue")

#choix couleur remplissage
fillcolor("red")

#on commence une forme à remplir
begin_fill()

forward(100)

left(90) 
forward(100)

left(90)
forward(100)

left(90)
forward(100)
#on a terminé la forme à remplir
end_fill()

done()

Diriger la tortue, P2

Au départ la tortue est en (0, 0). On peut la faire aller à une position (X, Y) donnée en faisant :

goto(X, Y)

X et Y sont les coordonnées.

Activité

Déplacez le carré vers le haut gauche de l'écran.

Utilisez goto.

from turtle import *


# On se déplace vers le haut gauche avant de dessiner.
goto(-100, 100)

pencolor("blue")
fillcolor("red")

begin_fill()

forward(100)

left(90) 
forward(100)

left(90)
forward(100)

left(90)
forward(100)

end_fill()

done()

Vous avez sans doute remarqué que la tortue fait un trait quand elle se déplace.

On peut ne pas dessiner durant un déplacement de la tortue en utilisant

penup()

avant le déplacement. Pour recommencer à dessiner, il faut utiliser

pendown()

Activité

Supprimez le trait tout moche qui apparaît avant le carré.

from turtle import *

penup() #on lève le stylo
goto(-100, 100)
pendown() #on remet le stylo

pencolor("blue")
fillcolor("red")

begin_fill()

forward(100)

left(90) 
forward(100)

left(90)
forward(100)

left(90)
forward(100)

end_fill()

done()

On complique

Les carrés c'est pas très drôle. Essayons de dessiner un truc un peu plus sympa.

Activité : maison

Dessinez une maison, comme ça :

maison

Le toit a la couleur "brown" et les murs la couleur "red".

Remarque : vous pouvez orienter la tortue dans un angle donné en utilisant

setheading(angle)

Par exemple

setheading(0)

Essayez eventuellement de structurer votre code en blocs et de le préciser avec des commentaires.

# Un triangle rouge pour le toit
#(ici le code du toit)

# Un carré marron pour les murs
#(ici le code des murs)

Faites de l'essai erreur : dessinez les formes puis deplacez-les.

from turtle import *

#Un carré pour les murs
penup()
goto(0, 0)
pendown()

pencolor("red")
fillcolor("red")

begin_fill()

left(90)
forward(100)
left(90) 
forward(100)
left(90)
forward(100)
left(90)
forward(100)

end_fill()

# Un triangle pour le toit

penup()
goto(-125, 100)
pendown()

pencolor("brown")
fillcolor("brown")

begin_fill()

forward(150)
left(120) 
forward(150)
left(120)
forward(150)

end_fill()

done()

Quelle jolie maison ! Mais c'est un peu... sombre à l'intérieur, et on peut pas y rentrer. Il faut ajouter une porte et une fenêtre ... Allez hop, activité !

Activité : menuiseries

Essayez d'ajouter une porte et une fenêtre à la maison, comme ça :

maison

la couleur de la porte est "brown" et la couleur de la fenêtre est "white".

Essayez sérieusement pendant 15 ou 20 minutes. Notez les difficultés que vous rencontrez. Si au bout de 20 minutes vous n'y arrivez toujours pas, appelez votre prof.

Soyez méthodiques et séparez bien votre code en morceaux.

from turtle import *

#Un carré pour les murs
penup()
goto(0, 0)
pendown()

pencolor("red")
fillcolor("red")

begin_fill()

left(90)
forward(100)
left(90) 
forward(100)
left(90)
forward(100)
left(90)
forward(100)

end_fill()

# un carré pour la porte
penup()
goto(-60, 0)
pendown()

pencolor("brown")
fillcolor("brown")

begin_fill()

setheading(270) #petit ajustement de l'orientation de la tortue
left(90)
forward(30)
left(90) 
forward(60)
left(90)
forward(30)
left(90)
forward(60)

end_fill()

# Un triangle pour le toit
penup()
goto(-125, 100)
pendown()

pencolor("brown")
fillcolor("brown")

begin_fill()
setheading(0) #petit ajustement de l'orientation de la tortue
forward(150)
left(120) 
forward(150)
left(120)
forward(150)

end_fill()

# Un triangle pour la fenêtre
penup()
goto(-50, 175)
pendown()

pencolor("white")
fillcolor("white")

begin_fill()
forward(60)
left(120) 
forward(60)
left(120)
forward(60)

end_fill()

done()

Un tour de magie

Normalement, vous avez dû rencontrer quelques difficultés, parce que le code devient vraiment compliqué. Il y a beaucoup de lignes, des chiffres de partout, et toutes les lignes se ressemblent.

On va reprendre le code de l'activité de la maison sans les menuiseries, et je vais faire un tour de magie qui va simplifier le code.

Donc pour rappel, voilà le code initial :

from turtle import *

#Un carré pour les murs
penup()
goto(0, 0)
pendown()

pencolor("red")
fillcolor("red")

begin_fill()

left(90)
forward(100)
left(90) 
forward(100)
left(90)
forward(100)
left(90)
forward(100)

end_fill()

# Un triangle pour le toit

penup()
goto(-125, 100)
pendown()

pencolor("brown")
fillcolor("brown")

begin_fill()

forward(150)
left(120) 
forward(150)
left(120)
forward(150)

end_fill()

done()

Je me concentre pour accumuler du mana autour de moi...

HIPPITY HOPPITY, Le code Python est mieux écrit !

from turtle import *

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille_x)
    left(90) 
    forward(taille_y)
    left(90)
    forward(taille_x)
    left(90)
    forward(taille_y)

    end_fill()


def dessiner_triangle_equi(base_x, base_y, taille, couleur):
    penup()
    goto(base_x - taille/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille)
    left(120) 
    forward(taille)
    left(120)
    forward(taille)

    end_fill()

#un rectangle pour les murs  
dessiner_rectangle(0, 0, 100, 100, "red")
#un triangle pour le toit
dessiner_triangle_equi(0, 100, 150, "brown")

done()

Activité : menuiseries V2

Copiez-collez le code ci-dessus et vérifiez qu'il affiche bien la maison sans les menuiseries.

Modifiez le code pour ajouter les menuiseries.

from turtle import *

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille_x)
    left(90) 
    forward(taille_y)
    left(90)
    forward(taille_x)
    left(90)
    forward(taille_y)

    end_fill()


def dessiner_triangle_equi(base_x, base_y, taille, couleur):
    penup()
    goto(base_x - taille/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille)
    left(120) 
    forward(taille)
    left(120)
    forward(taille)

    end_fill()

dessiner_rectangle(0, 0, 100, 100, "red")

#un rectangle pour la porte
dessiner_rectangle(20, 0, 30, 60, "brown")

dessiner_triangle_equi(0, 100, 150, "brown")

#un triangle pour la fenetre
dessiner_triangle_equi(0, 125, 60, "white")

done()

Est-ce plus simple de faire les menuiseries maintenant qu'avant ? Essayez d'expliquer pourquoi.

NE REGARDEZ PAS LA SOLUTION, APPELEZ VOTRE PROF. LA SOLUTION EST UNIQUEMENT LA POUR L'APPRENTISSAGE EN AUTONOMIE.

A priori, c'était plus simple, parce qu'au lieu de devoir écrire des tas de lignes compliquées et floues, vous avez simplement ajouté deux lignes relativement claires.

On va voir dans la suite du chapitre qu'il y a un peu plus que ça en réalité.

Activité : analyse

Cette activité peut être faite en petit groupe.

Expliquez précisément avec vos mots ce que fait la ligne

dessiner_rectangle(0, 0, 100, 100, "red")

N'hésitez pas à expérimenter un peu en changeant les valeurs pour voir ce qu'elles changent sur le dessin.

NE REGARDEZ PAS LA SOLUTION, APPELEZ VOTRE PROF. LA SOLUTION EST UNIQUEMENT LA POUR L'APPRENTISSAGE EN AUTONOMIE.

La ligne dessine un rectangle, dont la base est en (0, 0), qui fait 100 de large et 100 de haut, de couleur rouge ("red")

Expliquez avec vos mots ce que fait la ligne

dessiner_triangle_equi(0, 100, 150, "brown")
N'hésitez pas à expérimenter un peu en changeant les valeurs pour voir ce qu'elles changent sur le dessin.

NE REGARDEZ PAS LA SOLUTION, APPELEZ VOTRE PROF. LA SOLUTION EST UNIQUEMENT LA POUR L'APPRENTISSAGE EN AUTONOMIE.

La ligne dessine un triangle equilatéral, dont la base est en (0, 100), de coté 150, de couleur marron ("brown")

Expliquez ce que fait le morceau de code suivant :

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille_x)
    left(90) 
    forward(taille_y)
    left(90)
    forward(taille_x)
    left(90)
    forward(taille_y)

    end_fill()

En particulier, à quoi servent les noms listés entre parenthèse dans cette ligne :

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):

NE REGARDEZ PAS LA SOLUTION, APPELEZ VOTRE PROF. LA SOLUTION EST UNIQUEMENT LA POUR L'APPRENTISSAGE EN AUTONOMIE.

Ce morceau de code décrit comment dessiner un rectangle.

Les noms listés dans cette ligne sont les caractéristiques du triangle : la position de sa base, sa taille, et sa couleur.

Fonctions

Vous venez juste de voir un des outils les plus importants que fournissent les langages de programmation : les fonctions.

Attention

Cette section formalise ce que vous avez probablement déjà compris dans la section précédente.

En cas de doute, n'hésitez pas à poser des questions à votre prof.

Une fonction permet de définir et nommer une nouvelle opération, potentiellement complexe:

def print_sapin(): #nom_de_l_operation
    #instructions_de_l_operation
    print("    *    ")
    print("    *    ")
    print("   ***   ")
    print("   ***   ")
    print("   ***   ")
    print("  *****  ")
    print(" ******* ")
    print("*********")
    print("   ###   ")
    print("   ###   ")

puis de l'exécuter ailleurs dans le code simplement en l'appelant par son nom :

print_sapin() #remarquez les ()

Remarque

Vous avez déjà appelé pleins de fonctions : print, input, begin_fill, ...

En utilisant les fonctions, on peut découper le code en opération séparées de manière à le garder toujours relativement simple à lire et à écrire.

Vous l'avez déjà expérimenté dans la section précédente : après le tour de magie qui a défini comment dessiner un rectangle et un triangle, c'etait beaucoup plus facile de rajouter les menuiseries à la maison.

Reprenons le code de la maison avec les menuiseries. Il est divisé en 3 blocs :

La séquence principale (le code qui n'est dans aucune fonction) :

dessiner_rectangle(0, 0, 100, 100, "red")

#un rectangle pour la porte
dessiner_rectangle(20, 0, 30, 60, "brown")

dessiner_triangle_equi(0, 100, 150, "brown")

#un triangle pour la fenetre
dessiner_triangle_equi(0, 125, 60, "white")

done()

Le code pour dessiner un rectangle :

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille_x)
    left(90) 
    forward(taille_y)
    left(90)
    forward(taille_x)
    left(90)
    forward(taille_y)

    end_fill()

Le code pour dessiner un triangle :

def dessiner_triangle_equi(base_x, base_y, taille, couleur):
    penup()
    goto(base_x - taille/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille)
    left(120) 
    forward(taille)
    left(120)
    forward(taille)

    end_fill() 

Remarquez que ces 3 blocs sont indépendants les uns des autres : on peut comprendre chacun des blocs sans avoir à aller lire en détail les autres blocs. Ca veut dire que si l'on découpe correctement notre code, on peut faire des programmes de plusieurs millions de lignes sans jamais avoir à traiter plus de quelques dizaines de lignes à la fois au maximum.

Parametres

Souvent, une fonction peut-être paramètrée, c'est à dire qu'on peut lui passer des valeurs avec lesquelles travailler au moment de l'appel :

#on dessine un rectangle,
#                  en position x = 0
#                  |  en position y = 0
#                  |  |   de largeur 100 
#                  |  |   |    de hauteur 100
#                  |  |   |    |     de couleur rouge
dessiner_rectangle(0, 0, 100, 100, "red")

Voici un autre exemple :

Les paramètres d'une fonction sont nommés dans la ligne def de la définition de la fonction. Ils peuvent ensuite être utilisés dans la fonction comme des variables.

# le dessin du rectangle a les parametres suivants : 
# base_x -> sa position x
# base_y -> sa position y
# taille_x -> sa largeur
# taille_y -> sa hauteur
# couleur 
def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y) #ici, on utilise base_x, taille_x, base_y
    pendown()
    setheading(0)

    pencolor(couleur) #ici, on utilise couleur
    fillcolor(couleur) #ici, on utilise couleur

    begin_fill()

    forward(taille_x) #ici, on utilise taille_x
    left(90) 
    forward(taille_y) #ici, on utilise taille_y
    left(90)
    forward(taille_x) #ici, on utilise taille_x
    left(90)
    forward(taille_y) #ici, on utilise taille_y

    end_fill()

TODO ajouter exercice

Valeur de retour

Une fonction peut aussi retourner une valeur que l'on peut utiliser après l'avoir appelée. C'est le cas de la fonction input que vous avez déjà utilisé :

#input retourne la saisie de l'utilisateur
#que l'on stocke ici dans la variable réponse
reponse = input("Entrez votre nom:") 

Quand on définit une fonction, on utilise return pour lui faire retourner une valeur :

def addition(a, b):
    return a+b

Activité

Copiez-collez la fonction ci-dessus dans un fichier à part, puis utilisez-là pour écrire un programme qui :

  • demande deux nombres à l'utilisateur
  • appelle la fonction pour les additionner
  • affiche le résultat
def addition(a, b):
    return a+b

x = input("entrez x:")
y = input("entrez y:")

res = addition(x, y)

print("x + y =", res)

Quand l'instruction return s'execute, l'execution de la fonction est interrompue.

Activité

Copiez-collez le code suivant dans un fichier à part, et executez-le. Que remarquez vous ? Expliquez.

def mystere():
    print("cette fonction")
    print("fait des choses bizarres")
    print("puis retourne 1")
    return 1
    print("et fait d'autres choses bizarres")

mystere()

On remarque que la dernière ligne de la fonction

print("et fait d'autres choses bizarres")

ne s'est pas exécutée.

C'est parce que juste avant le return s'est exécuté, et donc que l'execution de la fonction s'est arrêtée là.

Exercices

Ecrire une fonction compter qui prends un nombre n en paramètres, et affiche les n + 1 premiers entiers (zéro inclus). puis l'utiliser pour écrire un programme qui demande un nombre m à l'utilisateur, et appelle compter(m).

m ? 5
0
1
2
3
4
5
  • Commencez par déclarer et écrire la fonction
  • N'oubliez pas d'indenter son corps
  • A L'intérieur de la fonction, vous devez utiliser une boucle while.

Voici un programme à compléter :

def compter(n):
    ##Ici le code pour afficher les entiers de 0 à n inclus

m = int(input("m ? "))
##Ici le code pour appeler compter(m)
def compter(n):
    i = 0
    while i <= n:
        print(i)
        i = i + 1

m = int(input("m ? "))
compter(m)

Plusieurs maisons

Activité : fonction maison

Reprenez le code de l'activité précédente. Déplacez ces lignes :

dessiner_rectangle(0, 0, 100, 100, "red")

dessiner_rectangle(20, 0, 30, 60, "brown")

dessiner_triangle_equi(0, 100, 150, "brown")

dessiner_triangle_equi(0, 125, 60, "white")

dans une fonction dessiner_maison, puis appelez-là pour dessiner la maison.

from turtle import *

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille_x)
    left(90) 
    forward(taille_y)
    left(90)
    forward(taille_x)
    left(90)
    forward(taille_y)

    end_fill()


def dessiner_triangle_equi(base_x, base_y, taille, couleur):
    penup()
    goto(base_x - taille/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille)
    left(120) 
    forward(taille)
    left(120)
    forward(taille)

    end_fill()

#on crée une fonction dessiner_maison qui dessine la maison
def dessiner_maison():
    dessiner_rectangle(0, 0, 100, 100, "red")
    dessiner_rectangle(20, 0, 30, 60, "brown")
    dessiner_triangle_equi(0, 100, 150, "brown")
    dessiner_triangle_equi(0, 125, 60, "white")

#puis on l'appelle
dessiner_maison()

done()

Activité : plusieurs maisons

Modifiez la fonction dessiner_maison pour lui ajouter deux paramètres x et y, qui permettent de choisir où dessiner la maison.

Appelez-là ensuite plusieurs fois pour dessiner plusieurs maisons à diverses positions.

TODO ajouter visuel ?

Remarque : vous pouvez changer la vitesse de la tortue en utilisant speed( VITESSE ), par exemple speed(5).

Il faut modifier les coordonnées des formes que vous dessinez en ajoutant les coordonnées de la maison :

dessiner_rectangle(0, 0, 100, 100, "red")

devient

dessiner_rectangle(0 + x, 0 + y, 100, 100, "red")
from turtle import *

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur):
    penup()
    goto(base_x - taille_x/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille_x)
    left(90) 
    forward(taille_y)
    left(90)
    forward(taille_x)
    left(90)
    forward(taille_y)

    end_fill()


def dessiner_triangle_equi(base_x, base_y, taille, couleur):
    penup()
    goto(base_x - taille/2, base_y)
    pendown()
    setheading(0)

    pencolor(couleur)
    fillcolor(couleur)

    begin_fill()

    forward(taille)
    left(120) 
    forward(taille)
    left(120)
    forward(taille)

    end_fill()

#on ajoute les parametres x y    
def dessiner_maison(x, y): 
    dessiner_rectangle(x, y, 100, 100, "red") #on les utilise
    dessiner_rectangle(20 + x, y, 30, 60, "brown")
    dessiner_triangle_equi(x, 100 + y, 150, "brown")
    dessiner_triangle_equi(x, 125 + y, 60, "white")

speed(100)


#on dessine plusieurs maisons
dessiner_maison(0, 0) 

dessiner_maison(200, 0)

dessiner_maison(-100, -200)

dessiner_maison(100, -120)

dessiner_maison(300, -160)

done()

Erreurs et trace d'appel

Les fonctions peuvent parfois rendre les erreurs plus compliquées à comprendre et à corriger.

Observez ce programme Python :

def addition(a, b):
    return a + b

print("10 et 20 font", addition(10, 20))
print("20 et 30 font", addition(20, "30"))

Si on le lance, on a l'erreur suivante :

Traceback (most recent call last):
  File "<basthon-input-1-945c13bc68f9>", line 5, in <module>
    print("20 et 30 font", addition(20, "30"))
                           ^^^^^^^^^^^^^^^^^^
  File "<basthon-input-1-945c13bc68f9>", line 2, in addition
    return a + b
           ~~^~~
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Analysons un peu cet erreur en partant du bas.

La première ligne

TypeError: unsupported operand type(s) for +: 'int' and 'str'

nous dit que l'on a essayé de faire un + entre un entier et une chaîne de caractères. Python ne sait pas faire ça donc il lève une erreur.

Les lignes suivantes :

  File "<basthon-input-1-945c13bc68f9>", line 2, in addition
    return a + b
           ~~^~~

Nous disent quel morceau de code à déclenché l'erreur : ligne 2, dans la fonction addition.

Or, ici, on a un problème : la fonction addition est appelée deux fois dans le code :

print("10 et 20 font", addition(10, 20))
print("20 et 30 font", addition(20, "30"))

Comment savoir quel appel a déclenché l'erreur ? C'est simple, il suffit de regarder les lignes suivantes dans l'erreur :

  File "<basthon-input-1-945c13bc68f9>", line 5, in <module>
    print("20 et 30 font", addition(20, "30"))
                           ^^^^^^^^^^^^^^^^^^

Maintenant, on sait que c'est l'appel addition(20, "30"), ligne 5, qui a conduit à l'erreur, et on peut corriger.

Docstring

On peut documenter ce que fait une fonction à l'aide d'une docstring, une chaine de caractère spéciale entre trois paires de ' ou " placée juste en dessous de la ligne def, comme suit :

def dessiner_rectangle(base_x, base_y, taille_x, taille_y, couleur)
    """dessine un rectangle
    base_x, base_y : coordonnées du centre du côté bas du rectangle
    taille_x : largeur du rectangle
    taille_y : hauteur du rectangle
    couleur : la couleur de remplissage du rectangle
    """
    # [...] le code ici

On reviendra plus tard sur les docstrings. Je vous conseille d'en ajouter dans votre code pour expliquer vos fonction quand c'est nécessaire.

TP (a rendre) : un dessin complet

TP : dessin

Utilisez Turtle et les fonctions pour créer un dessin "complet", de ce style :

dessin turtle

Bien entendu, vous n'êtes pas limités à un dessin de montagne. Vous pouvez choisir tout autre chose.

CE TP EST A RENDRE, ET POURRA ETRE NOTE. Il vous est demandé de remplir les critères suivants :

  • Avoir au moins 3 objets différents qui apparaissent au moins 2 fois dans le dessin (par exemple, une maison, un sapin et une montagne)
  • Votre dessin doit être significativement différent des autres dessins des membres de la classe : changer une ou deux valeur n'est pas suffisant !
  • Le code doit être structuré avec des fonctions comme vu dans le chapitre.
  • Vous devez être capables d'expliquer et modifier votre code si on vous le demande. Vous pouvez donc reprendre le code de camarades, a condition de le comprendre.
  • Ne pas remplir les critères est acceptable si vous avez demandé régulièrement de l'aide et fait des efforts.

Accrochez-vous et ne vous laissez pas abattre. N'hésitez pas à expérimenter ou demander de l'aide.

Si vous êtes arrivés au bout du TP et que vous remplissez les critères, vous pouvez améliorer encore votre dessin, ou éventuellement vous frotter à une des extensions, ou même aux deux.

TP : extension 1, aléatoire

Ajouter la ligne:

from random import randint

en haut d'un fichier Python vous donne accès à la fonction randint :

# randint retourne un nombre entier 
# aléatoire compris entre mini (inclus) et maxi (inclus)
nombre = randint(mini, maxi) 

# example :
nombre = randint(10, 20)
print(nombre) #affiche un nombre choisi aléatoirement entre 10 et 20

Utilisez cette fonction pour ajouter des variations aléatoires dans vos objets.

Voici par exemple un lot de sapins randomisés, c'est à dire avec des variations aléatoires :

turtle_tp_rng

TP : extension 2 (difficile), perspective

Vous pouvez donner une illusion que votre dessin est en 3 dimensions, c'est la perspective.

La perspective fonctionne avec deux critères :

  • Les objets plus loins apparaissent plus petit, et plus ils sont proches
  • Un sol plat, plus on s'éloigne, et plus les objets loin apparaissent légèrement plus "haut" dans l'image.

Voici par exemple une forêt de sapins en perspective :

turtle_tp_3d

Exercices

Exercice

Observez le code bout de code suivant :

def chats(n):
    print("miaou ! " * n)

A quoi sert le mot clé def ?

Il sert à définir une fonction.

Quel est le nom de la fonction définie dans ce code ?

Le nom de la fonction est chats

Combien d'arguments cette fonction a-t-elle ? Quels sont leurs noms ?

Cette fonction a un seul argument, n. La liste des arguments d'une fonction est définie entre les parenthèses.

A quoi servent les arguments d'une fonction ?

Les arguments d'une fonction servent à donner des valeurs au corps de la fonction.

Exercice

Observez le code de la fonction suivante :

def addition(a, b):
    return a + b

Combien d'arguments cette fonction admet-elle ? Quels sont leurs noms ?

cette fonction admet 2 arguments : a et b.

A quoi sert le mot clé return

Le mot clé return sert à dire quelle valeur la fonction retourne

Que se passe t'il quand on appelle une fonction qui retourne une valeur dans une expression ?

Par exemple

a = addition(10, 20)
print(addition(a, b))

L'appel de la fonction est "remplacé" par la valeur que la fonction retourne dans l'expression.

a = addition(10, 20)
## Ici, a vaut 30
## affiche 30 a l'écran
print(addition(a, b))

Exploration

Ecrivez une fonction compter qui prends un entier n >= 0 en paramètre, et affiche les nombres entiers de n à 0 par ordre décroissant.
La fonction doit absolument faire moins de 10 lignes, et vous n'avez pas le droit de déclarer d'autre fonction. Dans le corps de la fonction, la seule structure de contrôle autorisée est un if, sans else, ni elif.

compter(5)

affiche

5
4
3
2
1
0

Vous savez qu'on peut appeler une fonction depuis le corps d'une autre fonction.

Mais saviez-vous qu'une fonction peut s'appeler elle-même ?

Executez le code suivant :

def afficher():
    print("YEAAAAAAAH!")
    afficher()

afficher()

Observez bien sa sortie.

Essayez d'executer le code suivant. Observez bien sa sortie. Comment le modifier pour qu'il s'arrête à 0 ?

def afficher(m):
    print(m)
    afficher(m-1)

afficher(10)
def compter(n):
    print(n)
    # On utilise la valeur de n comme un critère d'arrêt.
    if n > 0:
        compter(n-1)

Reprenons le code de la fonction de l'exercice précédent.

def compter(n):
    print(n)
    # On utilise la valeur de n comme un critère d'arrêt.
    if n > 0:
        compter(n-1)

Essayez d'appeler compter(n), avec une très grande valeur de n. Par exemple, 1000000. Laissez tourner un peu le programme jusqu'a qu'il s'arrête. Que se passe-t-il ?

A votre avis, pourquoi ?

Relisez bien la partie du cours sur les traces d'appel.

A votre avis, où est stockée la pile d'appel ?

Après un certain temps, le programme lève une erreur.

RecursionError: maximum recursion depth exceeded while calling a Python object

Une erreur de récursion est survient quand la pile d'appel grandit trop.

La pile d'appel est stockée dans la mémoire de l'ordinateur. Comme la mémoire n'est pas infinie, les systèmes d'exploitation et les interprêteurs limitent la taille de la pile d'appel, et donc le nombre d'appel imbriqués qui peuvent être fait.

Si on fait trop d'appel, le programme est alors terminé en erreur. C'est ce qu'on appelle un dépassement de pile.

Voici un code incomplet

## vous pouvez définir d'autres fonctions.

def ap(o, a, b):
    return o(a,b)

def afficher_addition(a, b):
    m = ## remplacez ce commentaire par qqch
    print(ap(m, a, b))

afficher_addition(10, 20)

Sans modifier la fonction ap, et en ne modifiant la fonction afficher_addition qu'à l'endroit indiqué en commentaire, faites en sorte que ce programme affiche EXACTEMENT

30

Vous pouvez définir des fonctions supplémentaires si besoin.

Il y a deux méthodes.

L'une demande de bien analyser ce que fait la fonction ap.

L'autre est "très simple" mais conduit à un code très peu clair.

Executez le code suivant :

def afficher(m):
    print(m)

af = afficher

af(m)

Peut-être celà vous aidera-t-il...

En Python, on peut passer une fonction comme argument d'une autre fonction.

La fonction ap prends une fonction o et deux arguments supplémentaires a et b en entrée, et retourne le résultat de l'appel o(a,b)

On va alors créer une fonction addition et la passer en argument o à ap dans afficher_addition.

def addition(a, b):
    return a + b

def ap(o, a, b):
    return o(a,b)

def afficher_addition(a, b):
    m = addition
    print(ap(m, a, b))

afficher_addition(10, 20)

En Python comme dans beaucoup d'autres langages, les fonction sont des valeurs, et peuvent être donc stockées dans des variables et passées en arguments à d'autres fonctions. Nous verrons bien plus loin dans ce cours que cette propriété permet de faire du code extrêmement concis et flexible, et fait partie d'un concept plus large qu'on appelle la généricité.

En Python, on peut redéfinir une fonction. C'est à dire définir une fonction avec le même nom qu'une fonction déjà existante. C'est alors la définition vue en dernier qui sera utilisée. On aurait donc pu redéfinir afficher_addition juste avant son appel :

## vous pouvez définir d'autres fonctions.

def ap(o, a, b):
    return o(a,b)

def afficher_addition(a, b):
    m = 0 ##ici, on met une valeur juste pour que l'interpréteur ne râle pas
    print(ap(m, a, b))

def afficher_addition(a, b):
    print(a + b)

afficher_addition(10, 20)

Redéfinir des fonctions est une en Python une mauvaise pratique, parce qu'avoir deux définitions pour une même fonction peut induire en erreur.

Implémentez une fonction addition_partielle(n), de manière à ce que l'appel suivant :

addition_partielle(10)(20)
Retourne 30.

Vous pouvez définir une fonction dans une fonction !

def afficheur(message):
    def afficher_message():
        print(message)
    return afficher_message

afm = afficheur("Hello, World !")
afm()

On va créer la fonction addition_partielle(n) de manière à ce qu'elle retourne une fonction ajout(m) qui retourne n + m.

def addition_partielle(n):
    def ajout(m):
        return n+m
    return ajout

print(addition_partielle(10)(20))

Cette capacité à créer des fonction dynamiquement, c'est à dire pendant l'execution du programme, combinée au fait que l'on peut utiliser une fonction en tant que valeur, permet dans certains cas de faire des programmes très facilement extensibles et quand même très concis !

Les essentiels

Au programme

Une fonction se déclare et se définit avec le mot clé def et la syntaxe suivante :

def nom_fonction(arg1, arg2):
    #ici des instructions
    return ar11 + arg2 #pas obligatoire

arg1 et arg2 sont les arguments ou paramètres de la fonction. Une fonction peut avoir zéro, un ou plusieurs arguments, séparés par des virgules.

On appelle une fonction avec la syntaxe suivante :

nom_fonction(val1, val2)

val1 et val2 sont des valeurs.

l'instruction return permet à la fonction de retourner une valeur, qu'on peut alors utiliser dans une expression.

a = nom_fonction(1,2) + 3

Une fonction peut appeler une autre fonction :

def fonction1():
    pass

def fonction2():
    fonction1()

Si on veut appeler une fonction, il faut qu'elle ait été définie plus haut dans le code.

Quand on appelle une fonction, c'est l'ordre des arguments qui importe, pas leurs noms.

def fonction(a,b,c):
    return (a + b) * c

a = 1
b = 100 
c = 1

r = fonction(a, c, b) #r vaut (1 + 1) *100

  1. Mesuré avec scc (https://github.com/boyter/scc) le 13/09/2022 à partir des dépôts GitHub ou GitLab, sur la branche principale. 

  2. C'est un langage pour automatiser la compilation. les fichiers de configuration pour pouvoir compiler LLVM font 73000 lignes au total. 

  3. http://lotrproject.com/statistics/books/wordscount