|
|
Cette page (re)pose ici quelques fondamentaux de Python utiles pour faire des graphiques, notamment la notion de **variable**, de **conteneur** (listes, tuples...) et de **fonctions** et leurs arguments (de position, nommés avec valeur par défaut, en nombre indéfini...).
|
|
|
On abordera aussi d'autres notions, comme la portée des variables.
|
|
|
Normalement, on commence par les bases et on évoque ensuite des propriétés plus complexes ; dès que ça devient trop théorique, attendez d'en avoir besoin et jouez sur la complémentarité TDs/théorie, ainsi que sur la complémentarité avec les autres sources d'info !
|
|
|
|
|
|
:books: Pour commencer, vous pouvez tester le [tuto officiel :three:.:one: :zero:](https://docs.python.org/3.10/tutorial/index.html) ou trouver un cours en ligne qui vous correspondra ; vous pouvez consulter [ma sélection](Tutographie).
|
|
|
|
|
|
:bulb: Pour mieux comprendre, n'hésitez pas à exécuter des petits bouts de code sur le site : :bulb: [PYTHON TUTOR](http://pythontutor.com) :bulb:
|
|
|
|
|
|
PYTHON TUTOR est un outil pédagogique, qui exécute du code en ligne, pas à pas, en représentant graphiquement tous les **objets** créés (chiffres, chaînes de caractères et conteneurs (listes, tuples...) contenant les objets élémentaires), et ceci dans leurs espaces de nommage respectifs, avec les liens avec les noms de variables.
|
|
|
Cela vous permettra en particulier de voir comment la modification d'une variable impacte (ou pas) certaines autres.
|
|
|
Il est limité par le nombre d'instructions, et ne permet pas l'import de modules (en particulier, pas de matplotlib...)
|
|
|
|
|
|
# A :snake: Bases pour débuter
|
|
|
|
|
|
### A1) découverte des instructions de base dans la console
|
|
|
En séance, on fera les premiers pas dans la console. Si vous préférez, ou si vous préférez garder une trace, vous pourrez créer un fichier *.py ou un notebook (attention aux petites différences).
|
|
|
|
|
|
#### Premier contact : variables et listes
|
|
|
|
|
|
En python, on ne déclare pas les variables ; par contre, elles ont bien un **type**.
|
|
|
Python se débrouille pour assigner le bon type (:duck: "duck typing" : si ça ressemble à un canard, ben c'est un canard... ; 5 est un entier, 3.2 un flottant et "bonjour" une chaîne de caractères : c'est évident pour vous, ça l'est pour Python).
|
|
|
|
|
|
**Règles de nommage** : peu de choses sont interdites en Python, mais un nom de variable doit commencer par une lettre (:robot: 6Po n'est pas un nom valide) et bien sûr certains noms sont réservés : **if**, **for**...
|
|
|
Python est un langage sensible à la "casse", c'est à dire que les lettres en minuscule sont différentes des lettres en majuscules...
|
|
|
Donc ma_variable, MA_VARIABLE, Ma_Variable et ma_Variable sont des noms différents...
|
|
|
|
|
|
:warning: certains noms de méthode ou de classe sont cependant utilisables, ce qui pose problème car en nommant votre variable **max** ou **list** vous surchargez la méthode ou la classe correspondante, qui du coup n'est plus accessible ! Normalement vous devriez vous en rendre compte avec la coloration syntaxique : si le mot change de couleur c'est qu'il est reconnu par Python...
|
|
|
|
|
|
**Principales conventions** :
|
|
|
rien n'est obligatoire en Python, mais il est fortement recommandé de respecter les conventions suivantes pour faciliter la lecture d'autres codes, notamment :
|
|
|
* noms de **variables** : :snake: en snake-case, les mots sont en minuscules et reliés par des underscore qui le font ressembler à un animal articulé : ma_variable_peut_avoir_un_nom_assez_long
|
|
|
* noms de **classes** : :camel: en CapWords, autrement dit UpperCaseCamelCase : un nom qui commence par une capitale, ou plusieurs mots collés avec chacun une capitale, ce qui crée des bosses dans le nom : MaClasseQuiFaitUnTrucSympa. Les instances de classe seront écrites en snake-case : instance_de_la_classe_sympa = MaClasseQuiFaitUnTrucSympa()
|
|
|
* noms de **constantes** : en snake-case majuscule ("SCREAMING_SNAKE_CASE") : COEFFICIENT, CODE_LACUNE. :warning: ce n'est qu'une convention donc en pratique vous pouvez modifier vos constantes en cours de programme...
|
|
|
* (il y en a d'autres, voir les [PEP8](https://www.python.org/dev/peps/pep-0008/], [éventuellement en chanson...]https://youtu.be/hgI0p1zf31k))
|
|
|
|
|
|
|
|
|
```python
|
|
|
a = 10 # assignation, sans préciser le type, Python comprendra : "duck typing"
|
|
|
type(a) # réponse = int (typage réalisé par Python en fonction de l'entrée)
|
|
|
```
|
|
|
Python comprend quel type est affecté à un nom de variable, on parle de "duck typing" : quand vous entendez un canard vous savez que c'est un canard ...
|
|
|
Pour travailler avec une collection d'éléments, il existe plusieurs structures en Python.
|
|
|
On va utiliser dans un premier temps le **conteneur** le plus "basique", la **liste**.
|
|
|
```python
|
|
|
une_liste_vide = list() # on crée une _instance_ de la classe _liste_
|
|
|
une_autre_liste_vide = [] # les CROCHETS suffisent à signaler à Python que vous avez créé une liste
|
|
|
ma_liste = [1, 2, 5, 10, 50] # une liste, définie comme telle par ses CROCHETS, avec ses éléments
|
|
|
|
|
|
ma_liste[1] # une liste est INDEXABLE* ; le résultat peut vous surprendre : Python commence l'indexation à ZERO, eh oui
|
|
|
|
|
|
12 in ma_liste # quel est le type de la réponse renvoyée ?
|
|
|
ma_liste[2]= 12 # je peux changer un élément de la liste ; c'est possible car une liste est MUTABLE*
|
|
|
ma_liste.append(100) # j'ajoute un élément à la liste ; c'est possible car une liste est MUTABLE*
|
|
|
ma_liste_fourre_tout = [1, 2, 5, 10, "camembert" , 50.5, (8,12), ma_liste]
|
|
|
```
|
|
|
Le conteneur liste peut être un fourre-tout... mais ce n'est pas recommandé !
|
|
|
Pour du calcul matriciel, on préférera la structure de numpy.array (objet array du module numpy).
|
|
|
On verra d'autres conteneurs par la suite : tuples (à faire) et dictionnaires : A4.
|
|
|
|
|
|
En Python, pas de 'begin" ni de 'end' ni d'accolades pour délimiter les blocs : on définit des **blocs de code** par la seule indentation : le bloc est annoncé par ":" et les lignes appartenant au bloc sont indentées de 4 espaces (ou une tabulation) par rapport à la ligne qui annonce le bloc.
|
|
|
C'est une habitude à prendre, et votre environnement de développement vous aidera beaucoup (sous PyCharm : on peut utiliser indifféremment 4 espaces ou une tabulation, et pour réajuster des blocs il y a Edit/Indent ou /Unindent).
|
|
|
```python
|
|
|
for element in ma_liste: # une liste est ITERABLE* ; tous les conteneurs sont itérables
|
|
|
print(element) # indentation de 4 espaces, on est dans le bloc de code
|
|
|
print("fini") # l'indentation a cessé, on a quitté le bloc
|
|
|
```
|
|
|
|
|
|
### A2) Pièges et astuces des boucles
|
|
|
|
|
|
```python
|
|
|
ma_liste = [1, 2, 5, 10, 50] # une liste d'entiers
|
|
|
|
|
|
for element in ma_liste: # une liste est ITERABLE* ; tous les conteneurs sont itérables
|
|
|
print(element) # indentation de 4 espaces, on est dans le bloc de code
|
|
|
element = element + 2
|
|
|
print ("après modification ",element)
|
|
|
print("fini") # l'indentation a cessé, on a quitté le bloc
|
|
|
print (ma_liste)
|
|
|
```
|
|
|
Le programme se déroule comme suit :
|
|
|
1
|
|
|
après modification 3
|
|
|
2
|
|
|
après modification 4
|
|
|
5
|
|
|
après modification 7
|
|
|
10
|
|
|
après modification 12
|
|
|
50
|
|
|
après modification 52
|
|
|
|
|
|
Et pourtant, à la fin print(ma_liste) montre que la liste est inchangée !
|
|
|
La ligne " element = element + 2" a bien modifié l'élément extrait de la liste, mais n'a pas modifié la liste...)
|
|
|
|
|
|
On peut vérifier sur [cette capture d'écran Python Tutor](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/blob/master/python_tutor_modif_elements_liste.png) que au cours de la 2e itération la variable element qui valait 2 (2e élément de la liste) est bien passée de 2 à 4, mais que cela n'a pas du tout modifié la liste originale.
|
|
|
|
|
|
Une manière de transformer la liste en ajoutant deux consiste à recréer une liste :
|
|
|
```python
|
|
|
ma_liste = [1, 2, 5, 10, 50] # une liste d'entiers
|
|
|
ma_liste_modifiée = []
|
|
|
for element in ma_liste:
|
|
|
print(element)
|
|
|
ma_liste_modifiée.append(element + 2) # j'ajoute les éléments un à un à la nouvelle liste
|
|
|
|
|
|
print("fini")
|
|
|
ma_liste = ma_liste_modifiée # je fais pointer ma_liste vers la nouvelle liste
|
|
|
```
|
|
|
...ce qui peut s'écrire de manière plus compacte par une "comprehension list" (liste en intension) :
|
|
|
|
|
|
```python
|
|
|
ma_liste = [1, 2, 5, 10, 50] # une liste d'entiers
|
|
|
ma_liste = [element + 2 for element in ma_liste ]
|
|
|
```
|
|
|
Si je veux parcourir une liste avec le rang : *enumerate*
|
|
|
```python
|
|
|
ma_liste = [1, 2, 5, 10, 50] # une liste d'entiers
|
|
|
|
|
|
for rang, element in enumerate(ma_liste):
|
|
|
print("L'élement de rang ",rang, " est :", element)
|
|
|
```
|
|
|
L'élement de rang 0 est : 1
|
|
|
L'élement de rang 1 est : 2
|
|
|
L'élement de rang 2 est : 5
|
|
|
L'élement de rang 3 est : 10
|
|
|
L'élement de rang 4 est : 50
|
|
|
|
|
|
Hé oui, les indices commencent à zéro !
|
|
|
|
|
|
Parcourons maintenant deux listes en parallèle, en prenant à chaque itération un élément de même rang, comme une fermeture éclair parcourt deux bords crantés : *zip*
|
|
|
|
|
|
|
|
|
```python
|
|
|
ma_liste_numeros = [1, 2, 5, 10, 50] # une liste d'entiers
|
|
|
ma_liste_mots = [ "un", "deux", "cinq", "dix", "cinquante" ]
|
|
|
for numero, mot in zip(ma_liste_numeros, ma_liste_mots):
|
|
|
print("Le numéro ",numero, " s'écrit :", mot)
|
|
|
```
|
|
|
|
|
|
Le numéro 1 s'écrit : un
|
|
|
Le numéro 2 s'écrit : deux
|
|
|
Le numéro 5 s'écrit : cinq
|
|
|
Le numéro 10 s'écrit : dix
|
|
|
Le numéro 50 s'écrit : cinquante
|
|
|
|
|
|
### A3) Les fonctions
|
|
|
La fonction est un objet comme les autres (ou presque) !
|
|
|
Les fonctions et méthodes (fonctions définies dans une classe) peuvent être appelées pour exécuter des instructions et retourner un ou plusieurs objets. Techniquement, ce sont donc des « callables » (http://sametmax.com/quest-ce-quun-callable-en-python/ ).
|
|
|
|
|
|
#### A.3.1. Rappels, fonction et **paramètres** de la fonction
|
|
|
|
|
|
Une fonction (ou une méthode) est définie par le mot-clé **def**, un nom de fonction suivi d'une liste de noms de variables entre parenthèses, puis un bloc de code introduit par les "**:**", qui commence de préférence par une docstring suivie d'une ligne blanche.
|
|
|
|
|
|
``` python
|
|
|
def ma_fonction(param1, param2, param3):
|
|
|
""" doctring pour décrire ce que fait ma_fonction """
|
|
|
|
|
|
```
|
|
|
|
|
|
Cas particulier des **méthodes**, avec le mot-clé **self**pour renvoyer à l'instance courante (attention, tout autre mot fait l'affaire, dont si vous l'oubliez le premier nom fourni fera office de "self" !).
|
|
|
On remarque que dans le cas de la fonction **`__init__`** qui initialise l'instance, les **attributs** n'ont pas forcément le même nom que les paramètres, et peuvent d'ailleurs être en nombre différent !
|
|
|
|
|
|
``` python
|
|
|
class MonObjet:
|
|
|
""" Cette classe est là car il en faut une ; un docstring au format google
|
|
|
|
|
|
Attributes:
|
|
|
class_attribute (str): (class attribute) parce que j'ai envie de mettre un argument de classe
|
|
|
param1 (float): un attribut d'instance
|
|
|
somme (float): somme de deux valeurs fournies dans le __init__
|
|
|
"""
|
|
|
class_attribute = "la classe, cet attribut"
|
|
|
def __init__ (param1, param2, param3):
|
|
|
self.param1 = param1
|
|
|
self.somme = param2 + param3
|
|
|
def ma_methode(self, a, b):ab:
|
|
|
""" docstring au format PEP : J'ajoute a et b à self.somme.
|
|
|
:param a : un truc à ajouter
|
|
|
:type a : entier
|
|
|
:param b : un autre truc à ajouter
|
|
|
:type a : flottant ou entier
|
|
|
:return: somme a + b
|
|
|
:rtype: flottant
|
|
|
"""
|
|
|
|
|
|
aplusb = a + b
|
|
|
self.somme += aplusb
|
|
|
return aplusb
|
|
|
|
|
|
```
|
|
|
|
|
|
On peut remplacer **self** par **this**, comme en java ; ce n'est pas du tout recommandé, mais on insiste sur l'importance de ne pas l'oublier !
|
|
|
``` python
|
|
|
def ma_methode(this, a, b):
|
|
|
""" petite description de ma fonction : elle calcule a + b, et l'ajoute à l'attribut somme ; elle retourne aussi a+b """
|
|
|
aplusb = a + b
|
|
|
this.somme += aplusb
|
|
|
return aplusb
|
|
|
```
|
|
|
|
|
|
machin = MonObjet(4,5,6)
|
|
|
|
|
|
bidule = machin.ma_methode(1,2)
|
|
|
|
|
|
Un point de vocabulaire : **paramètres** et **arguments**
|
|
|
En Python vous trouverez ces deux termes. Pour la documentation des fonctions (les "docstrings"), qui est une bonne pratique [recommandée dans les PEPs](https://peps.python.org/pep-0257/), sphinx "paramètres" de les définir comme `:param nom_variable: ` ([Sphinx](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html) ) tandis que le format Google utilise Argas... Attributes:
|
|
|
En théorie, les **paramètres** sont ce qui est **déclaré** dans la fonction, entre les parenthèses, et les **arguments** sont ce qui est passé lors de l'**appel** de la fonction.
|
|
|
Donc, ici a et b seraient les paramètres de `ma_methode` et 1,2 seraient les arguments passés lors de l'appel.
|
|
|
La méthode `ma_methode` renvoie ici un nombre calculé par la fonction, qui est également décrit dans la docstring.
|
|
|
|
|
|
:bulb: Petite astuce sous l'IDE PyCharm quand on appelle une fonction:
|
|
|
> quand le curseur est positionné entre les parenthèses, **Ctrl+P** fait apparaître la liste des paramètres attendus.
|
|
|
|
|
|
:bulb: Votre IDE, convenablement configuré, peut créer automatiquement la docstring, surtout si vous utilisez les annotations.
|
|
|
`def ma_fonction(param1:str, param2:int, param3:float) -> float:`
|
|
|
Attention, les **annotations** sont des indications ; votre IDE ou votre débuggueur peuvent détecter des anomalies, mais une définition incorrecte d'annotation n'empêchera pas vos code de tourner, par contre cela peut compliquer le travail de quelqu'un qui essaierait de comprendre le code...
|
|
|
|
|
|
La manière de définir et d'ordonner les paramètres est décrite dans le point **f** ci-dessous
|
|
|
|
|
|
|
|
|
|
|
|
#### A.3.2. Rappel des bases pour éviter des erreurs courantes :
|
|
|
Gardez en tête que la fonction est un objet ! Cela aide à bien comprendre les points importants suivants, qui seront détaillés ensuite.
|
|
|
|
|
|
a) Attention à mettre les parenthèses quand on appelle la fonction et seulement là !
|
|
|
```python
|
|
|
ma_fonction # = objet, de type function
|
|
|
ma_fonction() # = appel à la fonction (elle s’exécute)
|
|
|
truc= ma_fonction # truc est une fonction, en fait truc se comporte maintenant comme ma_fonction
|
|
|
truc= ma_fonction() # j'exécute ma_fonction et truc est ce que renvoie ma_fonction
|
|
|
```
|
|
|
b) une fonction *retourne toujours quelque chose*, si rien n’est précisé par défaut return = None
|
|
|
je peux récupérer tout ce qu’elle retourne dans une variable, ici appelée resultat, qui est éventuellement un *tuple* contenant plusieurs objets ;
|
|
|
```python
|
|
|
resultat = ma_fonction() # éventuellement resultat = None ou un *tuple*
|
|
|
```
|
|
|
|
|
|
```python
|
|
|
def ma_fonction(x):
|
|
|
# définition d'une fonction prenant en argument x ; je ne précise pas son type
|
|
|
print(x)
|
|
|
return x, 2*x, x**2 # la fonction renvoie un tuple de 3 éléments
|
|
|
|
|
|
# j'appelle la fonction
|
|
|
resultat = ma_fonction(5) # ici la fonction renvoie un tuple de 3 éléments
|
|
|
print (resultat) # donc la variable resultat est un tuple de 3 éléments
|
|
|
|
|
|
```
|
|
|
L'exécution du code donne :
|
|
|
|
|
|
(5, 10, 25)
|
|
|
|
|
|
|
|
|
Ma variable resultat est un tuple autrement dit resultat = (5, 10, 25)
|
|
|
Un *tuple* est u conteneur, comme *list* mais avec des caractéristiques différentes.
|
|
|
Pour récupérer tous les termes on « déballe » le tuple (unpacking) :
|
|
|
|
|
|
(x,y,z) = resultat ou x,y,z = resultat.
|
|
|
|
|
|
On aura dans les 2 cas x=5 ; y=10 et z=25
|
|
|
|
|
|
Si on n’a besoin que du premier terme : on peut se contenter d'écrire
|
|
|
x, le_reste = resultat
|
|
|
|
|
|
Vous pourrez vérifier que dans ce cas le_reste est un *tuple* équivalent à (y,z), c'est logique !
|
|
|
Mais on peut faire encore plus succinct, pour bien montrer que le reste ne nous intéresse vraiment pas :
|
|
|
x, _ = resultat ou même x, = resultat
|
|
|
( _ = nom de variable valide (!) , mais par convention on sait qu'on ne s’en servira pas )
|
|
|
|
|
|
|
|
|
Si aucune variable ne récupère le résultat ce n’est pas gênant mais ce qui a été retourné est « perdu » _a priori_ car on n'a pas affecté de nom ; plus exactement on ne peut agir dessus (on pourrait épiloguer un peu plus, mais pour débuter cela suffira).
|
|
|
|
|
|
C’est le cas pour certaines fonctions matplotlib où on peut écrire plt.plot(x,y) (je trace juste une courbe) ou ma_courbe, = plt.plot(x,y) (en plus je nomme ma courbe, premier terme du tuple, je n’ai pas besoin des autres)
|
|
|
|
|
|
c) portée des variables : :warning: pas si simple en fait.
|
|
|
|
|
|
On a dit plus haut que si j'exécute une fonction sans récupérer le résultat dans une variable, elle s'exécute normalement (écrire une ligne dans un fichier ou la console, tracer un graphique, faire une calcul...) mais je ne récupère pas ce qu'elle renvoie éventuellement avec **return**.
|
|
|
:warning: les objets créés dans la fonction ne lui survivent pas... **return** est le seul moyen de récupérer les objets nouvellement créés.
|
|
|
:warning: je peux par contre modifier à l'intérieur d'une fonction des objets préexistants, pas forcément passés en argument !
|
|
|
La "portée des variables" en Python n'est pas forcément quelque chose de simple... on va essayer de poser quelques bases ; pour aller plus loin voir un bon tutoriel (avec les mots-clé *global* et *non local" pour pimenter !) et exercez vous avec :bulb: **Python tutor**
|
|
|
:bulb: Le site [Python Tutor](http://pythontutor.com) est vraiment précieux pour comprendre comment les variables sont rangées dans des espaces différents, et pour suivre pas à pas l'effet des instructions.
|
|
|
|
|
|
Un exemple où l'on essaie de définir des variantes de variables existantes à l'intérieur d'une fonction ; n'hésitez pas à exécuter ces instructions dans Python Tutor et à les triturer :
|
|
|
|
|
|
```python
|
|
|
# ici on est dans le corps principal
|
|
|
annonce = "ici on parle français"
|
|
|
salutation = "bonjour"
|
|
|
c = "Chopin"
|
|
|
print(salutation)
|
|
|
ma_liste_numeros = [1, 2, 5, 10, 50]
|
|
|
ma_liste_mots = [ "un", "deux", "cinq", "dix", "cinquante" ]
|
|
|
|
|
|
# définition d'une fonction qui va avoir son "propre espace"
|
|
|
def ma_fonction_pl(salutation):
|
|
|
print("Nous sommes dans la fonction, on a passé en argument ", salutation)
|
|
|
salutation= "dzień dobry" # on change la valeur de la variable passée en argument
|
|
|
print(salutation) # on vérifie que le changement est effectif
|
|
|
annonce = "teraz mówimy po polsku" # on change la valeur de la variable préexistante annonce
|
|
|
print(annonce)
|
|
|
|
|
|
print(c) # comme il n'y a pas de c dans la fonction, Python va aller chercher au-delà
|
|
|
|
|
|
ma_liste_numeros.append(100) # je MODIFIE la liste EN PLACE par une méthode
|
|
|
ma_liste_mots = [ "jeden", "dwa", "pięć", "dziesięć", "pięćdziesiąt", "sto" ] # j'écrase cette liste
|
|
|
for numero, mot in zip(ma_liste_numeros, ma_liste_mots):
|
|
|
print("Le numéro ",numero, " s'écrit :", mot)
|
|
|
# remarque : il n'a pas le mot clef return : CETTE FONCTION RETOURNERA None
|
|
|
|
|
|
# de retour dans le corps du programme, j'appelle la fonction
|
|
|
resultat = ma_fonction_pl(salutation)
|
|
|
print (resultat) # on n'a pas explicité 'return' donc la fonction renvoie None
|
|
|
|
|
|
print("Nous sommes sortis de la fonction, on a passé en argument ", salutation)
|
|
|
print (annonce ) # la fonction n'a modifié ni annonce ni argument_bidon
|
|
|
print(ma_liste_numeros) # la modification par *append* a été prise en compte
|
|
|
print(ma_liste_mots) # la liste n'a pas été modifiée
|
|
|
|
|
|
```
|
|
|
Le code ci-dessus affiche :
|
|
|
bonjour
|
|
|
Nous sommes dans la fonction, on a passé en argument bonjour
|
|
|
dzień dobry
|
|
|
teraz mówimy po polsku
|
|
|
Chopin
|
|
|
Le numéro 1 s'écrit : jeden
|
|
|
Le numéro 2 s'écrit : dwa
|
|
|
Le numéro 5 s'écrit : pięć
|
|
|
Le numéro 10 s'écrit : dziesięć
|
|
|
Le numéro 50 s'écrit : pięćdziesiąt
|
|
|
Le numéro 100 s'écrit : sto
|
|
|
None
|
|
|
Nous sommes sortis de la fonction, on a passé en argument bonjour
|
|
|
ici on parle français
|
|
|
[1, 2, 5, 10, 50, 100]
|
|
|
['un', 'deux', 'cinq', 'dix', 'cinquante']
|
|
|
|
|
|
|
|
|
Sur la [Copie d'écran de Python tutor, code en cours d'exécution à pas à pas](https://gitlab.irstea.fr/christine.poulard/atelier-matplotlib/-/blob/master/python_tutor_chiffres_en_polonais.png), on remarque qu'il y a en mémoire 2 versions différentes de ma_liste_mots, dont une dans l'espace de nommage de la fonction, qui a été créée par l'instruction ma_liste_mots=[ ...]. La liste ma_liste_numeros n'existe en revanche qu'en un seul exemplaire.
|
|
|
|
|
|
|
|
|
d) *une méthode modifie l’objet en place ou en renvoie un nouveau* selon le cas…
|
|
|
Bien vérifier dans la doc comment se comporte la méthode que l’on utilise ! ! !
|
|
|
Selon les cas, une *méthode* peut :
|
|
|
* modifier un objet (exemple *append*) ; en général elles retournent « None »
|
|
|
* retourner un nouvel objet sans modifier le premier
|
|
|
* laisser le choix (argument *inplace*=True ou =False) :
|
|
|
|
|
|
Exemples courants pour comprendre
|
|
|
méthode *sort* _vs_ fonction *sorted*
|
|
|
```python
|
|
|
# méthode sort = tri « en place » / « in place »
|
|
|
ma_liste.sort() # ma_liste est maintenant triée
|
|
|
# fonction sorted : crée un nouvel objet, trié
|
|
|
ma_liste_triee = sorted(ma_liste)
|
|
|
```
|
|
|
|
|
|
*np.asarray* _vs_ *np.array*
|
|
|
```python
|
|
|
import numpy as np
|
|
|
|
|
|
# nouvel objet = vecteur numpy avec valeurs de la liste
|
|
|
np_mes_valeurs = np.array(ma_liste_de_valeurs) # ma_liste_de_valeurs est toujours de type *list*
|
|
|
# transformer ma_liste_de_valeurs en vecteur numpy
|
|
|
np.asarray(ma_liste_de_valeurs) # ma_liste_de_valeurs est maintenant un numpy.array
|
|
|
```
|
|
|
Parfois, un mot-clef permet de choisir ; voir par exemple dans la bibliothèque Pandas
|
|
|
pandas.DataFrame.drop
|
|
|
*DataFrame.drop*(labels=None, axis=0, index=None, columns=None, level=None, inplace=False, errors='raise')[source]
|
|
|
Drop specified labels from rows or columns.
|
|
|
*Parameters *
|
|
|
(...)
|
|
|
*inplace bool, default False*
|
|
|
If False, return a copy. Otherwise, do operation inplace and return None.
|
|
|
Returns
|
|
|
DataFrame (DataFrame without the removed index or column labels) : sauf si inplace=True.
|
|
|
|
|
|
|
|
|
|
|
|
e) *une fonction peut être passée en argument d’une autre fonction*
|
|
|
```python
|
|
|
fonction_avec_options(var1, var2, ma_fonction)
|
|
|
```
|
|
|
|
|
|
f) :bell: les différents types arguments « de position », "nommés", facultatifs, en nombre indéfini...
|
|
|
|
|
|
Les cas simples sont intuitifs, mais on propose ici un topo détaillé pour bien comprendre les différentes natures d'argument.
|
|
|
|
|
|
_Sources:_
|
|
|
:flag_fr: [Sam et Max, voir §Paramétrage dynamique](https://sametmax.oprax.fr/operateur-splat-ou-etoile-en-python/index.html)
|
|
|
:flag_us: [RealPython, Python args and kwargs: Demystified](https://realpython.com/python-kwargs-and-args/)
|
|
|
et mon livre de chevet :
|
|
|
:flag_pl: "Python avancé et programmation scientifique", J. Karczmarczuk
|
|
|
|
|
|
Les paramètres soivent être absolument placés *dans cet ordre* :
|
|
|
- paramètres normaux et obligatoires;
|
|
|
- paramètres normaux facultatifs (valeur par défaut);
|
|
|
- paramètres dynamiques;
|
|
|
- paramètres dynamiques nommés.
|
|
|
|
|
|
```python
|
|
|
# exemple éhontément piqué à Sam et Max
|
|
|
def affichage_hybride(parametre_normal,
|
|
|
parametre_avec_default="valeur par défaut",
|
|
|
*args,
|
|
|
**kwargs):
|
|
|
print(parametre_normal)
|
|
|
print(parametre_avec_default)
|
|
|
print(args)
|
|
|
print(kwargs)
|
|
|
```
|
|
|
|
|
|
Ce qui va donner à l'appel de fonction suivant :
|
|
|
|
|
|
```python
|
|
|
affichage_hybride("param1", "param2", "infini1", "infini2", kwinfini1=1, kwinfini2=2)
|
|
|
```
|
|
|
param1
|
|
|
param2
|
|
|
('infini1', 'infini2')
|
|
|
{'kwinfini1': 1, 'kwinfini2': 2}
|
|
|
|
|
|
On va expliquer tout ça dans la suite, et vous pouvez également regarder les 2 tutos mis en ref ci-dessus. Et surtout, MANIPULEZ
|
|
|
|
|
|
Pour un utilisateur de fonctions codées par d'autres, les méthodes de matplotlib par exemple, on n'a pas forcément besoin de ce niveau de détail, mais c'est quand même bien de savoir quand l'ordre est important (en premier des arguments de position, obligatoires, et tout à la fin les arguments passés par mot-clef). Les arguments nommés avec valeur par défaut et les arguments passés par dictionnaire se ressemblent beaucoup pour un utilisateur qui ne "rentre" pas dans le code, mais il faut savoir que là encore l'ordre est important.
|
|
|
Vous reviendrez à ces notions en développant vous-mêmes.
|
|
|
|
|
|
**le plus simple : un nombre fini d'arguments "de position"**
|
|
|
Si vous définissez un nombre fini d'arguments, il suffit de les passer **dans cet ordre** lors des appels de fonction, sans rappeler leur nom.
|
|
|
|
|
|
```python
|
|
|
# définition d'une fonction avec 2 arguments
|
|
|
def ma_fonction(a, b):
|
|
|
message = f"{a} divisé par {b} = {a/b}" # au passage, une " f string"
|
|
|
return message
|
|
|
|
|
|
print (ma_fonction(2,5)) # je n'ai pas précisé les noms, leur position suffit
|
|
|
print (ma_fonction(b=5,a=2)) # si je précise les noms, je peux intervertir les arguments
|
|
|
|
|
|
```
|
|
|
|
|
|
_pour mémoire_ : dans les versions récentes de Python, on peut forcer un argument à être "de position" et uniquement de position (on ne peut pas le définir par son mot clé)
|
|
|
|
|
|
**nombre fini d'arguments facultatifs, avec valeur par défaut**
|
|
|
Les arguments avec valeur par défaut permettent d'alléger les appels, mais comme on peut en avoir plusieurs qui soient facultatifs il faut préciser les noms lors des appels pour que l'interpréteur s'y retrouve. De même, ils doivent toujours être définis **après** les arguments de position.
|
|
|
```python
|
|
|
# définition d'une fonction avec un 3e argument avec valeur par défaut
|
|
|
def ma_fonction(a, b, nb_decimales=2):
|
|
|
# on en profite pour utiliser une " f string"
|
|
|
# pour écrire mon_chiffre avec deux chiffres après la virgule on écrit {mon_chiffre:.2f}
|
|
|
# mais je peux aussi passer une variable, entre {}, comme nombre de décimales
|
|
|
message = f"{a} divisé par {b} = {a/b:.{nb_decimales}f}"
|
|
|
return message
|
|
|
|
|
|
print (ma_fonction(2,5)) # je n'ai pas précisé nb_decimales donc nb_decimales vaut la valeur par défaut
|
|
|
print (ma_fonction(2,5, nb_decimales=5 )) # je précise nb_decimales
|
|
|
print (ma_fonction(b=5,a=2, nb_decimales=5 )) # si je précise les noms, je peux intervertir les arguments "de position"
|
|
|
```
|
|
|
2 divisé par 5 = 0.40
|
|
|
2 divisé par 5 = 0.40000
|
|
|
2 divisé par 5 = 0.40000
|
|
|
|
|
|
**nombre indéfini d'arguments *args avec le _splat_ (*)**
|
|
|
|
|
|
La fonction **print** est un exemple de fonction qui prend un nombre indéfini d'arguments : on peut écrire `print("bonjour")`, `print("le résultat est", resultat)`, `print("si je divise ",a, " par ", b, " j'obtiens ", resultat)`
|
|
|
Si on veut admettre un nombre non défini d'arguments, on peut bien sûr faire passer un itérable (liste, tuple...) et dans la fonction on va itérer sur les éléments.
|
|
|
Il existe une autre façon (plus "pythonesque" ??) de faire : avec une étoile précédant l'argument, pour préciser que c'est en fait un **tuple** que la fonction saura 'déballer' (unpacking) et interpréter.
|
|
|
Par convention, on note ce "tuple d'arguments" _*arg_) et on ne peut y recourir qu'une seule fois, sinon l'interpréteur ne s'y retrouverait pas (dans les arguments passés, où commencerait la deuxième série ??), dans ce cas il faut travailler avec un argument = un tuple, à passer comme tel.
|
|
|
|
|
|
```python
|
|
|
# définition d'une fonction avec un nombre indéfini d'arguments
|
|
|
def somme(*un_tuple_de_nombres):
|
|
|
somme = 0
|
|
|
for nombre in un_tuple_de_nombres:
|
|
|
# on frime avec une notation qui ressemble au c ;
|
|
|
# c'est surtout plus rapide car on modifie "en place" ;
|
|
|
somme += nombre # équivalent à somme = somme + nombre
|
|
|
|
|
|
return somme
|
|
|
|
|
|
print(somme(1,2))
|
|
|
print(somme(1,2,3,4,5,6))
|
|
|
# je ne peux pas passer directement un itérable mais je peux en forcer l'unpacking
|
|
|
un_tuple = range(10) # je peux travailler avec n'importe quel type d'itérable : liste...
|
|
|
print(somme(*un_tuple )) # si j'oublie le * j'aurais un "TypeError"
|
|
|
|
|
|
```
|
|
|
|
|
|
:warning: _Attention_ : les arguments de type *args doivent être placés après les arguments **de position**.
|
|
|
Dit autrement : "**tous les arguments suivant un paramètre 'splatted' sont obligatoirement nommés**" (in Karczmarczuk).
|
|
|
Dans les versions récentes : pour "**forcer à utiliser des arguments nommés** même en l'absence de *args, il est possible de placer le séparateur splat seul.
|
|
|
`ma_fonction(arg_de_position,*, arg_nomme) `
|
|
|
`ma_fonction_avec_seulement_des_arguments_nommes(*, arg_nomme_1, arg_nomme_2) `
|
|
|
|
|
|
**nombre indéfini d'arguments** **kwargs : nombre indéfini de clés et valeurs
|
|
|
|
|
|
Avec *arg, on peut faire passer une et une seule fois un nombre indéfini d'arguments de même nature, ou au moins susceptibles d'être compris et traités par la fonction (**print** accepte des **string** et des **nombres** même mélangés par exemple).
|
|
|
|
|
|
Si on veut admettre un nombre non défini d'arguments de types différents et facultatifs, on peut bien sûr les définir en paramètre avec défaut, mais on peut aussi prévoir dans le code de considérer une suite d'arguments nommés comme un dictionnaire, dont les clés et les valeurs seront traitées dans le code.
|
|
|
Par convention, on note ce "dictionnaire d'arguments" _**kwarg_).
|
|
|
|
|
|
Le module **matplotlib** recourt énormément aux kwargs, peut-être parce qu'il recourt à un grand nombre d'attributs partagés par différents objets.
|
|
|
Par exemple : ax.plot(x,y,color='red', linewidth=5) ; on peut définir des couleurs et des largeurs de ligne pour un grand nombre d'objets,
|
|
|
|
|
|
:wink: vous pouvez maintenant revenir à l'exemple affichage_hybride de Sam et Max pour vérifier que vous avez tout compris.
|
|
|
|
|
|
#### plus avancé... pour mémoire
|
|
|
g) une fonction peut avoir des *attributs* (c’est ce qui permet la définition de décorateurs…)
|
|
|
Il y a beaucoup de similitudes entre classes et fonctions, mais ceci dépasse le cadre actuel de ces notes.
|
|
|
|
|
|
### A4 Les dictionnaires
|
|
|
#### Définition d'un dictionnaire
|
|
|
Un dictionnaire est un conteneur qui contient des *clés*, et à chaque *clé* est associée une *valeurs*.
|
|
|
Pour que cela ait un sens, il faut que la *clé* ne puisse pas changer en cours de route : elle doit être d'un type * non mutable*
|
|
|
La valeur peut être une variable simple ou un conteneur ou une instance de classe...
|
|
|
|
|
|
#### création d'un dictionnaire ; entre accolades
|
|
|
De la même façon que les crochets [ ] sont associés aux objets de type *list*, les dictionnaires sont délimités par des accolades { }.
|
|
|
```python
|
|
|
dictionnaire_main = {1:"un", 2:"deux", 3: "trois", 4:"quatre", 5: "cinq"} # dictionnaire avec couples clé: valeur
|
|
|
mon_dictionnaire = dict() # dictionnaire vide
|
|
|
|
|
|
|
|
|
ceci_n_est_pas_un_dictionnaire_mais_un_set = {1, 2, 5, 10, 50} # pas de couples clé, valeurs...
|
|
|
|
|
|
dico_chiffres = dict(zip(ma_liste_numeros, ma_liste_mots)) # si les 2 ont bien le même nombre d'éléments...
|
|
|
|
|
|
```
|
|
|
mon_dictionnaire [clef] = valeur # j'ajoute une entrée (j'ajoute des entrées comme je veux)
|
|
|
|
|
|
la variable clef doit être d'un type NON-MUTABLE (voir notes) ; les types string, datetime, chiffre... conviennent
|
|
|
la variable valeur peut être de n'importe quel type, y compris des conteneurs
|
|
|
```python
|
|
|
mon_dictionnaire[clef] #renvoie la valeur qui correspond à la clé clef
|
|
|
mon_dictionnaire.keys() # vue des clés ; ce n'est pas une liste mais c'est un itérable
|
|
|
mon_dictionnaire.values() # vue des valeurs; ce n'est pas une liste mais c'est un itérable
|
|
|
mon_dictionnaire.items() # vue des couples clés-valeurs ; ce n'est pas une liste mais c'est un itérable
|
|
|
|
|
|
six = 6
|
|
|
six in dictionnaire_main.keys() # booléen, vrai si cle_de_sol est une ces clés
|
|
|
```
|
|
|
Pour terminer, plusieurs manières de faire afficher les couples clé/valeurs :
|
|
|
```python
|
|
|
for clef in dictionnaire_main.keys() :
|
|
|
print("clé = ", clef, " valeur = ",dictionnaire_main [clef])
|
|
|
# il y a plus simple :
|
|
|
for clef in dictionnaire_main :
|
|
|
print("clé = ", clef, " valeur = ",dictionnaire_main [clef])
|
|
|
# et encore plus simple :
|
|
|
for clef, valeur in dictionnaire_main.items() : # à chaque itération on a un tuple clef, valeur
|
|
|
print("clé = ", clef, " et valeur = ", valeur)
|
|
|
# pour jouer avec les tuples :
|
|
|
for tuple_clef_valeur in dictionnaire_main.items() : # à chaque itération on a un tuple clef, valeur ; ici on le garde sous forme de tuple pour comprendre ce qui se passe
|
|
|
clef, valeur = tuple_clef_valeur # on "déballe" le tuple : UNPACKING
|
|
|
print("clé = ", clef, " et valeur = ", valeur)
|
|
|
|
|
|
``` |
|
|
\ No newline at end of file |