objectifs de cette section¶
dans cette partir consacrée aux fonctions, nous allons approfondir le sujet, et notamment creuser
le passage de paramètres
les fonctions comme citoyen de niveau 1
la visibilité des variables
mais pour commencer, voici quelques rappels et généralités sur ce thème
pour réutiliser du code en python¶
DRY = don’t repeat yourself : cut’n paste is evil
fonctions | pas d’état après exécution |
modules | garde l’état, une seule instance par programme |
classes | instances multiples, chacune garde l’état, héritage |
comment définir une fonction ?¶
syntaxe¶
def name(arg1, arg2, .. argN):
<statement>
return <value> # peut apparaitre n’importe oùdefcrée un objet fonction, et l’assigne dans la variablenameassimilable à une simple affectation
name = ..tout est objet en Python, la fonction aussi !
bien entendu, le code n’est évalué que quand la fonction est appelée
# pas du tout usuel, mais pour bien comprendre :
# on peut parfaitement écrire ceci
if test:
def func():
...
else:
def func():
...
func() # exécute une version qui dépend du testduck typing¶
il n’y a pas de typage statique en Python: on ne sait pas de quel type doivent être x et y, et tant que ça fait du sens au moment de l’exécution, le code est correct ! on appelle ça aussi le duck typing
# on va pouvoir appeler cette fonction ...
def times(x, y):
return x * y# ... aveec deux entiers
times(2, 3)6# ou avec deux floats
times(1.6, 9)14.4# ... et même comme ceci !
times('-spam-', 4)'-spam--spam--spam--spam-'type hints¶
si on le souhaite, on peut indiquer le type des paramètres attendus et du résultat
def type_hints_1(x: int, y: float) -> str:
"""
pour des types simples
"""
...# un peu plus compliqué (depuis la 3.9)
def type_hints_2(x: tuple[int, str, bool],
y: dict[str, list[int]]) -> None:
...il faut savoir que c’est totalement optionnel et que ça ne modifie pas le comportement du code
à quoi ça sert du coup, me direz-vous ? eh bien surtout à deux choses
d’abord et surtout à améliorer la documentation et faciliter l’usage de la librairie en question
ensuite et de manière plus optionnelle, on peut utiliser un outil externe appelé type checker comme par exemple mypy qui, lui, va utiliser cette information pour détecter les incohérences entre types attendus et appels effectifs; par contre pour que cette approche soit effective il faut en général que les type hints soient généralisés dans le code...
on reparle plus en profondeur des type hints ici
un objet comme un autre¶
voici à nouveau un exemple biscornu; ce n’est évidemment pas recommandé, mais pour bien comprendre, sachez que c’est légal de faire ceci:
# la fonction est un objet
times<function __main__.times(x, y)># pas du tout recommandé, mais
# on peut affecter cet objet à une autre variable !
foo = times
# et donc maintenant foo désigne une fonction, je peux donc l'appeler
foo(3, 14)42# et redéfinir `times` pour faire + à la place de * !
def times(x, y):
# ne vraiment pas faire ça en vrai !!
return x + y# maintenant times fait une addition !
times(3, 4)7# et foo fait bien la multiplication
foo(3, 4)12l’instuction return¶
un appel de fonction est une expression
le résultat de cette expression est spécifié dans le corps de la fonction par l’instruction
returnle premier
returnrencontré provoque la fin de l’exécution de la fonctionsi la fonction se termine sans rencontrer un
returnon retourne
None-Noneest un mot-clé de Python, qui désigne un objet unique (singleton)
les docstrings¶
où documenter ?¶
si, dans un objet de type fonction, classe ou module, la première instruction est une chaîne de caractères
alors c’est le docstring de cet objet, qui constitue sa documentation
l’idée étant naturellement de pouvoir maintenir en même temps le code et la doc, plutôt que d’avoir la doc dans un système séparé qui du coup n’est jamais à jour
def hyperbolic(x, y):
"""
computes xˆ2 - y^2
"""
return x**2 - y**2c’est ce qui est utilisé pour afficher la doc avec help(objet)
help(hyperbolic)Help on function hyperbolic in module __main__:
hyperbolic(x, y)
computes xˆ2 - y^2
comment documenter ?¶
c’est une bonne habitude de toujours documenter !
le plus souvent multiligne - avec
"""comme danshyperbolicpas utile de répéter le nom de l’objet, qui est extrait automatiquement de la signature (DRY)
la première ligne décrit brièvement ce que fait l’objet
la deuxième ligne est vide
les lignes suivantes décrivent l’objet avec plus de détails - notamment les paramètres, etc...
format et exemple¶
historiquement le contenu du docstring était basé sur ReST, mais jugé peu lisible
du coup pas forcément hyper-bien standardisé - plusieurs conventions existent dans le code disponible
recommande personnellement:
styles
googleet/ounumpyvoir doc ici
PEP8¶
d’après la PEP8, on doit
utiliser une (ou plusieurs) ligne(s) vide(s) pour séparer les fonctions, classes et les grands blocs d’instructions
rappel: les noms de fonctions sont en minuscules
rappel: espace autour des opérateurs et après les virgules
a = f(1, 2) + g(3, 4)rappel: des espaces autour de l’
=, et pas après la fonctionf(1, 2)et non pascar un espace en tropf (1, 2)a = f(1, 2)et non pascar manque des espacesa=f(1, 2)
passage d’arguments et références partagées¶
le passage de paramètres se fait par référence
ce qui crée donc des références partagées
et donc une fonction peut modifier les objets qu’on lui passe
# nous allons illustrer ce mécanisme de
# références partagées grâce à pythontutor.com
%load_ext ipythontutor%%ipythontutor curInstr=2 width=1000 height=400
# les arguments d'une fonction sont toujours passés par référence
liste = [1, 2, 3]
def mess_with(reference):
reference[1] = 100
mess_with(liste)
print(liste)%%ipythontutor width=800 height=450 heapPrimitives=true
def mess_with2(a, b):
a = 3 # ceci n'aura pas de conséquence sur A
b[0] = 'boom' # ceci va changer B
A = 1 # immutable ne peut pas être modifiée
B = [10, 20] # mutable, l'objet liste est modifié par
# changer() par une modification in-place
mess_with2(A, B)
print(A)
print(B)