Initial commit

This commit is contained in:
2025-07-04 19:26:39 +02:00
commit c8682d4801
248 changed files with 12519 additions and 0 deletions

166
.gitignore vendored Normal file
View File

@ -0,0 +1,166 @@
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/documentation/slides/
.idea

View File

@ -0,0 +1,180 @@
title: Plan de formation Python Initiation
author: Steve Kossouho
'''
= Programme de Python Initiation
'''
La formation s'adresse à des quasi débutants en programmation, ou curieux d'en savoir un peu plus sur les bases de Python.
Pour les plus avancés, le rythme change (uniquement si le groupe est suffisamment homogène).
'''
== Démarrage
* Tour de table et présentation
* Logistique, planning et administratif
* Rappel du programme
'''
== Découvrir Python
* Nature de Python et court historique
* Versions de Python et différences
* Découvrir la ligne de commande et le shell
* Environnements de développement intégrés
* Créer un nouveau projet avec PyCharm
`lundi 10h30 si pas de pb d'install`
'''
== Découvrir le langage
* Syntaxe de base
* Types de base : chaînes, nombres
* Variables et expressions
* Contrôler ce que l'on exécute : conditions et boucles
* Découvrir les listes (et tuples, sets) + slicing
* Découvrir les dictionnaires
* Compréhensions (listes etc.)
* Opérations sur les chaînes : méthodes et formatage
`mardi 14h30 max`
'''
== Le droit aux erreurs
* Découvrir le concept d'exceptions
`mardi 15h45`
'''
== Découvrir les fonctions
* Découvrir l'utilité des fonctions
* Découvrir la syntaxe
* Référence vs appel de fonction
* Valeurs de retour d'une fonction
* Passer des arguments aux fonctions
`mercredi 10h00`
'''
== Découvrir la structure du code au-delà du simple script
* Expliquer ce qu'est pip, venv (avec exemples) et aussi conda
* Modules et packages
* Bibliothèque standard python et imports
* Exemple : faire un peu de calcul avec la bibliothèque standard
* Réutiliser votre propre code
* Extra : virtualenv et pip)
`mercredi 12h00`
'''
== Types supplémentaires
* Découvrir les types heure+date et intervalle de temps
* Convertir depuis du texte ou vers du texte
`mercredi 14h30`
'''
== Découvrir la programmation orientée objet
* Intérêts de la programmation objet
* Découvrir la syntaxe
* Concepts d'attributs et de méthodes
* Différence entre une classe et ses objets
* Instancier avec une autre signature
* Découvrir l'héritage et le polymorphisme
* Bonus : Courte introduction aux décorateurs avec @staticmethod
* Bonus : Introspection (getattr, setattr, dir, isinstance, type)
* Bonus : Diagramme de classes UML
`mercredi 17h00`
'''
= Programme de Python Approfondissement
Même public que l'initiation : peu d'expérience en programmation ou curiosité quant à Python. Nécessite toutefois de connaître les bases du langage.
*Objectif* : aller un peu plus loin dans les bases de Python pour pouvoir traiter quelques données.
'''
== Découvrir comment manipuler des fichiers texte
* Écrire et lire des fichiers (open/close)
* Écrire et lire des fichiers (gestionnaire de contexte)
* Formats : JSON
* Formats : CSV
* Extra : Parcours de fichiers (pathlib)
`jeudi 14h30`
'''
== Toucher du doigt les bases de données SQL avec DBAPI et SQLite3
* Exemple simple pour dialoguer avec une base SQL
* Ajouter et requêter des données simplement
* Bonus : ORMs (peewee)
`jeudi 17h00`
'''
== Documenter du code Python
* Comment on fait ?
* Qu'est-ce qu'on documente ?
* Comment on génère des fichiers HTML de documentation ? (pdoc3, sphinx)
* Bonus : Donner des indications sur le type des variables et arguments
`vendredi 10h45`
'''
== Faire sa première interface graphique avec Python
* Exemples d'outils pour les interfaces graphiques
* PySide6 avec Python (sous Linux)
* Écrire une fenêtre simple
* Interagir avec les contrôles (boutons, champs)
* Concevoir graphiquement une fenêtre et l'utiliser avec Python
`vendredi 15h45`
'''
== Bonus : Aborder la journalisation
* Qu'est-ce que c'est ? Et pourquoi, pour qui, quand ?
* Exemple simple de journalisation
* Exemple configuré de journalisation dans un fichier
`vendredi 16h30` (selon possibilités)
'''
== Certifications
Au plus tard le vendredi à 15h30. Dure jusqu'à 17h30.
'''
== That's all folks!
image:assets/images/x-outro-end.png[That's all folks!]

View File

@ -0,0 +1,168 @@
---
title: Découvrir Python
author: Steve Kossouho
---
[intro-guido]: assets/images/intro-guido.jpg
[intro-terminal]: assets/images/intro-terminal.png
[ide-pycharm]: assets/images/ide-pycharm.png
# Découvrir Python
----
## Introduction à Python
Python, c'est surtout :
- Un langage de programmation généraliste
- Bénéficie de 33 ans d'améliorations publiques (fév. 1991)
- Très populaire depuis 2018
- Et il paraît que c'est simple à apprendre…
----
### Popularité 2021
![Popularité des langages de programmation 2021](assets/images/x-intro-tiobe-2021.jpg)
----
### Popularité 2022
![Popularité des langages de programmation 2022](assets/images/x-intro-tiobe-2022.png)
----
## Historique de Python
- Développement débuté en décembre 1989 par **Guido Van Rossum**
- Première publication le 20 février 1991 (version 0.9)
- Maintenu par la **Python Software Foundation** (mars 2001)
- Licence permissive (PSFL) similaire à la BSD
- Utilisé en éducation, IA, traitement de données, automatisme etc.
- Python 2.0 sorti en octobre 2000 : gestion Unicode
- Python 3.0 (Python 3000) sorti en décembre 2008 : réorganisation
- Python 3.11 sorti en novembre 2022 : performance
----
![Guido Van Rossum, BDFL jusqu'en 2018][intro-guido]
----
![Équipe des développeurs de Python en 2018](assets/images/x-intro-core-developers-2018.jpg)
----
## Propriétés techniques de Python
Le langage Python repose sur le socle technique suivant :
- Langage **interprété** : un programme exécute vos scripts
- Langage de programmation impérative : instructions exécutées dans l'ordre
- Langage de programmation fonctionnelle
- Langage de programmation strictement orienté objet (différent de Java)
- Typage dynamique des variables
- Syntaxe relativement concise
- Démarrage facile (davantage que Java, C, Rust…)
----
### Python 3.x
- Première version (Python 3000) datée du 3 décembre 2008
- Version prise en charge la plus ancienne : _3.7_ (2018)
- Version la plus récente stable : _3.11_ (2022)
- Version la plus répandue (Linux) : _3.9_
----
## Démarrer avec Python sous Linux
Les machines Linux proposent toujours un terminal, même si vous y travaillez via une interface de bureau graphique (Gnome, KDE ou autre).
L'environnement de développement intégré graphique **PyCharm** en inclut également un.
Sous Windows, l'invite de commande ou **PowerShell** sont également des terminaux. On conseillera
largement PowerShell face à `cmd.exe`. Pour cette raison, passez à Windows 10 au minimum.
----
### Terminaux sous Linux
![Neofetch lancé dans le Terminal de Gnome][intro-terminal]
----
### Environnements de développement
Il existe de nombreux éditeurs de code et environnements intégrés de développement. Entre autres :
1. PyCharm (Community ou Professional)
2. Visual Studio Code
3. Spyder
4. ~~Atom~~
Les environnements de développement vous facilitent l'écriture et l'exécution de code en Python,
même si vous pourriez également tout faire avec un terminal et un éditeur de texte.
**Note** : Si vous possédez une adresse email académique (ex. université), vous avez normalement
accès [gratuitement à PyCharm Professional](https://www.jetbrains.com/fr-fr/community/education/#students)
(intégration Jupyter, Numpy et Pandas, profiling, Django et Javascript, Frameworks JS, Docker etc.)
----
#### PyCharm
PyCharm, développé par JetBrains (CZ), est un environnement de développement intégré spécialisé
dans la programmation Python. Il existe dans deux versions, **Community** (gratuite) et **Professional**,
cette dernière prenant en charge les bases de données, les frameworks web, le profiling et l'analyse de données etc.
Points particuliers :
- Excellente prise en charge du langage
- Interface élégante et personnalisable
- Débogueur intégré
- Console Python avancée
- Terminal efficace et fonctionnel
- Système de plugins offrant de nouvelles fonctionnalités
----
#### Visual Studio Code
Visual Studio Code est développé par Microsoft (US). C'est un environnement de développement généraliste
qui fonctionne avec des extensions pour augmenter ses capacités. Il est totalement gratuit et très utilisé
par les développeurs Python ayant débuté en 2017 et au-delà.
Points particuliers :
- Lancement rapide
- Couleurs personnalisables et zoom
- Beaucoup d'extensions pour beaucoup de fonctions
- Bon support du langage
- Expérience utilisateur limitée par l'interface
----
#### Spyder
Spyder a été développé par Pierre Raybaut (FR). C'est un éditeur de code Python simple et spécialisé
dans l'exécution de petits scripts, notamment pour les chercheurs et académiciens.
Points particuliers :
- Interface simple
- Visualisation des variables dans un tableau à l'exécution
- Aucune notion de projet
- Pas de terminal intégré
----
### Créer un projet avec PyCharm
1. Lancer PyCharm et créer un nouveau projet
2. Choisir le nom du répertoire de projet
3. Choisir le nom du répertoire d'isolation de dépendances (`virtualenv`)
4. C'est parti !

View File

@ -0,0 +1,493 @@
---
title: Découvrir le langage - types de données avancés
author: Steve Kossouho
---
# Collections de données
----
## Les listes
Type de collection de données : `list`{.python}. Une liste peut contenir une **séquence** d'éléments
de n'importe quel type pris en charge par Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)
```python {.numberLines}
liste1 = [1, 2, 3, 4, 5, 6] # types cohérents
liste2 = [1, 2, 3, "a", "b", "c"] # types divers
liste3 = [None, None, True, False]
liste4 = [[1, 2, 3], [4, 5, 6]]
liste5 = [] # liste vide, équivalent à list()
liste6 = [liste1] # Liste contenant 1 élément, qui est lui-même une liste
```
----
Il est possible de manipuler une liste facilement. On peut ajouter des éléments à la fin, à une
position précise, retirer un élément ou encore récupérer un élément seul.
```python {.numberLines}
liste1 = [1, 2, 3, 4, 5, 6]
liste2 = list() # Créer une nouvelle liste vide
# Infos sur la liste
length = len(liste1) # Récupérer le nombre d'éléments
position = liste1.index(3) # Renvoie l'index de la valeur 3, ou erreur si introuvable
nine_trouvable = 9 in liste1 # Renvoie si l'élément 9 existe dans la liste
# Récupérer des éléments
print(liste1[0]) # Affiche le premier élément si la liste est non vide
# Manipuler le contenu
liste1.append(7) # Ajoute 7 comme nouvel élément à la fin
liste1.insert(0, 99) # Insère 99 comme nouvel élément au tout début
liste1[0] = 98 # Remplace la valeur à l'index 0
liste1.remove(4) # Enlève la première occurrence du nombre 4, ou erreur si introuvable
del liste1[3] # Retire l'élément à l'index 3
```
[Méthodes accessibles sur les listes](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists)
----
### Erreurs d'accès
Accéder à un élément de liste n'existant pas génère une erreur (`IndexError`{.python}) et interrompt
votre programme; il peut être utile d'utiliser la fonction `len()`{.python} pour tester que vous
accédez à un indice valide.
```python {.numberLines}
liste1 = [1, 2, 3]
if len(liste1) >= 4:
print(liste1[3])
```
----
### Erreurs de méthodes
La méthode `.remove(valeur)`{.python} provoque également une erreur (`ValueError`{.python}) si
l'élément en argument n'existe pas dans la liste. De la même façon, il peut être utile de tester
qu'un élément est présent dans la liste avant d'essayer de l'en supprimer :
```python {.numberLines}
liste1 = [1, 2, 3]
if 8 in liste1:
liste1.remove(8)
```
----
### Indices de liste négatifs
Il est également possible d'accéder à des éléments de liste en utilisant des indices négatifs.
Si la liste n'est pas vide, l'élément d'indice `-1` est le dernier élément de la liste (équivalent
à `len(liste) - 1`{.python}), et l'élément à l'indice `-len(liste)`{.python} est le premier (
équivalent à `0`).
Tout nombre inférieur génère une erreur de type `IndexError`{.python}.
```python {.numberLines}
liste1 = [1, 2, 4, 6, 9, 11]
print(liste1[-1])
```
----
## Parcourir une liste
Comme on l'a vu avec la boucle `for`{.python}, utilisée avec `range()`{.python}, on peut utiliser la
boucle `for`{.python} sur une liste :
```python {.numberLines}
prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
for number in prime_numbers:
print(number)
```
La variable `number`{.python} déclarée pour la boucle contiendra tour à tour
les valeurs `2`{.python}, puis `3`{.python}, puis `5`{.python}, etc.
----
## Opérations sur listes
Récupérer des portions ou partitions de listes (slicing) :
```python {.numberLines}
a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
b = a[0:5] # Index 0 à 5 non inclus. Marche aussi sur les chaînes.
c = a[5:0] # L'index de fin est inférieur au départ, renvoie une liste vide
d = a[:] # Renvoie une copie de toute la liste
e = a[::-1] # Tout parcourir à l'envers
f = a[::2] # Tout parcourir 2 par 2
g = a[5:2:-1] # L'indice de début est supérieur à l'indice de fin
h = a[2:5:-1] # Le pas est négatif, start < end, renvoie une liste vide
```
Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si
vous souhaitez appliquer un calcul sur une petite partition d'un jeu de données.
----
## Autres collections
Outre les listes, il existe 3 types de base pour collectionner des éléments :
- Tuple : `tuple()`{.python}
- Set (jeu d'éléments uniques) : `set()`{.python}
- Dictionnaires (association) : `dict()`{.python}
----
### Tuples
**Tuple** : Un `tuple`{.python} fonctionne quasiment trait pour trait comme une liste, à la différence qu'une
fois que vous avez défini ses éléments, vous ne pouvez plus en changer. On parle de « collection immuable ».
```python {.numberLines}
a = (1, 2, 3)
b = () # ou tuple()
c = (1,) # il vous faut au minimum une virgule
d = (None, "Salut", 15, 2.718)
```
Les méthodes de modification de contenu, telles que `append`{.python} etc. ne sont logiquement pas disponibles avec
ce type : [opérations des tuples](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations)
----
### Ensembles (données uniques)
**Set** (ensemble) : Un `set`{.python} peut contenir plusieurs éléments et est modifiable au même titre
qu'une liste, mais n'est pas une séquence; aucune notion d'ordre ou d'index.
La collection garantit l'unicité des éléments, un élément ne pouvant apparaître qu'une fois au maximum
dans votre objet.
```python {.numberLines}
a = {1, 1, 2, 3, 3, 3} # équivaut à {1, 2, 3}, les valeurs étant uniques à la fin
b = {2, 4, 5, "hello", (1, 2, 3)}
c = set() # obligatoire pour déclarer un set vide, sinon considéré dict
print(len(b))
a.add(9.5) # ajoute la valeur 9.5 (float) au set
a.discard(9.5) # retire la valeur 9.5, ou ne fait rien
d = a.intersection(b) # renvoie un set avec les éléments communs à a et b
e = a.union(b) # avec les éléments présents dans a ou b
...
```
[Méthodes disponibles sur les ensembles](https://docs.python.org/fr/3/library/stdtypes.html#set)
----
![Opérations sur les ensembles](assets/images/basics-sets-operations.jpg)
----
#### Bonus: Particularités des ensembles
Les ensembles sont des structures de données très efficaces. Techniquement, vous pouvez attendre une
complexité en `O(1)` pour que Python sache si un élément est déjà présent ou pas; cela signifie qu'il
faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre `set`{.python} en possède un
seul ou un milliard.
L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme **Table de hachage**.
----
##### Tables de hachage
Une table de hachage fonctionne en trois temps:
- On réserve des zones de mémoire d'avance (**buckets**), au départ vides;
- lorsque l'on souhaite vérifier si un élément y est présent, on lui calcule une signature (`hash()`{.python});
- la signature de l'objet permet de savoir quelle zone de mémoire consulter pour le trouver.
- si la zone de mémoire est vide, c'est que l'objet n'était pas présent.
----
##### Exemple de hachage
![Table de hachage](assets/images/basics-sets-hashing.png)
----
##### Hachage
La signature est un nombre calculé qui est sensé dépendre du contenu d'un objet. Python propose une fonction
à cet effet, `hash()`{.python}, pour laquelle chaque classe propose sa propre implémentation.
L'objectif d'un hash est simple; deux objets différents doivent avoir un hash différent.
Dans Python ce dernier est un nombre entier sur 64 bits. Si deux objets a et b ont le même hash, ils seront généralement
considérés comme équivalents et donc en **collision** (dans ce cas, `a == b`{.python}). S'ils ne sont pas considérés équivalents,
il n'y a pas de collision.
----
##### Hachage en général dans Python
Entre deux lancements d'un interpréteur Python, le hash de la majorité des objets est différent;
l'algorithme utilise une valeur aléatoire pour générer les hashs, de façon qu'il soit impossible de créer un
dictionnaire de signatures dédié à créer des collisions.
Le hachage est en général imprévisible pour les valeurs suivantes:
- `str`{.python} non vides
- `float`{.python} non équivalents à un `int`{.python}
- `None`{.python}
- `tuple`{.python}
----
##### Hashes prévisibles
Le hash est par contre prévisible pour les valeurs suivantes:
- `0`{.python}, `0.0`{.python}, `False`{.python} et `""`{.python} ont un hash de `0`{.python} mais `""`{.python} n'est pas équivalent;
- `1`{.python}, `1.0`{.python} et `True`{.python} ont un hash de `1`{.python} et sont tous en collision.
- `int`{.python}, où le hash est identique au nombre...
Les objets `int`{.python} ont généralement un `hash()`{.python} identique, sauf cas suivants:
- `-1`{.python} a un hash de `-2`{.python} car la valeur `-1`{.python} est un code d'erreur;
- `2 ** 61 - 2`{.python} est le dernier nombre positif avec un hash identique;
- à partir de `2 ** 61 - 1`{.python}, le hash revient à 0 etc.
- le même algorithme fonction sur les entiers négatifs, mais avec des hashes négatifs.
----
##### Hashes impossibles
L'algorithme de la table de hachage nécessite qu'un objet stocké dans un bucket possède en
permanence un `hash` correspondant à son bucket. Cela pose un problème avec les objets modifiables
à tout moment, tels que les `list`{.python}, `set`{.python} ou encore les `dict`{.python}.
La solution adoptée par Python consiste à interdire le calcul de signature desdits objets.
Les `tuple`{.python} demeurent des données valides, puisque nous avons la garantie de ne jamais pouvoir
changer leur contenu, et ainsi leur signature.
```python {.numberLines}
# La fonction hash est disponible par défaut en Python
print(hash([1, 2, 3])) # Provoque une exception
```
----
### Dictionnaires : associations entre clés et valeurs
**Dictionnaires** : (`dict`{.python}) C'est un **ensemble** d'associations où l'on définit clés et valeurs. C'est le même
fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le
mot (la clé), vous retrouvez la définition s'il y en a une (la valeur). D'autres langages ont des structures
similaires et appellent ça des `HashMap`{.java} ou des `object`{.javascript}.
```python {.numberLines}
a = {"server1": "192.168.1.2", "server2": "192.168.1.3", "server3": "192.168.1.5"} # serveurs et adresses IP
b = {8: "Mme Garnier", 10: "M. Dubois", 11: "Mlle Yousfi", 12: "Mme Préjean"} # rendez-vous horaires
d = {1.2: "flottant", True: "booléen", None: "rien", (1, 2, 3): "tuple"} # clés de plusieurs types
c = {} # ou dict(), ceci est un dictionnaire vide
print(a["server1"]) # affiche "192.168.1.2"
print(b[10]) # affiche "M. Dubois"
print(b[9]) # provoque une erreur
```
----
#### Parcourir un dictionnaire
Il est possible de parcourir un dictionnaire avec une boucle `for`{.python}. De base, ce sont les
clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour
parcourir un dictionnaire :
```python {.numberLines}
a = {
"Jérémy": (25, "M", "Lille"),
"Hélène": (30, "F", "Ambérieu-en-Bugey"),
"Gwladys": (35, "F", "Nyons"),
}
for prenom in a:
print(prenom) # affiche uniquement une clé
print(a[prenom]) # retrouve la valeur associée à la clé
```
----
Si vous parcourez une collection dont tous les éléments sont eux-mêmes des séquences
de `n` éléments, vous pouvez les dépaqueter (**unpacking**) :
```python {.numberLines}
# Avancé, unpacking via la méthode `items`
for item in a.items(): # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
print(item) # est un tuple
for key, value in a.items():
# on peut utiliser 2 variables de boucle si chaque élément parcouru est
# une séquence de taille 2. Merci l'unpacking !
print(key, value)
```
----
#### Manipuler un dictionnaire
Pour changer la valeur associée à une clé existante, ou par la même occasion, associer une valeur à
une nouvelle clé, il suffit d'écrire :
```python {.numberLines}
a = {"Bordeaux": 250000}
a["Marseille"] = 800000
a["Bordeaux"] = 90
print(a["Bordeaux"]) # Affiche 90
# On peut supprimer du dictionnaire une association en écrivant
del a["Marseille"] # plus de clé pour Marseille !
print("Marseille" in a) # renvoie si la clé "Marseille" existe
```
----
### Plus: méthodes de dictionnaires
La méthode `get(key, default)`{.python} des dictionnaires renvoie la valeur associée à une clé. Si
l'on ne passe pas de valeur pour l'argument `default`{.python}, pour une clé introuvable, la méthode
renvoie `None`{.python} (au lieu de planter comme lorsqu'on écrit `dict[key]`{.python}).
```python {.numberLines}
populations = {"Paris": 2.6e6, "Marseille": 8e5, "Lyon": 5e5, "Bordeaux": 2.5e5}
print(populations.get("Tarbes")) # renvoie None
print(populations.get("Mulhouse", -1)) # renvoie -1
print(populations.get("Marseille", -1)) # renvoie 800 000.0
```
Si l'on passe une valeur pour l'argument `default`{.python}, alors, si la clé n'a pas été trouvée,
la méthode renvoie la valeur de repli que vous avez définie.
----
## Compréhensions de listes, tuples etc.
Déclarer des listes, tuples, sets et dictionnaires avec la syntaxe de compréhension :
```python {.numberLines}
a = [x * 2 for x in range(100)] # tous les nombres pairs de 0 à 198
b = [x * 2 for x in range(100) if x != 10] # nombres pairs de 0 à 198 sauf 20
c = (x for x in range(100)) # pas un tuple mais un générateur, nombres de 0 à 99
d = {x / 2 for x in range(100)} # on peut faire pareil avec les sets
pre = {"Jon": None, "Pam": None, "Mel": None, "Kim": None}
e = {k: pre[k] for k in pre} # on copie pre
```
----
## Récapitulatif des collections
| Type | Propriétés |
|---------------|--------------------------------------------------------------------------------------------------------------------------|
| `list` | `[...,]`{.python}. Séquence dont le contenu est modifiable (ajout, suppression). |
| `tuple` | `(...,)`{.python}. Séquence dont le contenu est gelé après déclaration. Utilisable comme élément d'un `set`. |
| `set` | `{...,}`{.python}. Déduplique les éléments et permet des opérations entre ensembles <br/>(union, intersection etc.) |
| `dict` | `{..:..,}`{.python}. Structure de **recherche** rapide où l'on peut associer des valeurs quelconques à des identifiants. |
----
### Usages d'exemple
Le `tuple`{.python} n'est jamais très important, et il sera souvent plus pratique de travailler sur des listes. Les seuls
cas de figure qui obligent à utiliser des `tuple`{.python} sont les cas où vous voulez ajouter une séquence comme élément d'un `set`
ou comme clé d'un `dict`{.python}.
- `list`{.python} : si vous avez des données séquentielles à stocker, par exemple lues depuis un fichier.
- `set`{.python} : si vous souhaitez enlever des doublons, connaître le nombre d'éléments uniques.
- `dict`{.python} : si vous avez besoin d'une "table de référence" pour stocker des données que vous allez rechercher fréquemment.
----
# Chaînes de caractères
----
## Opérations sur les chaînes de caractères
Nous n'avons pas vu grand chose sur les chaînes de caractères, mais certaines informations peuvent
être fréquemment utiles aux développeurs :
- [Méthodes sur les objets de type `str`{.python}](https://docs.python.org/3/library/stdtypes.html#string-methods)
- [Formatage de chaînes via les f-strings](https://zetcode.com/python/fstring/)
- [Documentation officielle sur le format des interpolations](https://docs.python.org/3/library/string.html#format-string-syntax)
```python {.numberLines}
a = 19
b = f"Le serveur qui doit être vérifié aujourd'hui est le numéro {a}"
c = f"Formatage de la variable : {a:f}" # affiché comme flottant (6 chiffres après la virgule)
```
----
## Les chaînes de caractères et leurs méthodes
```python {.numberLines}
chaine = "Bonjour"
print(chaine.upper(), chaine.lower())
print(chaine.center(50, " ")) # Centre la chaîne originale sur 50 caractères
```
Méthodes qui renvoient une copie modifiée d'une chaîne
----
## Méthodes fréquemment utiles sur les chaînes de caractères
- `upper()`{.python} : renvoie une copie en majuscules
- `lower()`{.python} : renvoie une copie en minuscules
- `strip()`{.python} : retire les espaces aux extrêmités
- `replace(old, new, count=None)`{.python} : remplace `old` par `new`
- `index(sub, start=None)`{.python} : renvoie la position de `sub`
- `sub in text`{.python} : renvoie si `sub` est inclus dans `text`
- `split(sep=None)`{.python} : découpe en liste autour du séparateur, par défaut autour des espaces
- `str.join(iterable)`{.python} : sert de séparateur et joint une liste de chaînes
----
```python {.numberLines}
chaine = "Bonjour, nous sommes le 17 juillet 2063."
words = chaine.split() # renvoie une liste de chaînes autour des espaces
rejoin = " ".join(words) # renvoie une chaîne en insérant l'espace comme séparateur
print(chaine.upper(), chaine.lower(), rejoin)
print("17 juillet" in chaine)
```
Exemple de `str.split()`{.python} et `str.join()`{.python}
----
## Bonus : Convertir des données d'un type à un autre
Nous avons vu, ici et là, quelques fonctions pour déclarer des valeurs de base, ou convertir des
valeurs. En voici une liste plus complète :
- `bool(val)`{.python}
- `int(val)`{.python}, `float(val)`{.python}
- `str(val)`{.python}
- `list(val)`{.python}, `tuple(val)`{.python}
- `set(val)`{.python}, `dict(val)`{.python}
Toutes ces fonctions renvoient un nouveau booléen, entier, flottant etc. correspondant à une
conversion de l'expression passée en argument. Cela fonctionne uniquement lorsque la conversion a du sens.
----
Appelées sans argument, ces fonctions vous renvoient `False`{.python}, `0`{.python}, une chaîne ou une
collection vide (des valeurs considérées neutres, qui renvoient toujours `False`{.python} lorsqu'on les convertit en booléen).
Lorsque vous passez un argument à ces fonctions, elles vous renvoient une nouvelle valeur,
qui est une conversion de l'argument passé vers le type
représenté par la fonction. Ce n'est pas toujours possible d'effectuer une conversion; par exemple, il est impossible de
convertir une liste vers un nombre flottant, ou encore de convertir la chaîne `"bonjour"`{.python} vers un nombre entier.
```python {.numberLines}
converted1 = int(3.14159) # tronque le flottant / retire la partie décimale
converted3 = float("3.14159") # comprend le texte et génère un flottant
converted2 = list(1) # erreur
converted4 = float("salut") # aucun caractère valide pour représenter un nombre, échoue
```

View File

@ -0,0 +1,579 @@
---
title: Decouvrir le langage
author: Steve Kossouho
---
# Bases du langage
----
## Le langage Python
Le langage Python peut être découpé en deux grands types d'instructions :
- Les déclarations
- Les expressions
Les déclarations sont en général des instructions pour créer des variables ou des structures de données.
Les expressions sont des instructions pour effectuer des calculs ou récupérer des données.
----
## Hello World
```python {.numberLines}
print("Hello world!")
```
----
## Commentaires
En Python, un commentaire est un élément textuel dans votre code qui sera ignoré à l'exécution.
Il ne pourra pas causer d'erreur de syntaxe, mais encore faut-il l'utiliser correctement :
```python {.numberLines}
# Commentaire sur sa propre ligne
print("Bonjour") # Commentaire à la fin d'une ligne de code
```
----
## Variables
Une variable est une boîte en mémoire à laquelle on donne un nom, et dans cette boîte, on place une valeur.
Cette valeur peut être un peu n'importe quoi en Python, mais notamment :
- Un nombre (entier ou flottant)
- Une chaîne de caractères (texte)
- D'autres types avancés (collection, date, heure, classes, etc.)
----
## Exemple d'assignation de variables
```python {.numberLines}
a = 15 # Python comprend qu'il s'agit d'un entier
c = 3.14159265358979323 # Python comprend qu'il s'agit d'un flottant
b = "Bonjour" # Python reconnaît du texte
d = input("Entrez du texte")
del c # Détruit la variable, qui devient invalide
```
**Note** : une chaîne de caractères peut aussi être marquée par le caractère `'` :
```python {.numberLines}
b = 'Bonjour' # Python reconnaît du texte
```
----
## Changer la valeur associée à une variable
Une variable a, comme son nom l'indique, la capacité de changer de valeur dans le temps.
En Python, on utilise exactement la même syntaxe pour déclarer une nouvelle variable ou encore
changer la valeur qui lui est associée :
```python {.numberLines}
a = 15
print(a) # Affiche 15
a = 99
print(a) # Affiche 99
a = a + 1 # ou a += 1
print(a) # Affiche 100
a = "Bonjour" # On associe une valeur d'un type différent
print(a) # Affiche le texte "Bonjour"
```
----
## Typographie des variables
Typographie conventionnelle des noms de variables :
- Tout en minuscules (ex. `texte`, `tour_eiffel`)
- Commence par une lettre (obligatoire, ex. `saisie`)
- Contient des underscores entre les mots (ex. `user_name`)
- Pas d'accent ni d'espace ni de tiret
- Peut contenir un chiffre, mais pas en premier caractère (ex. `mambo_number_5`)
----
## Expressions
Une expression correspond toujours à une valeur que l'on pourrait mettre dans une variable.
```python {.numberLines}
a = 15
chaine = "bonjour"
nombre = 15
print(a) # `a` existe et peut être utilisée comme expression
beau_temps = True # Valeur spécifique *vrai*
sale_temps = False # Valeur spécifique *faux*
non_defini = None # valeur spéciale considérée comme "pas de valeur".
```
----
## Types de données
En programmation, les types de données ont des noms, et en Python ce sont les suivants :
- `int`{.python} : (integer) nombres entiers
- `float`{.python} : (floating point numbers) nombres à virgule flottante
- `str`{.python} : (string) chaînes de caractères
- `bool`{.python} : (boolean) c'est vrai / c'est faux
```python {.numberLines}
print(type(15)) # Affiche <class int>
print(type(15.0)) # Affiche <class float>
print(type("Bonjour")) # Affiche <class str>
```
----
## Expressions et opérateurs
Il est pratique de pouvoir écrire des expressions simples, mais c'est mieux de pouvoir faire des
calculs avec. Il existe des opérateurs mathématiques, mais aussi de comparaison etc.
```python {.numberLines}
a = 15
chaine = "bonjour " + "les amis" # intuitif
produit = 15 * 60 # intuitif aussi
a_double = a * 2 # ça aussi
```
Exemples d'opérateurs : `+`{.python}, `>`{.python}, `**`{.python}, `%`{.python}, `and`{.python},
`&`{.python}, `|`{.python}, `^`{.python}, `is`{.python}, `in`{.python}...
----
### Opérateurs arithmétiques
- Arithmétiques : `+`{.python}, `-`{.python}, `*`{.python}, `/`{.python}
- Modulo : `%`{.python} (reste de la division entière)
- Division entière : `//`{.python}
- Exposant : `**`{.python} (ex. `2 ** 3 == 8`{.python})
----
### Opérateurs de comparaison
- `>`{.python}, `<`{.python}, `>=`{.python}, `<=`{.python}
- `==`{.python} (équivalent), `!=`{.python} (non équivalent)
----
### Opérateurs booléens en langage naturel
- `a and b`{.python} : Vaut `True`{.python} si `a` **et** `b` valent `True`{.python}, sinon `False`.
- `a or b`{.python} : Vaut `True`{.python} si `a` **ou** `b` vaut `True`{.python}, sinon `False`.
- `a in b`{.python} : `True`{.python} si `a` fait partie de la collection ou séquence `b`.
- `a not in b`{.python} : `True`{.python} si `a` ne fait pas partie de la collection ou séquence `b`
.
- `a is b`{.python} : `True`{.python} si `a` est la même référence que `b`. Utilisé avec les booléens
et `None`{.python} notamment.
- `a is not b`{.python} : `True`{.python} si `a` n'est pas la même référence que `b`.
- `not a`{.python} : Convertit `a` en booléen, et renvoie la valeur opposée.
----
#### Opérateurs booléens (avancé)
Si `a`{.python} et `b`{.python} ne sont pas des booléens, la règle plus générale pour l'évaluation
des expressions utilisant des opérateurs booléens est la suivante :
- `a and b`{.python} : Vaut `a` si `a` est équivalent à la valeur fausse, sinon vaut b.
- `a or b`{.python} : Vaut `a` si `a` est équivalent à la valeur vraie, sinon vaut `b`.
----
### Opérateurs booléens (avancé, exemples)
Toutes les instructions suivantes affichent `True`{.python} :
```python {.numberLines}
print(("hello" and 0) == 0)
print((0 and "hello") == 0)
print(("hello" and 2) == 2)
print(("hello" or False) == "hello")
print((False or "") == "")
print("hello" is not False)
```
Ici, on teste en premier que `"hello" and 0`{.python} vaut bien `0`{.python}, et ainsi de suite…
----
### Exemple avec quelques opérateurs
```python {.numberLines}
distance = 17543 # 17543 mètres
kilometers = distance // 1000 # division entière par lots de 1000 mètres
remaining_meters = distance % 1000 # reste après la division entière
print(kilometers, "kilomètres et", remaining_meters, "mètres")
print(2 ** 3) # affiche `8`
print(2 <= 3) # affiche `True`
```
Petit exemple de code avec conversion d'ordres de grandeur proportionnels (ex. mètres),
et usage d'autres opérateurs pour des opérations plus classiques.
----
`a += b`{.python} est équivalent à → `a = a + b`{.python}
Des opérateurs similaires sont disponibles pour les autres opérations arithmétiques :
`-=`{.python}, `*=`{.python}, `/=`{.python}, `**=`{.python}, `//=`{.python}
et `%=`{.python}
(_dans la pratique, on n'utilisera que très rarement autre chose que les opérations de base, mais le principe est là_)
----
### Priorité des opérateurs en Python
| Opérateurs | Signification |
|------------------------------------------------------------------|---------------------------------------------------|
| `()` | Parenthèses |
| `**` | Exposant |
| `+x`, `-x`, `~x` | Plus unaire, Moins unaire, NOT binaire |
| `*`, `/`, `//`, `%` | Multiplication, Division, Division entière, Modulo |
| `+`, `-` | Addition, Soustraction |
| `<<`, `>>` | Opérateurs de décalage binaire |
| `&` | AND binaire |
| `^` | XOR binaire |
| `\|` | OR binaire |
| `==`, `!=`, `>`, `>=`, `<`, `<=`, `is`, `is not`, `in`, `not in` | Comparaisons, Identité, Opérateurs d'appartenance |
| `not` | NOT logique |
| `and` | AND logique |
| `or` | OR logique |
| `:=` | Expression d'assignation |
----
# Structures de contrôle
----
## Conditions
Dans tous les langages impératifs, on peut choisir de n'exécuter un bout de code que lorsque
certaines conditions sont vérifiées. En Python comme ailleurs, on utilise le mot-clé `if`{.python}
pour faire ce choix.
Le mot-clé `if`{.python} est suivi d'une expression booléenne (`True`{.python} ou `False`{.python}
ou équivalent) qui permet de savoir si on exécute du code ou pas.
```python {.numberLines}
a = 15
if a > 10: # l'expression de comparaison renvoie un booléen True
print("A est bien supérieur à 10.")
print("Le bloc de la condition a bien été exécuté.")
print("Exécuté quoi qu'il arrive")
```
. . .
Ce qui est exécuté pour cette condition est indenté de 4 espaces (on parle de bloc). Grâce à
l'indentation, Python sait où le code en question commence et où il s'arrête.
----
## Syntaxe : blocs vides
En Python, un bloc ne peut pas être vide (erreur de syntaxe), même si vous ne souhaitez rien y faire.
Si vous souhaitez écrire un bloc syntax(ct)iquement valide sans rien y faire, Python propose un mot-clé
spécifique à cet effet : `pass`{.python}. (Un commentaire ne suffit pas à former un bloc
valide car il est ignoré dans la grammaire Python)
```python {.numberLines}
if True:
pass # utiliser une ellipse (...) fonctionne aussi mais peu utilisé
```
----
## Conditions : `if…else`
Variante `if`…`else` :
On peut exécuter du code si une condition est vraie, mais on peut aussi exécuter du code si cette
condition ne s'est pas produite ! Le mot-clé `else`{.python} est au même niveau d'indentation que
le `if`{.python}, et apparaît **une seule fois au maximum** pour le `if`{.python} auquel il est associé.
```python {.numberLines}
a = 50
if a > 75:
print("A est supérieur à 75")
else:
print("A est inférieur ou égal à 75")
```
----
## Conditions : `if…elif…else`
On peut exécuter du code si une condition est vraie (`if`). Si elle est fausse, on peut exécuter du
code si une autre condition est vraie (`elif`), et ainsi de suite. La première condition vraie voit son bloc
exécuté, et l'interpréteur quitte ensuite la structure `if`{.python}.
Si un bloc `else`{.python} existe, il est toujours en dernier, et sera exécuté si toutes les conditions
le précédant ont été fausses.
```python {.numberLines}
vote = 3 # Valeurs entre 1, 2 et 3
if vote == 1:
print("Vous avez voté pour Andre")
elif vote == 2: # Le vote n'est pas 1, mais 2
print("Vous avez voté pour Beverly")
elif vote == 3: # Le vote n'est ni 1 ni 2, mais 3
print("Vous avez voté pour Carlos")
else: # Aucun des cas de figure du dessus
print("Vote incorrect")
```
----
## Faire des boucles : `for … in liste`
En programmation, une boucle consiste à répéter un bloc de code. En Python, le bloc est répété à
chaque fois qu'on parcourt un élément dans une liste. Par exemple, si on veut afficher les 10
premiers nombres, de 0 à 9, on utilise le mot-clé `for`{.python} avec la fonction `range`{.python} :
```python {.numberLines}
nombres = range(10) # une liste de 10 entiers de 0 à 9 inclus
for i in nombres: # Le bloc sera exécuté 10 fois
# La variable nommée i que nous avons déclarée sera modifiée par Python à chaque itération
print(i) # `i` contient tour à tour les nombres de 0 à 9
```
**À noter** : la variable `i` reste disponible après l'exécution de la boucle et contient la dernière
valeur assignée lors de la boucle.
----
### La fonction `range()`{.python}
La fonction `range()`{.python} fournie par Python permet de générer des suites arithmétiques de nombres
entiers. Elle fonctionne avec un, deux ou trois arguments selon l'objectif.
```python {.numberLines}
zero_to_nine = range(10) # stop (non inclus)
one_to_eight = range(1, 9) # start, stop
every_other_one = range(0, 10, 2) # start, stop, step
no_number_generated = range(10, 0, 2) # start, stop, step
```
----
## Faire des boucles : `while condition`
Python propose un autre type de boucle `while`{.python} dont le bloc est exécuté tant qu'une
condition est vraie (une expression convertie en booléen). Dès qu'elle devient fausse au début d'une
itération, Python quitte la boucle et passe à la suite.
```python {.numberLines}
nombre = 12
while nombre > 10:
print(nombre)
nombre = nombre - 1
```
**Attention** : Une boucle peut ne jamais se lancer, ou tourner indéfiniment !
Dans un terminal normal, (au clavier) `⌨️ Ctrl + C` interrompt le script. Dans un lancement de script avec PyCharm,
seul le bouton `⏹ Stop` peut l'interrompre.
----
## Exemple d'algorithme : Suite de Fibonacci
**Suite de Fibonacci** : Une suite commençant par les nombres 0 et 1. On peut générer itérativement les
éléments successifs de la suite simplement; chaque nouveau nombre de la suite est la somme des deux qui le précèdent.
Cela donne dans l'ordre :
`0 1 1 2 3 5 8 13 21 34 55 89 144 …`
Pour le résoudre, au choix, on utilise :
- Deux variables (avec ou sans _unpacking_), ou trois avec une variable temporaire.
- Une boucle `while`{.python} (pour définir quand on s'arrête d'afficher un nouvel élément)
----
### Fibonacci : unpacking
En Python, le "dépaquetage" (**unpacking**) est une forme d'assignation de variables où, lorsque
l'expression à droite du signe `=` est une séquence à `n` éléments (avec `n > 1`),
vous pouvez assigner la séquence à `n` variables :
```python {.numberLines}
a, b, c = (1, 2, 3) # a = 1, b = 2 et c = 3
a, b = (1, 2, 3) # erreur, trop d'éléments à dépaqueter
a = (1, 2, 3) # aucun problème, a est une séquence de 3 éléments
```
. . .
**Rappel** : si vous assignez la séquence à `1` variable, ça fonctionnera à nouveau, et votre
variable sera une séquence de `n` éléments.
----
## Interrompre des boucles
On peut interrompre une boucle `for`{.python} ou `while`{.python} avec deux mots-clés :
- `break`{.python},
- `continue`{.python}.
----
### Interruption : `break`{.python}
Lorsque l'on se trouve dans le bloc d'une boucle (seule la boucle directement englobante est concernée),
l'exécution d'une instruction `break`{.python} interrompt immédiatement la boucle en cours, qu'il s'agisse
d'un `for`{.python} ou d'un `while`{.python}.
L'utilisation classique du mot-clé `break` s'applique aux traitements où vous exécutez une boucle (potentiellement
avec un nombre d'itérations que vous ne contrôlez pas) à la recherche d'une information. Dans ce cas précis, si
vous trouvez l'information qui vous intéresse avant la fin naturelle de la boucle, il n'est alors plus nécessaire
de la laisser se poursuivre; typiquement, si vous trouvez votre information à la ligne 10 d'un fichier de 900 000
lignes, _pourquoi parcourir les 899 990 lignes restantes_ ?
----
### `break`{.python} : Exemple
Que fait cet exemple-ci ?
```python {.numberLines}
for number in range(10 ** 9):
if number == 23:
print("Valeur trouvée")
break
else:
print("Itération...")
```
----
### Interruption : `continue`{.python}
Lorsque l'on se trouve dans le bloc d'une boucle, l'exécution d'une instruction `continue`{.python} revient
immédiatement à la première ligne de l'itération suivante, ou autrement dit, ignore le reste du code de l'itération en cours.
L'utilisation d'un mot-clé `continue`{.python} peut **toujours** être remplacée par un `if`{.python}, plus lisible.
Cependant, ce mot-clé peut servir à réduire le niveau d'indentation d'un bloc complexe :
----
#### `continue`{.python} : Exemple
```python {.numberLines}
for x, y in zip(a, b):
if x > y:
z = process_z(x, y)
if y - z < x:
y = min(y, z)
other_actions()
```
. . .
Deviendrait comme suit.
```python
for x, y in zip(a, b):
if x <= y:
continue
z = process_z(x, y)
if y - z >= x:
continue
y = min(y, z)
other_actions()
```
Très peu de développeurs s'en servent car le gain est minime à part pour ces raisons d'indentation.
Les cas d'indentation extrême doivent pouvoir être simplifiés d'une autre manière.
----
## Bonus : Opérateur morse (walrus)
Sans rentrer dans le détail, on peut, depuis Python 3.8 (fin 2019), simplifier des boucles `while`{.python} avec un opérateur `:=`{.python}.
C'est un opérateur qui permet d'assigner une valeur à une variable, mais contrairement au mot-clé `=`{.python}, l'opérateur
forme une expression dont la valeur est l'opérande de droite. On peut ainsi écrire :
```python {.numberLines}
while (saisie := input("Saisissez oui :")) != "oui":
print("Saisie incorrecte")
```
. . .
au lieu d'écrire :
```python {.numberLines}
saisie = input("Saisissez oui :") # afficher l'invite et récupérer la saisie
while saisie != "oui":
print("Saisie incorrecte !")
saisie = input("Saisissez oui :")
```
----
## Opérateur morse : considérations
Attention toutefois, l'opérateur possède la priorité la plus basse de tous les opérateurs Python, et dans
certains cas, l'expression l'utilisant **doit** employer des parenthèses pour ne pas provoquer d'erreur de
syntaxe. **Dans le doute, utilisez toujours des parenthèses.**
```python {.numberLines}
a := 1 # Erreur de syntaxe
```
. . .
```python {.numberLines}
(a := 1) # Ça fonctionne, mais ça n'a aucun intérêt
```
. . .
```python {.numberLines}
print(a := 1 < 9) # Affiche True, et a == True
```
. . .
```python {.numberLines}
print((a := 1) < 9) # Affiche True, et a == 1, ce qu'on voulait
```
----
### Petit plus : expression ternaire
En Python, il est possible de décrire une expression qui vaut une valeur `a` si une condition est
vraie, et `b` sinon. Sans cet opérateur, on écrit :
```python {.numberLines}
if condition:
variable = a
else:
variable = b
```
. . .
Avec cet opérateur, on écrira plutôt :
```python {.numberLines}
variable = a if condition else b
```

View File

@ -0,0 +1,144 @@
---
title: Le droit aux erreurs
author: Steve Kossouho
---
# Le droit aux erreurs
----
## Exceptions
En Python, une exception est une erreur qui se produit pendant l'exécution de votre code. En tant que développeur, vous pouvez choisir ce que vous souhaitez faire quand une exception se produit, plutôt que de laisser le programme s'arrêter avec un message d'erreur. Les exceptions s'opposent entre autres aux erreurs de syntaxe, où l'interpréteur n'est pas même en mesure d'exécuter votre code.
----
### Jargon des exceptions
- `Lever une exception` : Ce que Python effectue lorsqu'on rencontre une erreur à l'exécution d'un script. Les exceptions générées par Python sont d'un type qui dépend de l'erreur rencontrée. Elles font toutes partie d'une hiérarchie dont le type le plus générique est nommé `Exception`. (voir programmation orientée objet et hiérarchie des classes d'exception)
- `Traceback` : <span style="color:red">Texte qui apparaît</span> dans la sortie d'erreur (en rouge) lorsque votre programme plante à l'exécution. C'est un récapitulatif des exécutions d'instructions qui ont mené directement à l'interruption de l'exécution, accompagné d'informations sur l'exception.
----
### Exemple d'exception
Dans l'exemple ci-dessous, on effectue une division par zéro, mais on pourrait accéder à un mauvais indice de liste, une mauvaise clé de dictionnaire, un fichier verrouillé…
```{.python .numberLines}
try:
15 / 0 # erreur évidente pour l'exemple
except ZeroDivisionError:
pass
```
Gestion des erreurs de division par zéro
----
![Hiérarchie des classes d'exception](assets/images/exception-class-hierarchy.png)
----
### À ne pas faire
Il est déconseillé d'utiliser directement la classe `Exception` (ou rien, d'ailleurs) avec la clause `except`;
ceci a pour effet de pousser votre code à réagir exactement de la même manière pour tous les cas de figure.
Si vous le tentez malgré tout, affichez ou conservez le traceback, vous me remercierez plus tard :
```{.python .numberLines}
import traceback
try:
print(15 / 0)
except Exception: # ou except:
traceback.print_exc() # Affiche le traceback dans la console
```
Voir [antipattern sur les exceptions pour plus d'info](https://realpython.com/the-most-diabolical-python-antipattern/)
----
### Un bloc `except`, plusieurs exceptions
On peut même avoir des blocs `except`{.python} prenant en charge plusieurs types d'exceptions, pour cela il suffit de les indiquer dans un tuple :
```{.python .numberLines}
try:
pass # ou faire autre chose
except (TypeError, ValueError):
pass
```
----
### Un bloc `try`, plusieurs gestions
On peut, à l'instar de la structure `if/elif/else`, faire suivre le bloc `try`{.python} de
plusieurs blocs `except`{.python} :
```{.python .numberLines}
try:
print("Code à essayer")
except ValueError:
print("Code pour ValueError")
except TypeError:
print("Code pour TypeError")
```
----
Si aucun des blocs `except`{.python} ne gère précisément l'erreur qui se produit dans le bloc `try`{.python},
alors l'interpréteur Python plante avec un traceback dans la console, comme attendu.
```{.python .numberLines}
try:
1 / 0
except NameError:
pass
```
----
### Code exécuté si tout se passe bien
La structure `try`{.python}...`except`{.python} peut être accompagnée d'un bloc `else`{.python}. Ce bloc
sera exécuté si aucun des blocs `except`{.python} précédents n'a été exécuté (comme dans une structure `if`{.python}) :
```{.python .numberLines}
try:
value = input("Saisissez un nombre entier :")
value = int(value)
except ValueError:
value = None
else:
# Exécuté si le bloc try n'est pas interrompu par une erreur
print(f"Merci, la valeur est de {value}.")
```
----
### Exécution garantie
Pour finir, il existe un bloc `finally`{.python}, dont le bloc est *toujours* exécuté, même si le programme doit planter :
```{.python .numberLines}
try:
15 / 0
except KeyError:
pass
finally:
print("Toujours exécuté")
```
En général, on imagine l'utiliser lorsqu'on souhaite penser à toujours libérer une ressource (connexion, fichier etc.)
----
On peut souhaiter également lever soi-même une exception pour notifier un problème à gérer.
C'est généralement le cas lorsque l'on fournit une API utilisable par d'autres développeurs.
```{.python .numberLines}
def do_something(value):
if not isinstance(value, int): # si value n'est pas un entier
raise TypeError("Value must be an integer.")
```

View File

@ -0,0 +1,284 @@
---
title: Découvrir les fonctions
author: Steve Kossouho
---
# Découvrir les fonctions
----
## L'utilité des fonctions
Une fonction, c'est :
1. Minimaliste si possible,
2. Réutilisable,
3. Un gain de temps et d'espace
----
## Syntaxe de déclaration de fonctions
```{.python .numberLines}
def my_first_function():
# Affiche un texte à chaque fois qu'on l'exécute
print("Voici le code de la fonction")
my_first_function()
```
----
## Typographie des fonctions
Les noms de fonctions se choisissent, par convention, comme toute autre variable :
- Tout en minuscules (ex. `exponential`)
- Mots séparés par des underscores (ex. `get_warning_count`)
- Peuvent contenir des chiffres sauf au début (ex. `log4`)
----
## Référence _versus_ appel de fonction
Lorsque vous définissez une fonction appelée `action` :
`action` est une variable de type fonction dont vous pouvez ensuite exécuter le code associé.
- `action` est donc la référence de la fonction.
- `action()`{.python} exécute le code de `action`, et récupère son expression de retour.
----
## Valeurs de retour d'une fonction
En mathématiques, une fonction renvoie toujours une valeur.
Par exemple, on peut déclarer `Pour tout x dans les nombres réels, f(x) = x × 15`.
En Python, pour qu'une fonction qu'on déclare renvoie une valeur (transmette une valeur au code qui l'exécute)
lorsqu'on l'exécute, il faut utiliser le mot-clé `return`{.python}.
Lorsque le mot-clé est rencontré par l'interpréteur, il interrompt immédiatement l'exécution de la
fonction et l'instruction qui a appelé la fonction récupère la valeur de l'expression qui a été retournée.
```{.python .numberLines}
def multiply_by_five(value): # Fonction qui prend un argument nommé value
return value * 5
result = multiply_by_five(10) # l'expression `10 * 5` est assignée à la variable
print(result) # affiche 50
```
----
### Valeurs de retour spécifiques
Le mot-clé `return`{.python} a quelques comportements implicites :
- Il peut être utilisé tout seul, sans expression : `return`{.python}. C'est équivalent à écrire `return None`{.python}, mais moins explicite.
- Si une fonction se termine sans avoir utilisé le mot-clé `return`{.python} :
- C'est toujours une fonction valide (on l'a vu dans le premier exemple)
- Implicitement, l'interpréteur Python retourne également la valeur `None`{.python}.
. . .
En clair, cela signifie qu'une fonction en Python renvoie **toujours** une valeur qui peut être assignée
à une variable, même si la valeur `None`{.python} a souvent peu d'utilité.
----
## Passer quelques arguments aux fonctions
Déclarer une fonction simple, c'est déjà pas mal, mais en déclarer une qui dépend de un ou
plusieurs arguments reçus en entrée, c'est bien plus utile !
Python propose au moins 4 types d'arguments différents, dont les usages sont évidemment différents,
et parmi ceux-ci, deux sont absolument essentiels et nous allons les aborder :
1. Arguments positionnels
2. Arguments par défaut (avec valeur par défaut)
----
## Arguments : Positionnels
Derrière cet adjectif un peu pompeux se cache le type d'argument le plus simple à déclarer et à utiliser :
```{.python .numberLines}
def f(x, y):
# Accepte deux arguments, affiche leur valeur
# Mais ne renvoie rien d'autre que `None`
print(x, y)
f(1, 2) # exécute la fonction, donc affiche "1 2"
```
. . .
Passer des valeurs à ces arguments est **obligatoire** lorsqu'on souhaite exécuter la fonction,
et les valeurs sont passées aux arguments dans le même ordre que dans la signature;
ici, à la ligne 5, `1`{.python} va dans `x`{.python} et `2`{.python} va dans `y`{.python}.
C'est de ce comportement que vient la notion de **positionnalité**.
----
## Arguments : Valeurs par défaut
```{.python .numberLines}
def f(x, alpha=15, beta=16):
# Accepte trois arguments, dont deux avec une valeur par défaut
print(x, alpha, beta)
return (x, alpha, beta) # renvoie un tuple
f(1) # ça marche, alpha vaut 15, beta vaut 16
f(46, 10, 12) # acceptable
f(99, alpha=23) # conseillé
f(99, 23) # équivalent à l'exemple du dessus
f(True, beta=23) # conseillé et obligatoire
f(True, , 23) # ceci est une erreur de syntaxe, on ne met pas deux virgules de suite en vrac
f(beta=23, x=50) # passer un arg. positionnel par son nom : déconseillé
```
. . .
Les arguments avec valeur par défaut sont facultatifs; cela signifie que si le développeur les omet
lorsqu'il exécute la fonction, Python choisira pour ces arguments leur valeur par défaut.
----
### Arguments : ordre
Malheureusement, mais c'est techniquement nécessaire, il y a un ordre imposé par l'interpréteur Python
lorsque vous déclarez les arguments acceptés par une fonction :
les arguments positionnels **doivent** être déclarés **en premier** dans la liste des arguments, s'il y en a.
. . .
Tous les arguments, positionnels (obligatoires) et par défaut, peuvent cependant être passés par leur nom,
mais ne faites jamais ça, vous induirez vos relecteurs en erreur :
```{.python .numberLines}
def f(x, alpha=15, beta=16):
return (x, alpha, beta) # renvoyer un tuple avec x, alpha et beta
# x peut être passé par son nom, non positionnellement, mais c'est déconseillé
print(f(beta=23, x=50))
```
----
## Bonus : Argument "étoile" (séquence)
Il existe un type d'argument, qui n'apparaît qu'une seule fois **maximum**, et dont le nom est précédé d'une étoile dans la signature de la fonction.
Cet argument apparaît après les arguments positionnels, et _de forte préférence_ avant les arguments avec valeur par défaut.
Lors de l'exécution de la fonction, cet argument contient toujours un tuple valide, même vide.
Également, comme cet argument contient toujours un tuple, il est généralement nommé avec un nom **au pluriel**.
```{.python .numberLines}
def stretchable(number, *words):
print(number, words) # words est toujours un tuple
stretchable(15, "word 1", "word 2", "word 3") # les 3 arguments vont dans le tuple `words`
stretchable(15, True, 3.14159) # les 2 arguments vont dans le tuple `words`
stretchable(1) # le tuple `words` sera vide
```
----
### Argument "étoile" : usage
Un argument de ce type accepte, lors de l'appel de la fonction, un nombre de valeurs arbitraire (0 ou plus), et ces valeurs sont passées comme des arguments positionnels (sans nom d'argument).
Cette technique est utilisée dans la fonction `print`{.python} pour pouvoir afficher à la suite plusieurs arguments.
----
On peut directement renseigner une liste pour ce type d'argument, en passant, lors de l'appel de fonction, une expression de liste (ou tuple) précédée par une étoile :
```{.python .numberLines}
def star_function(number, *words):
print(number, words) # words est toujours une liste
star_function(15, *["word 1", "word 2", "word 3"]) # raccourci
```
TODO: Si arg étoile suivi de défaut, passer *[], suivi d'une valeur positionnelle, cette dernière s'ajoute quand même à l'argument étoile, et donc obligation passer l'argument par défaut via son nom.
----
## Bonus : Argument "double-étoile" (dictionnaire)
Un dernier type d'argument, apparaît aussi une seule fois **maximum**, et généralement en tout dernier dans les arguments. Celui-ci s'utilise en passant des noms d'arguments qui n'existent pas ailleurs dans la signature de la fonction :
```{.python .numberLines}
def f_keywords(other=15, **kwargs):
print(kwargs) # toujours un dictionnaire
f_keywords(plop="Hello", foo=19) # vont dans le dictionnaire `kwargs`.
```
On appelle aussi cet argument l'argument "mots-clés", et s'appelle très fréquemment `kwargs`.
----
Si l'on appelle `f_keywords`{.python} avec un argument `plop`, Python regarde si un argument de ce nom existe. Si oui, la valeur de cet argument est modifiée. Si non, l'argument est ajouté comme association dans `kwargs`, de telle façon que `kwargs == {"plop": "Hello"}`{.python}.
Souvent utilisé par coquetterie, mais l'argument pourrait être généralement remplacé par le simple passage d'un dictionnaire dans un argument positionnel.
----
## Arguments : Ordre de déclaration
Si vous deviez avoir dans vos fonctions tous les types d'arguments que nous avons vus, l'ordre de leur
apparition **devrait** être le suivant :
1. `positionnel` (toujours premier)
2. `étoile` (utilisé positionnellement, toujours second)
3. `par défaut` (utilisé de préférence par nom, de préférence troisième)
4. `double-étoile` (utilisé par nom)
----
## Superbonus : Arguments spéciaux
----
### Arguments spéciaux : Slash
Depuis Python 3.8 (2019), vous pouvez contrôler l'utilisation de vos fonctions en ajoutant dans votre signature un argument
simplement nommé `/`{.python}. Il ne peut exister qu'une fois au maximum.
Lorsque vous spécifiez cet argument dans votre signature, **tous les arguments qui le précèdent ne
peuvent pas être spécifiés autrement que positionnellement** :
```{.python .numberLines}
def my_function(a, b, /, c, d):
print(a, b, c, d)
my_function(1, 2, 3, 4) # autorisé
my_function(1, 2, c=3, d=4) # autorisé
my_function(1, b=2, c=3, d=4) # impossible, b est positionnel uniquement
```
Lorsqu'un argument `/`{.python} ou `*`{.python} est présent, les arguments `*args`{.python} sont interdits. La raison est que, si un tel
argument est présent, on est automatiquement tenu de passer les arguments qui suivent par leur nom.
----
### Arguments spéciaux : Étoile
Vous pouvez contrôler l'utilisation de vos fonctions en ajoutant dans votre signature un argument
simplement nommé `*`{.python}. Il ne peut exister qu'une fois au maximum, et se trouver après l'argument
`/`{.python}.
Lorsque vous spécifiez cet argument dans votre signature, **tous les arguments qui le suivent ne
peuvent être spécifiés autrement qu'en précisant leur nom** :
```{.python .numberLines}
def my_function(a, b, c=3, *, d=4):
print(a, b, c, d)
my_function(1, 2, 3, d=4) # autorisé
my_function(1, 2, 3, 4) # impossible, d ne peut pas être utilisé positionnellement
```

View File

@ -0,0 +1,123 @@
---
title: Réutiliser du code
author: Steve Kossouho
---
# Réutiliser du code
----
En Python, on peut écrire des choses simples avec un seul script. Mais
souvent, et même pour des choses simples, on peut se retrouver à écrire du code, organisé de façon un peu plus complexe, ou même du code qui réutilise des fonctionnalités en dehors de votre script.
----
## Jargon : [Modules]{.naming} et [packages]{.naming}
Jusque là, tout ce que nous avons écrit, c'est des [modules]{.naming}. Ce sont des fichiers .py (vides ou non, ils peuvent contenir du code).
Il existe aussi les [packages]{.naming}. Ce sont des répertoires, pouvant contenir d'autres modules, et qui sont utilisables comme des modules (peuvent contenir du code). Pour pouvoir associer du code à ces répertoires et les considérer comme des packages, le langage Python impose que le code associé repose dans un fichier nommé `__init__.py` dans le répertoire. L'intérêt principal d'un package est d'y ranger d'autres packages et modules, pour organiser son code en arborescence cohérente.
----
## Typographie des modules et packages
Les modules et packages ont la même typographie que les variables (car Python les traite comme des variables de type `module`) :
- Tout en minuscules (ex. `mypackage`)
- <span style="color:red;">Sans espace ni tiret</span> (ex. `my_package`)
- etc.
----
## Bibliothèque standard de Python
Python est à la fois un langage et un exécutable interprétant des scripts écrits dans le langage.
L'interpréteur est toujours livré avec ce qu'on appelle la bibliothèque standard.
Il s'agit d'une immense bibliothèque de fonctionnalités, que l'on peut réutiliser dans nos programmes.
La bibliothèque propose des outils pour manipuler du texte, du réseau, des bases de données, des fonctions mathématiques etc.
[Liste des modules et packages de la bibliothèque standard](https://docs.python.org/3/library/)
----
Parmi les très nombreux modules de la bibliothèque standard, assez peu vous serviront régulièrement. En voici une liste :
- `random` : génération de nombres aléatoires
- `math` : fonctions mathématiques et trigonométriques
- `statistics` : fonctions statistiques, comme l'écart type.
- `pathlib` : outils pour gérer les chemins de fichier
- `datetime` : types pour gérer les dates
D'autres peuvent servir ponctuellement, comme `csv` ou `sqlite3` pour nos exemples à venir.
----
Comment accéder à ces nombreuses fonctionnalités ?
Par défaut, elles ne sont pas toutes accessibles dans votre code, excepté celles documentées dans les sections "built-in", et pour pouvoir les utiliser, il faut en faire la demande explicite dans vos modules via la notion d'`import` :
Variantes :
```{.python .numberLines}
# Les imports sont écrits au sommet d'un module
import math # vous avez maintenant une variable nommée `math`
from os import path # vous avez une variable `path`
import datetime as dt # datetime est utilisable en tant que dt uniquement
from math import sin, cos # importer plusieurs fonctions de la même bibliothèque
math.cos(math.pi / 2.0) # possible grâce à import math
cos(0) # possible grâce à from math import cos
path.join(…)
```
Chaque script qui utilise directement un _symbole_ doit toujours l'importer.
**Note** : Pour importer le contenu d'un module, l'interpréteur Python doit toujours exécuter le contenu dudit module, afin d'en connaître le contenu. Attention donc à la présence de `print` dans ledit module.
----
Note : Normalement, importer un package donne accès uniquement aux variables directement définies dans celui-ci, mais pas
aux sous-modules ou packages qui s'y trouvent. Si vous créez un package `package` contenant un sous-module `module1` :
```{.python .numberLines}
import package
print(package.module1) # ceci provoque une erreur
```
Cela ne fonctionne pas car Python ne charge pas directement les modules et packages présents à l'intérieur du package.
Pour y avoir accès, il faut que le package lui-même importe les éléments qui seront directement accessibles depuis celui-ci :
```{.python .numberLines}
from . import module1 # ajoute une variable module1 dans le package
```
`package/__init__.py`
----
## Faire un petit peu de calcul avec la bibliothèque standard
Avec l'outil que sont les imports, on peut avoir accès à de nombreuses fonctions de calcul.
Par exemple, on pourrait générer un nombre aléatoire et calculer un cosinus :
```{.python .numberLines}
import random
import math
random_number = random.randint(0, 100) # nombre entre 0 et 100 inclus
cosinus_result = math.cos(random_number) # cosinus du nombre aléatoire
```
----
## Bonus : Installer des paquets externes ([PyPI](https://pypi.org))
Avec PyCharm, on va installer simplement quelques paquets externes et utiliser leurs fonctionnalités :
1. `requests` : Faire des requêtes HTTP plus simplement
2. `unidecode` : Translittération et désaccentuation
3. `attrs` : Accélérer l'écriture de classes

View File

@ -0,0 +1,195 @@
---
title: Types supplémentaires
author: Steve Kossouho
---
# Types supplémentaires
----
## Un type pour les dates et heures
La bibliothèque standard nous offre plusieurs types de données pour gérer des moments dans le temps, avec le module `datetime`. Dans ce module, le type pour les dates avec heure s'appelle aussi `datetime`.
```{.python .numberLines}
from datetime import datetime
now = datetime.now()
then = datetime(2021, 1, 31) # arguments positionnels
then2 = datetime(2021, 1, 31, hour=12) # arguments par défaut
```
----
Les objets de ce type offrent un accès à plusieurs attributs, pour récupérer le jour, le mois, l'heure etc.
```{.python .numberLines}
from datetime import datetime
now = datetime.now()
print(now.day, now.date()) # affiche le jour du mois, et la date sans l'heure
```
----
## Opérations arithmétiques entre dates et intervalles de temps
Le module `datetime` propose un type qui représente un intervalle de temps, nommé `timedelta`.
C'est ce que l'on obtient quand on soustrait deux dates (le temps écoulé entre les deux).
Mais on peut aussi créer des objets `timedelta`{.python} et les ajouter (ou soustraire) à des dates.
```{.python .numberLines}
from datetime import timedelta, datetime
interval = timedelta(hours=15)
now = datetime.now()
then = datetime(2000, 1, 1)
interval2 = now - then
the_future = now + interval # dans 15 heures
print(interval2, the_future)
```
----
Les objets d'intervalle de temps donnent accès à peu d'attributs. Nous avons uniquement accès aux jours, secondes et microsecondes.
On préférera utiliser le résultats renvoyés par la méthode `total_seconds()` comme base pour nos propres conversions d'ordres de grandeur.
```{.python .numberLines}
from datetime import timedelta
interval = timedelta(hours=14, minutes=31, seconds=53)
print(interval.days, interval.seconds, interval.microseconds)
print(interval.total_seconds())
```
----
## Récupérer une date depuis un timestamp
Un [timestamp]{.naming} est une date exprimée sous la forme d'un nombre entier (ou flottant).
En général, un timestamp en programmation est le nombre de secondes depuis le
1er janvier 1970 à 00:00:00 UTC. On appelle ce moment l'epoch Unix.
On peut calculer un objet `datetime`{.python} depuis un timestamp, ou un timestamp depuis un objet `datetime`{.python}.
```python {.numberLines}
from datetime import datetime
timestamp = 1577836800
as_datetime = datetime.fromtimestamp(timestamp)
print(as_datetime)
```
----
## Convertir une date en chaîne
Pour convertir une date vers une chaîne, ou convertir une chaîne vers un objet de date, consultez toujours la même ressource :
[Table de référence pour le format de dates](https://docs.python.org/fr/3/library/datetime.html#strftime-and-strptime-format-codes)
----
### Éléments de date localisés dans la langue système
Pour une raison inconnue, probablement un bug sous certaines distributions Linux, Python n'utilise pas la langue configurée sur votre système,
et vous voyez un formatage tel que `Sunday` s'afficher au lieu de `Dimanche`.
Pas de panique, il existe une solution pour forcer Python à utiliser la langue que vous désirez.
```{.python .numberLines}
import locale
locale.setlocale(locale.LC_ALL, "") # utilise les paramètres de langue de l'utilisateur
```
[Documentation sur setlocale](https://docs.python.org/fr/3/library/locale.html)
----
Pour convertir un objet `datetime` vers une chaîne, grâce à la table de référence, c'est simple :
```{.python .numberLines}
from datetime import datetime
now = datetime.now()
text = now.strftime("%d/%m/%Y %H:%M et %S secondes.")
other = f"{now:%d/%m/%Y %H:%M et %S secondes}" # via des f-strings
print(text, other)
```
----
## Convertir une chaîne en date
Dans l'autre sens, c'est à peine plus compliqué :
```{.python .numberLines}
from datetime import datetime
text = "24 12 2003 15:17"
moment = datetime.strptime(text, "%d %m %Y %H:%M")
print(moment)
```
----
## Bonus : Fuseaux horaires
Par défaut, les objets de date que vous confectionnez sont dits naïfs, car ils ne contiennent pas d'information de fuseau
horaire. Vous pouvez créer ou modifier des objets pour porter ces informations si vous en avez besoin.
La façon de s'en servir changera selon que vous utilisez Python 3.9 ou une version antérieure.
----
### Fuseaux horaires avant Python 3.9
Avant Python 3.9, il est conseillé d'installer `pytz` et `tzlocal` avec `pip`, puis de définir vos dates ainsi :
```{.python .numberLines}
import pytz
from datetime import datetime
moment = datetime(2013, 4, 16, tzinfo=pytz.timezone("Europe/Paris"))
moment2 = datetime(2010, 1, 1)
moment2 = moment2.replace(tzinfo=pytz.UTC)
```
----
Vous pouvez convertir votre date de façon à la représenter dans un autre fuseau horaire :
```{.python .numberLines}
import pytz, tzlocal
from datetime import datetime
moment = datetime(2013, 4, 16, tzinfo=pytz.timezone("America/Chihuahua"))
moment2 = moment.astimezone(tzlocal.get_localzone())
```
----
### Fuseaux horaires avec Python 3.9+
Python 3.9 ajoute enfin `zoneinfo`, qui couvre presque totalement les larges lacunes de `tzinfo`. (il demeure impossible
de connaître le fuseau horaire local)
```{.python .numberLines}
from datetime import datetime
from zoneinfo import ZoneInfo
moment = datetime(1975, 3, 1, hour=13, tzinfo=ZoneInfo("Europe/Madrid"))
```
----
## Bonus : Bibliothèques tierces pour les dates
L'écosystème Python propose de temps en temps des alternatives plus abordables que les
outils présents dans la bibliothèque standard. La gestion de dates n'y échappe pas et je peux proposer
deux bibliothèques élégantes et simples à comprendre pour Python :
- [Arrow](https://arrow.readthedocs.io/en/latest/) : Better dates and times for Python
- [Pendulum](https://pendulum.eustace.io/) : Python datetimes made easy

349
documentation/07-objects.md Normal file
View File

@ -0,0 +1,349 @@
---
title: Découvrir la programmation orientée objet
author: Steve Kossouho
---
# Découvrir la programmation orientée objet
----
## La programmation objet ? Quoi, pourquoi ?
La programmation orientée objet, c'est des outils de langage fournis par certains langages de programmation pour vous permettre de décrire et de manipuler des concepts (objets, types).
Lorsque l'on décrit un concept, on lui associe ce qu'on appelle des [attributs]{.naming} et [méthodes]{.naming} (comportements).
Pour commencer, on peut imaginer un système où l'on mettrait en contact des propriétaires de chiens,
avec la possibilité de créer des fiches de leurs chiens :
----
### Diagramme de classes UML
![Diagramme UML représentant une classe de chiens](assets/images/classes-uml-dog.jpg)
Exemple spartiate de diagramme de classes UML.
----
## Découvrir la syntaxe de l'objet
Dans les langages orientés objet, pour faire de l'objet, il faut écrire ou manipuler des **classes**.
Les **classes** sont des modèles utilisés pour créer des objets.
Dans l'exemple ci-dessous, Pierre et Marie sont des **objets** de la classe `Humain`.
```{.python .numberLines}
class Human:
name = None
def parler(self):
"""Fonction uniquement disponible sur les humains"""
print(f"{self} parle.")
pierre = Human() # nouvel objet de la classe Human
marie = Human() # encore un nouvel objet de la classe Human
pierre.parler()
```
----
## Typographie des classes
La typographie des classes conventionnelle est un peu différente (enfin !) de celle des variables, fonctions, modules et packages. En Python,
pour nommer une classe, on utilisera le [PascalCase]{.naming}
- Commence par une majuscule (ex. `Animal`)
- Ne contient jamais d'underscore;
- Les mots commencent par une majuscule (ex. `WildAnimal`)
- Les sigles et acronymes gardent toutes leurs majuscules (ex. `CRTMonitor`)
----
## Les attributs
Les attributs, ce sont simplement des variables, mais uniquement accessibles sur la classe ou ses objets.
Les attributs sont accessibles sur la classe et sur les objets de la classe. Tout objet instancié possède les mêmes valeurs d'attributs que la classe.
```{.python .numberLines}
class Animal:
name = None
animal1 = Animal() # Crée un nouvel animal et l'assigne à une variable
print(animal1.name) # Afficher la valeur de l'attribut `name`
```
[Exercice 1](https://github.com/sk-dwtoulouse/python-initiation-training/blob/main/exercices/07-objects/01-base-class.asciidoc#exercice-1)
----
## Attributs : manipulation
Préférez manipuler des attributs sur les objets plutôt que sur la classe. Le reste, c'est très simple, et on peut même ajouter de nouveaux attributs :
```{.python .numberLines}
class Dummy:
attribute1 = 19
item = Dummy()
item.new_attr = "Hello" # nouvel attribut
item.attribute1 = 60 # modifier un attribut
```
[Exercice 2](https://github.com/sk-dwtoulouse/python-initiation-training/blob/main/exercices/07-objects/01-base-class.asciidoc#exercice-2)
----
## Méthodes
Les méthodes, c'est exactement la même chose que les fonctions (sauf l'argument `self` qui est implicite) :
```{.python .numberLines}
class Dummy:
def dummy_func(self):
print(self)
item = Dummy()
item.dummy_func() # ben, et self ??
```
Seules les instances de la classe `Dummy`{.python} peuvent utiliser la méthode nommée `dummy_func()`{.python};
vous avez la certitude de ne pas utiliser cette méthode sur les objets d'une classe incorrecte.
----
## L'argument `self`
Quand vous écrivez des classes et que vous ajoutez des fonctions (on appelle ça des **méthodes**),
elles ont toujours un premier argument nommé `self`. Il récupère toujours la référence de l'objet sur lequel vous avez demandé à exécuter la méthode.
Quand vous appelez la méthode **sur un objet** (et pas sur une classe), ne passez pas de valeur
entre parenthèses pour l'argument `self` :
Python passe _implicitement_ l'objet sur lequel on a appelé la méthode à l'argument `self`.
```{.python .numberLines}
class Dummy:
def foo(self, number):
print(number)
dummy = Dummy()
# `dummy` est implicitement passé dans `self`
dummy.foo(15) # l'argument `number` reste obligatoire
```
[Exercice 3](https://github.com/sk-dwtoulouse/python-initiation-training/blob/main/exercices/07-objects/01-base-class.asciidoc#exercice-3-m%C3%A9thodes)
----
## Différences entre une classe et ses objets
- La classe sert de modèle pour instancier des objets
- Les nouveaux objets possèdent toutes les propriétés de la classe
- Les nouveaux objets vivent indépendamment les uns des autres
- Les objets sont des objets, la classe est une classe
----
## Instancier en passant des arguments
```{.python .numberLines}
class Car:
doors = 5
gearbox = "manual" # transmission
brand = None # marque du véhicule
```
Avec cette déclaration de classe, créer un objet et l'initialiser prend quelques lignes… :
```{.python .numberLines}
car = Car()
car.doors = 3
car.gearbox = "automatic"
car.brand = "Seat"
```
----
C'est un peu long, notamment si l'on doit initialiser plusieurs objets qui ont beaucoup d'attributs.
Il peut être intéressant de pouvoir instancier un objet d'une classe en utilisant des arguments,
qui seraient utilisés pour modifier les attributs du nouvel objet, ex. :
```{.python .numberLines}
car = Car(doors=3, gearbox="automatic", brand="Seat") # une ligne au lieu de 4
```
----
Pour ce faire, il faut définir ou redéfinir dans notre classe ce qu'on appelle en Python une
**méthode spéciale** (ou "dunder") nommée spécifiquement `__init__`. Le nom de cette méthode est
défini dans le langage Python.
Cette méthode, si elle existe dans notre classe, est exécutée lorsque l'on instancie un nouvel objet,
et les arguments passés entre parenthèses sont transmis à cette méthode :
```{.python .numberLines}
class Car:
doors = 5
gearbox = "manual"
brand = None
def __init__(self, doors=5, gearbox="manual", brand=None):
# Grâce à cette méthode, on peut créer un objet en passant des arguments
# avec les noms "gearbox", "doors" et "brand", tous facultatifs (None par défaut)
self.doors = doors # self est l'objet que l'on vient de créer, doors est un argument
self.gearbox = gearbox
self.brand = brand
lamborghini1 = Car(doors=3, brand="Lamborghini")
```
[Exercice 4](https://github.com/sk-dwtoulouse/python-initiation-training/blob/main/exercices/07-objects/01-base-class.asciidoc#exercice-4-red%C3%A9finition-de-linstanciation)
----
## Héritage  et Polymorphisme 
----
### Héritage
L'héritage est un concept simple à appréhender :
```{.python .numberLines}
class Animal:
life_expectancy = None
class Vertebrate(Animal): # Classe parente mise entre parenthèses
vertebrae_count = None
dog = Vertebrate()
# Informations sur l'objet et les classes
print(issubclass(Vertebrate, Animal)) # Vertebrate est-elle une spécialisation de Animal ?
print(isinstance(dog, Vertebrate)) # dog est-il un vertébré ?
print(isinstance(dog, Animal)) # dog est-il un animal ?
```
L'**héritage** permet de partir d'une classe de base, et de créer une autre classe plus spécialisée. Ici,
les vertébrés sont effectivement une sous-classe du règne animal. Ils ont les mêmes attributs que tous
les animaux (et les mêmes méthodes), mais ont peut-être des attributs et méthodes propres aux vertébrés.
----
### Polymorphisme
Le polymorphisme en Python, c'est le fait de pouvoir redéfinir une méthode dans une classe enfant :
```{.python .numberLines}
class Animal:
life_expectancy = None
def move(self):
print(f"Do something general with {self}")
class Vertebrate(Animal):
vertebrae_count = None
def move(self): # On redéfinit move
super().move() # appelle `move` comme défini dans la classe parente
print("Do something different with vertebrae.")
```
**Note** : Une méthode redéfinie peut posséder une signature différente de celle existant
dans la classe parente.
----
### Le polymorphisme et la fonction `super()`
Lorsque vous créez une classe enfant, qui redéfinit une méthode de la classe parente (ex. vous redéfinissez `move()`),
il va souvent vous arriver de souhaiter recopier le code de la méthode que vous aviez dans la classe parente, et y
ajouter quelques lignes.
Plutôt que d'en recopier le code, Python vous propose la fonction `super()`, utilisable uniquement
dans une méthode d'une classe qui utilise l'héritage. Cette fonction vous renvoie automatiquement
votre objet `self`, mais il est modifié de façon à ce que tout appel de méthode sur cet objet
exécute le code de la méthode tel que défini dans la classe parente.
----
## Bonus : Courte introduction aux décorateurs avec @staticmethod
Des fois, dans une classe, on souhaite placer des méthodes, qui sont utiles vis-à-vis de la classe,
mais qui ne sont pas directement liées à des objets de la classe. Pour simplifier, on aimerait aussi se
passer de l'argument `self`, et s'en servir comme de simples fonctions.
C'est possible, grâce au décorateur `@staticmethod`.
----
```{.python .numberLines}
class Car:
power_horse = None
wheel_count = None
@staticmethod
def create_car_from_file(path):
"""Function to create car from file."""
return Car()
```
La fonction est rangée dans la classe comme si cette dernière était un package ou un module.
Il ne faut pas en abuser mais c'est très pratique.
----
## Bonus : Introspection
L'introspection, c'est le fait, dans votre script, d'aller chercher des informations sur vos variables
alors même que le programme est en train de tourner. C'est utilisé pour vérifier les types des variables,
récupérer les noms des attributs d'un objet, etc.
----
### Quelques fonctions d'introspection
```{.python .numberLines}
class Car:
wheel_count = 4
car1 = Car()
print(getattr(car1, "wheel_count", None)) # renvoie car1.wheel_count, ou None si introuvable
print(setattr(car1, "gearbox", None)) # exécute car1.gearbox = None
print(hasattr(car1, "wheel_count")) # Renvoie si l'attribut existe dans l'objet
print(dir(car1)) # liste les noms d'attributs de l'objet
print(isinstance(car1, Car)) # car1 est-elle une voiture ?
print(type(car1)) # renvoie la référence de la classe de l'objet
```
----
## Superbonus : Encapsulation
Dans d'autres langages objet (pas en Python), l'encapsulation consiste, dans le corps d'une classe,
à indiquer si des attributs de cette classe sont accessibles depuis le reste du code. Cela permet aux
développeurs utilisant nos classes d'avoir la certitude qu'on ne touche pas directement à l'attribut.
En Python, le concept d'encapsulation n'existe pas, **mais** on peut s'en approcher d'une certaine façon...
----
Nommer un attribut en le faisant précéder d'un double underscore (`__`) provoque un comportement spécifique
de l'interpréteur Python : l'attribut est accessible avec son nom dans les méthodes de la classe,
alors qu'il n'est accessible qu'avec un autre nom en dehors de la classe.
Voir l'[article de Dan Bader sur la signification des underscores](https://dbader.org/blog/meaning-of-underscores-in-python).
----
```{.python .numberLines}
from uuid import UUID, uuid4
class Person:
__uuid: UUID = None
def __init__(self):
self.__uuid = uuid4()
# Tester le "name mangling d'attributs"
if __name__ == "__main__":
person = Person()
print(getattr(person, "__uuid", "not found"))
print(getattr(person, "_Person__uuid", "not found"))
```

View File

@ -0,0 +1,77 @@
---
title: Informations des répertoires et fichiers
author: Steve Kossouho
---
# Exploiter le système de fichiers
----
## Le package `pathlib`
Le module `pathlib` de Python 3.4+ (2014) offre des classes orientées objet pour manipuler les chemins de fichiers. Il s'agit
d'une interface moderne orientée objet pour compléter ou remplacer ce qui se faisait avec le module `os.path`.
----
### Les objets `pathlib.Path`
L'objet `Path` du module `pathlib` représente un chemin de fichier général et offre des méthodes et des propriétés
pour effectuer diverses opérations sur ledit chemin.
```python {.numberLines}
from pathlib import Path
# Créer un objet Path
p = Path('/chemin/vers/fichier')
```
Créer un chemin. _Le chemin peut ou non exister réellement sur le système local, la seule contrainte étant qu'il soit
valide par rapport à la norme POSIX (et Windows)_.
----
### Méthodes utiles des objets `pathlib.Path`
Les objets `Path` ont plusieurs méthodes utiles pour gérer un chemin de fichier ou de répertoire. Parmi les outils
qui renvoient les informations les plus simples:
```python {.numberLines}
from pathlib import Path
# Imaginons que bob est un répertoire
f = Path("/home/bob")
# Vérifier si un chemin existe
print(f"Le chemin existe : {f.exists()}")
# Générer un objet Path qui est un répertoire enfant de bob
f2 = f / "morane"
# M'affiche "/home/bob/morane"
print(f2)
# Vérifier si un chemin est un répertoire
if p.is_dir():
print("C'est un répertoire.")
# Vérifier si un chemin est un fichier
if p.is_file():
print("C'est un fichier.")
```
----
### Parcourir récursivement des fichiers avec `pathlib.Path`
La méthode `rglob()` des objets `Path` peut être utilisée pour parcourir récursivement des fichiers.
```python {.numberLines}
from pathlib import Path
# Parcourir récursivement tous les fichiers .txt
for fichier in Path('/chemin/vers/répertoire').rglob('*.txt'):
print(fichier)
# Parcourir tous les fichiers .txt du répertoire, sans récursion
for fichier in Path('/chemin/vers/répertoire').glob('*.txt'):
print(fichier)
```

View File

@ -0,0 +1,124 @@
---
title: Manipuler des fichiers texte XML
author: Steve Kossouho
---
# Découvrir comment décoder des arborescences XML
----
## Structure d'un fichier XML
Le format XML (_Extensible Markup Language_) est un format de texte permettant de représenter des données
hiérarchiques (avec un élément principal) arbitraires. Le contenu d'un document de ce type utilise une syntaxe
à base de balises telles qu'on les retrouve en SGML ou HTML :
```xml
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
</CATALOG>
```
Extrait de document d'exemple. [Lien du document](https://www.w3schools.com/xml/cd_catalog.xml)
----
## Définition de la structure d'un document XML
Professionnellement, il peut être intéressant pour un producteur, ou un consommateur de fichiers
XML d'avoir accès à un document normalisant le contenu possible d'un fichier XML.
Pendant un temps, seules les [DTD](https://fr.wikipedia.org/wiki/Document_type_definition) existaient
mais étaient écrites dans un format assez indigeste. En 2001, le [XML Schema](https://fr.wikipedia.org/wiki/XML_Schema)
est plus accessible, et certains outils Python sont capables d'utiliser ces documents pour valider une
structure XML.
----
## Utilisation du XML en Python
Si l'on connaît la structure d'un document XML, il est possible de le lire facilement en Python.
Même si les packages de la bibliothèque standard fonctionnent, ils sont **spécifiquement**
indiqués comme sensibles à des attaques de document malicieusement formé (typiquement XML Bomb, attaque sur le
système de références en XML).
Parmi les autres bibliothèques, la plus utilisée dans l'écosystème Python semble être [LXML](https://lxml.de)
```{.bash .numberLines}
pip install lxml # pour installer la bibliothèque externe
```
----
### Exemple de démo de LXML
Pour naviguer dans un document XML, il existe plusieurs façons de faire :
- Méthode récursive, où l'on récupère un élément pour parcourir ses enfants
- Méthode XPATH, où l'on référence des éléments par rapport à leur "chemin" dans le document
La plus simple des méthodes disponibles consiste à se baser sur le XPATH pour trouver des éléments :
```{.python .numberLines}
from lxml import etree
tree = etree.parse(r"source.xml") # récupère l'élément racine
# Récupérer les éléments de la racine CATALOG qui ont le nom CD
items = tree.xpath("/CATALOG/CD")
```
- [Guide complet sur le XPath](https://www.ionos.com/digitalguide/websites/web-development/xpath-tutorial/)
- [Exemple plus simple sur le XPath](https://www.w3schools.com/xml/xpath_syntax.asp)
----
### Parcourir des résultats XPATH
Dans l'exemple précédent, nous avons pu récupérer, via une "requête" XPATH, un possible ensemble d'éléments.
Ces éléments peuvent être parcourus avec une simple boucle `for`{.python} :
```{.python .numberLines}
from lxml import etree
tree = etree.parse(r"source.xml") # récupère l'élément racine
# Récupérer les éléments de la racine CATALOG qui ont le nom CD
items = tree.xpath("/CATALOG/CD")
for cd in items:
for attribute in cd:
# afficher le nom et les attributs, puis le texte
print(attribute.tag, attribute.attrib, attribute.text)
```
----
## Propriétés des éléments `_ElementTree`
Les éléments du modèle de document sont représentés sous la forme d'instances de la classe
`_ElementTree`. Pour en extraire des données, de nombreuses méthodes sont documentées officiellement,
mais les attributs les plus simples à retenir sont les suivants :
- `attrib` (`dict`{.python}) : renvoie les attributs de la balise;
- `tag` (`str`{.python}) : renvoie le nom de la balise;
- `text` (`str`{.python}) : renvoie le texte direct de la balise.
Également, un élément peut être parcouru avec une boucle `for`{.python} et renverra à
chaque itération chacun de ses éléments enfants, même si la méthode suivante existe aussi :
- `getchildren()`{.python} : renvoie un itérable des éléments enfants.
----
## Documentation officielle de LXML
- [Documentation officielle (en anglais)](https://lxml.de)
- [Tutorial LXML par Oxylabs](https://oxylabs.io/blog/lxml-tutorial)
- [Sérialiser des objets Python en XML](https://pypi.org/project/lxml-dataclass/)

View File

@ -0,0 +1,410 @@
---
title: Manipuler des fichiers texte
author: Steve Kossouho
---
# Découvrir comment manipuler des fichiers texte
----
## Écrire et lire des fichiers (version de base)
Python vous offre, sans import nécessaire, une fonction `open`{.python} vous permettant d'ouvrir des
fichiers pour les manipuler. Cette fonction semble provenir du module `io`{.python} de la
bibliothèque standard.
Pour ouvrir un fichier en écriture :
```python {.numberLines}
file = open("fichier.txt", "w", encoding="utf-8") # Ouvrir le fichier
file.write("Texte à écrire\nDeuxième ligne.\n")
file.write("Troisième ligne.")
file.close() # Toujours penser à le refermer, pour votre système
```
----
La fonction `open()`{.python} prend quelques arguments importants :
- `filename` : chemin de fichier relatif (au répertoire de travail) ou absolu
- `mode` : `"w"` pour write, `"r"` pour read, `"a"` pour append
- `encoding`: rendez-le explicite, et "utf-8" si possible
----
### Pages de code les plus fréquemment rencontrées :
1. `utf-8` : ±90% du temps (complètement rétrocompatible avec `ASCII`)
2. `windows-1252` ou `1252` : ±5% du temps (provient de Windows 95)
3. `latin-1` : ±5% du temps (aussi nommé `iso8859-1`)
4. `utf-16` : certains pays n'utilisant quasiment pas de caractères ASCII, ils produisent souvent de l'UTF-16 (ex. Chine, Russie)
----
### Plus : Échappement de caractères
- Échappements classiques 0 à 8 bits (_0 à 255_)
- Échappements de caractères 0 à 16 bits (_256 à 65535_)
- Échappements de caractères 0 à 32 bits (_65536 et plus_)
```python {.numberLines}
print("\x41 = \101 = A") # codes 8 bits, 2 caractères obligatoires
print("\u00E6 = æ") # codes 16 bits, 4 caractères obligatoires
print("\U0001F44D = 👍️") # codes 32 bits, 8 caractères obligatoires
```
[Algorithme UTF-8](https://en.wikipedia.org/wiki/UTF-8)
----
### Ouvrir un fichier en lecture
```python {.numberLines}
file = open("fichier.txt", "r", encoding="utf-8")
line1 = file.readline() # Lit une ligne, et avance le curseur à la ligne suivante
rest1 = file.read() # Lit le fichier jusqu'à la fin
file.close() # Toujours penser à le refermer, pour votre système
```
----
### Outils disponibles pour gérer des fichiers
Si l'on utilise des éditeurs de texte, on pourrait penser qu'il est facile de demander à Python de, par exemple :
```{.text .numberLines}
- Remplacer une ligne
- Lire une ligne arbitraire d'un document
- Supprimer une ligne
- Remplacer du texte
```
Aucune de ces fonctionnalités n'existe en standard, dans aucun langage; les logiciels qui vous permettent
ces manipulations sont obligés, souvent, de conserver une copie du document en mémoire (quand c'est possible)
et d'effectuer les manipulations en mémoire avant de réécrire tout ou partie du fichier.
Tout ce qu'on a d'utile, c'est :
```{.text .numberLines}
- Ajouter du contenu à la fin
- Réécrire le fichier
- Lire ligne par ligne du début, jusqu'à arriver à une ligne arbitraire
- Déplacer le curseur à une position en octets
```
----
Une bibliothèque externe Python propose des outils pour effectuer certaines de ces actions :
```bash {.numberLines}
pip install textfile
```
[Page PyPI du package textfile](https://pypi.org/project/textfile/)
----
## Écrire et lire des fichiers (gestionnaire de contexte)
Une version raccourcie du `f = open()`{.python} … `f.close()`{.python} consiste à utiliser un gestionnaire de
contexte (la formation Python intermédiaire explique le concept). La syntaxe consiste en ce qui suit :
```python {.numberLines}
with open("fichier.txt", "r", encoding="utf-8") as file:
# `file.close()` : appelé automatiquement en fin de bloc
text = file.read()
```
À la fin de l'exécution du bloc, une méthode spéciale est automatiquement appelée et ferme le descripteur
de fichier s'il est resté ouvert. Plus besoin d'exécuter manuellement `file.close()`{.python}
----
### Bonus: Ouverture simultanée de fichiers
Avec un seul bloc `with` … `as`, il est possible d'utiliser plusieurs gestionnaires de contexte en même
temps, par exemple, si l'on veut ouvrir un fichier en lecture, faire une conversion de données et les
écrire dans un fichier ouvert en écriture, on peut :
```python {.numberLines}
with open("input.txt", "r") as infile, open("output.txt", "w") as outfile:
data = infile.read().upper() # tout mettre en majuscules
outfile.write(data)
```
----
## Parcourir facilement les lignes d'un fichier
Il est possible, lorsque vous avez un descripteur de fichier ouvert, de parcourir facilement ligne à ligne
le contenu de votre fichier texte, en utilisant le mot-clé `for`{.python} :
```python {.numberLines}
with open("fichier.ext", "r", encoding="utf-8") as file:
for line in file:
print(line)
```
En interne, ici la fonction `open()`{.python} est exécutée en deux temps; un première fois pour renvoyer
le descripteur de fichier, et une seconde fois lors de la fin du bloc... Mais c'est un peu magique, et c'est
un sujet pour une formation plus avancée de Python.
----
## Formats structurés : JSON
Le **JSON** (JavaScript Object Notation) est un format de texte extrêmement populaire
pour partager des données hiérarchiques. Il a largement supplanté le XML dans les systèmes
modernes et les services web. Les données contenues dans un fichier texte au format JSON peuvent être
interprétées et transformées en données Python, telles que `dictionnaires` et `listes`, `nombres` etc.
----
### Lecture et écriture de fichiers JSON
Il est extrêmement facile de lire ou d'écrire du contenu depuis ou vers un fichier
représentant une structure JSON. [Documentation du module JSON](https://docs.python.org/fr/3/library/json.html)
```python {.numberLines}
import json
# Lire un fichier JSON
with open("fichier.json", "r", encoding="utf-8") as file:
data = json.load(file)
# Écrire un fichier JSON, l'objet à écrire doit être une liste ou
# un dictionnaire
with open("copie.json", "w", encoding="utf-8") as file:
json.dump(data, file, indent=2)
```
----
## Formats structurés : CSV
Le CSV est un format accessible et non propriétaire pour représenter des données tabulées (en table)
. C'est un format texte, utilisant, par défaut, des virgules et des retours à la ligne pour séparer
les lignes et colonnes.
La manipulation des CSV fait aussi partie de la bibliothèque standard de Python. [Documentation CSV](https://docs.python.org/3/library/csv.html)
----
### Parcourir les lignes d'un fichier CSV
Lire le contenu d'un fichier CSV est presque aussi simple que pour un fichier JSON, à quelques détails près :
```python {.numberLines}
import csv
with open("fichier.csv", "r", encoding="utf-8") as file:
reader = csv.reader(file, delimiter=",")
for row in reader:
print(row) # Affiche une ligne sous forme d'une liste de chaînes de caractères
```
Vous ouvrez d'abord votre fichier en lecture et en mode texte, comme tout fichier texte, puis vous créez un objet de type
lecteur, configuré pour lire et interpréter le contenu de lignes du document. Par défaut, le lecteur ne reconnaît que les
documents dont les cellules sont séparées par la virgule `,` et dont les lignes sont séparées par `\n`.
Heureusement, cet objet peut être configuré pour comprendre des fichiers avec des propriétés différentes.
----
### Paramètres de formatage d'un fichier CSV
Pour lire ou écrire un fichier CSV, il est possible de configurer les objets en charge d'interpréter ou de formater le
contenu du fichier. En lecture, vous configurerez un objet de type `reader`, et en écriture... `writer`. Dans les deux cas, la procédure sera la même.
Vous avez trois façons de configurer votre objet lecteur ou écrivain :
- Soit en passant un objet de type `Dialect`, un objet dont les attributs servent à configurer toutes les facettes du formatage d'un fichier CSV;
- soit en passant un nom d'objet de type `Dialect`, par exemple `"excel"` (séparateur virgule);
- soit en passant des arguments individuels qui correspondent aux attributs des objets `Dialect`.
----
#### Configuration via un objet Dialect
Pour définir votre propre `Dialect`, vous pouvez créer une classe héritant de `Dialect`, dans laquelle vous n'avez qu'à
redéfinir les attributs dont vous souhaitez changer la valeur. Voir [Classe Dialect du module CSV](https://docs.python.org/fr/3/library/csv.html#dialects-and-formatting-parameters)
pour connaître les valeurs de base.
```python {.numberLines}
import csv
class MonDialecte(csv.Dialect):
"""Dialecte CSV utilisant des // comme séparateur."""
delimiter = "//"
```
----
Une fois que votre dialecte est défini et prêt à l'usage, vous pouvez l'utiliser pour lire ou écrire un fichier :
```python {.numberLines}
import csv
class MonDialecte(csv.Dialect):
"""Dialecte CSV utilisant des // comme séparateur."""
delimiter = "//"
with open("fichier.csv", "r", encoding="utf-8") as file:
reader = csv.reader(file, dialect=MonDialecte())
for row in reader:
print(row)
```
----
Plutôt que de créer une classe de dialecte pour personnaliser la manipulation d'un fichier, il est possible de passer à l'argument `dialect`
une chaîne de caractères représentant le nom d'un dialecte prédéfini. Par défaut, les valeurs disponibles sont `"excel"`, `"excel-tab"` et `"unix"`.
```python {.numberLines}
...
reader = csv.reader(file, dialect="excel")
```
Vous pouvez aussi enregistrer des noms de dialecte supplémentaires en utilisant la fonction [`csv.register_dialect(name, dialect)`](https://docs.python.org/fr/3/library/csv.html#csv.register_dialect).
```python {.numberLines}
import csv
class MonDialecte(csv.Dialect):
"""Dialecte CSV utilisant des // comme séparateur."""
delimiter = "//"
csv.register_dialect("mondialecte", MonDialecte)
```
----
#### Configuration via les attributs de Dialect
Pour gagner du temps, il est possible, lorsque vous créez votre objet lecteur ou écrivain, de passer des arguments pour
préciser le format du fichier CSV à lire ou à écrire. Ces arguments ont les mêmes noms que les attributs de la classe `Dialect`, et ont le même usage.
```python {.numberLines}
import csv
with open("fichier.csv", "r", encoding="utf-8") as file:
reader = csv.reader(file, delimiter=";", doublequotechar=True) # etc., etc.
for row in reader:
print(row)
```
----
### Détecter le dialecte d'un fichier
Outre la configuration de dialecte manuelle, il est possible de demander à Python d'autodétecter les propriétés de formatage d'un fichier CSV,
et de générer automatiquement un objet `Dialect`. Pour cela, vous devez utiliser la classe `csv.Sniffer` :
```python {.numberLines}
import csv
with open("output.csv", "r", encoding="utf-8") as file:
dialect = csv.Sniffer().sniff(file.read()) # Lire le contenu du fichier pour détecter le dialecte
file.seek(0) # Revenir au début du fichier pour démarrer la lecture
reader = csv.reader(file, dialect=dialect)
for row in reader:
print(row)
```
----
### Exemple: Copier un fichier CSV vers un fichier cible
On peut avec le module CSV lire un contenu CSV dans un objet Python, puis réécrire le contenu
de cet objet dans un nouveau fichier CSV.
```python {.numberLines}
import csv
table = list() # Objet à utiliser pour stocker le contenu
with open("fichier.csv", "r", encoding="utf-8") as file:
reader = csv.reader(file, delimiter=",")
for row in reader:
table.append(row) # Ajouter la ligne comme élément de `table`
with open("copie.csv", "w", encoding="utf-8") as file:
writer = csv.writer(file, delimiter=";")
writer.writerows(table) # table doit être une liste de listes
```
----
### Lire chaque ligne sous forme de dictionnaire
Si votre fichier CSV contient des en-têtes, et que vous préférez les utiliser pour retrouver le
contenu d'une colonne, plutôt qu'utiliser son indice, vous pouvez procéder
à [l'utilisation d'un `DictReader`](https://docs.python.org/fr/3/library/csv.html#csv.DictReader) :
```python {.numberLines}
import csv
with open("example-file.csv", "r", encoding="utf-8") as csvfile:
reader = csv.DictReader(csvfile) # lit automatiquement l'en-tête pour définir les clés des dictionnaires
for row in reader: # lit les lignes suivantes, renvoyées sous forme de dictionnaires
print(row)
print(row["<texte de la colonne d'en-tête>"])
```
----
### Lire un CSV sans en-tête via dictionnaires
Si votre fichier CSV ne contient pas de ligne d'en-tête, mais que vous souhaitez quand même définir des en-têtes à
associer aux colonnes, vous pouvez utiliser un `DictReader` et préciser un argument `fieldnames=` :
```python {.numberLines}
import csv
with open("example-file.csv", "r", encoding="utf-8") as csvfile:
reader = csv.DictReader(csvfile, fieldnames=["col1", "col2"]) # définit les en-têtes
for row in reader: # lit les lignes suivantes, renvoyées sous forme de dictionnaires
print(row) # renvoie un dictionnaire avec des clés "col1" et "col2"
print(row["col1"])
```
----
### Écrire un fichier CSV
```python {.numberLines}
import csv
# Créer une liste de listes pour imiter une feuille de calcul
rows = []
for i in range(10):
rows.append([str(j + i) for j in range(10)])
# Sous Windows, il faut préciser un argument newline à la fonction open, sinon
# chaque ajout de ligne causera deux passages à la ligne
with open("output.csv", "w", encoding="utf-8", newline="") as file:
writer = csv.writer(file, delimiter=";")
writer.writerows(rows)
```
----
### Écrire un CSV avec des en-têtes
```python {.numberLines}
headers = ["nom", "prenom"]
content = [
{"nom": "Doe", "prenom": "John"},
{"nom": "Smith", "prenom": "Jane"}
]
with open("header-output.csv", "w", encoding="utf-8") as file:
# Initialiser le writer en précisant la liste des textes d"'en-tête
writer = csv.DictWriter(file, fieldnames=headers)
# Écrire la ligne d'en-tête
writer.writeheader()
# Écrire le contenu restant
writer.writerows(content)
```
Exemple avec un `DictWriter`

270
documentation/09-sqlite.md Normal file
View File

@ -0,0 +1,270 @@
---
title: Le SQL avec SQLite3
author: Steve Kossouho
---
# Premiers pas en SQL avec SQLite3
----
## DBAPI c'est quoi ?
Python propose une API unifiée standardisée pour accéder à des bases de données SQL (_Structured Query Language_), nommée DBAPI.
Les objets et méthodes ont toujours les mêmes noms et le même usage.
Il existe plusieurs bibliothèques compatibles pour accéder à des systèmes différents.
La bibliothèque standard de Python propose une API compatible pour **SQLite3**.
[**SQLite3**](https://www.sqlite.org/index.html) est un moteur de bases de données relationnelles au format fichier, facile à mettre en place, et adapté pour des logiciels de bureau (ex. Firefox l'utilise). Il n'est pas adapté pour être utilisé dans une application web en production, car il ne gère pas les rôles et droits, ni ne tolère les multiples connexions (en écriture notamment).
----
## Manipuler une base avec DBAPI
Pour travailler sur notre première base de données, nous allons découper l'exemple en quatre étapes.
----
### Étape 1 : Se connecter à la base
```python {.numberLines}
import sqlite3
connection = sqlite3.connect("intro.sqlite3", isolation_level=None)
connection.close()
```
Crée un nouveau fichier de base de données SQLite3, pour le moment vide.
----
### Étape 2 : Créer un schéma de base
On brode sur l'exemple précédent pour créer une nouvelle table.
Cette table ne possède pas de contrainte particulière et autorise à enregistrer des doublons
(mêmes prénom, nom et âge).
```python {.numberLines}
import sqlite3
connection = sqlite3.connect("intro.sqlite3", isolation_level=None)
cursor = connection.cursor() # cet objet est utilisé pour envoyer des requêtes
cursor.execute("""
CREATE TABLE IF NOT EXISTS person (
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
nom varchar(30) NOT NULL,
prenom varchar(20) NOT NULL,
age int unsigned NOT NULL
)""")
connection.close()
```
----
### Étape 3 : Ajouter des données
On ajoute ensuite quelques données dans notre nouvelle table :
```python {.numberLines}
import sqlite3
# Requête d'insertion utilisant la notation QMark (protection contre injections)
PERSON_INSERT_QUERY = "INSERT OR IGNORE INTO person (id, nom, prenom, age) VALUES (NULL, ?, ?, ?)"
connection = sqlite3.connect("intro.sqlite3", isolation_level=None)
cursor = connection.cursor()
...
cursor.execute(PERSON_INSERT_QUERY, ("Bouquet", "Carole", 63))
cursor.execute(PERSON_INSERT_QUERY, ("Connery", "Sean", 80))
cursor.execute(PERSON_INSERT_QUERY, ("Trintignant", "Jean-Louis", 53))
connection.close()
```
----
### Étape 4 : Requêter et parcourir des résultats
Comme pour les fichiers texte dans lesquels nous pouvons naviguer dans les lignes via une boucle `for`{.python},
exécuter une commande SQL destinée à renvoyer des résultats autorise à parcourir l'objet de type `Cursor`{.python} via une boucle `for`{.python} pour récupérer des résultats :
```python {.numberLines}
import sqlite3
connection = sqlite3.connect("intro.sqlite3", isolation_level=None)
cursor = connection.cursor()
...
# Exécuter une requête pour retrouver des données
cursor.execute("SELECT * FROM person WHERE age > 50")
for result in cursor: # s'arrête automatiquement quand il n'y a plus de résultat
print(result) # result est toujours un tuple correspondant à un résultat
connection.close()
```
----
### Bonus : Récupérer des résultats sous forme de dictionnaires
Il est possible de configurer l'objet de connexion SQLite3 pour récupérer des objets de type `sqlite3.Row`{.python} :
```python {.numberLines}
import sqlite3
connection = sqlite3.connect("demo.sqlite", isolation_level=None)
# Configure la connexion pour que les requêtes renvoient des objets Row
connection.row_factory = sqlite3.Row
cursor = connection.cursor()
...
cursor.execute("SELECT * FROM person WHERE age BETWEEN 20 AND 40")
for result in cursor: # type: sqlite3.Row
print(result, dict(result))
connection.close()
```
Les objets de type `sqlite3.Row`{.python} sont utilisables comme des dictionnaires, et permettent ainsi d'extraire des résultats en utilisant les noms des colonnes. Ils sont aussi convertibles directement en dictionnaires.
----
## Hors-programme : Requêtes universelles avec les ORMs
C'est bien d'envoyer du SQL à une base de données, mais le problème du standard SQL, c'est que
tous les systèmes de gestion de bases de données ne gèrent pas toujours le SQL de la même façon;
certains proposent leur propre syntaxe ou ne prennent pas en charge une partie du standard, etc…
Sur un projet de taille moyenne, et même de taille modeste, si vous écrivez tout votre code SQL à la main,
basculer d'un _SGBDR_ à un autre (par exemple pour un passage en production) nécessitera
très probablement de retoucher ou d'adapter vos requêtes.
----
Ce problème est, entre autres questionnements, à l'origine de l'existence des ORMs (`Object Relational Mapper`).
Ce sont des bibliothèques qui offrent une abstraction de la base de données et la remplacent par
l'écriture de code, non plus en SQL, mais dans votre langage **orienté objet** préféré,
pour représenter des schémas et manipuler vos données.
----
Les ORMs font généralement usage des concepts suivants :
| Objet | Correspond à... |
|--------------------|:----------------------------------------|
| Classe Modèle | Table de la base de données |
| Attribut de classe | Colonne d'une table |
| Instance de Modèle | Ligne d'une table de la base de données |
En plus de cela, le même code peut être utilisé pour basculer d'un SGBD à un autre (s'il est pris
en charge par l'ORM).
----
### Exemples d'ORMs Python connus :
- [SQLAlchemy](https://www.sqlalchemy.org/)
- [Peewee](http://docs.peewee-orm.com/en/latest/)
- [Pony ORM](https://docs.ponyorm.org/toc.html)
- [Django ORM](https://docs.djangoproject.com/fr/3.2/topics/db/queries/) (framework web)
----
## Bonus : Petite explication sur `isolation_level`
Dans notre premier slide contenant du code, nous avons défini un argument facultatif à la fonction `connect`, nommé
`isolation_level`. Celui-ci permet de définir le [niveau d'isolation](https://en.wikipedia.org/wiki/Isolation_(database_systems)) des commandes SQL que l'on envoie.
Si l'on passe la valeur `None`{.python} à cet argument, l'auto-commit est activé, ce qui signifie que nous n'avons plus besoin
d'appeler manuellement `commit()`{.python} lorsqu'on souhaite persister nos modifications. Ceci est normalement le comportement
par défaut pour toute bibliothèque DBAPI, mais pour une raison inconnue, cela n'est pas le cas pour `sqlite`. Le bug sera corrigé
dans Python 3.12.
----
En temps normal, si nous n'avions pas précisé ce paramètre :
- Ouvrir la connexion (sans autocommit)
- `connection.execute()`{.python} → ouvre une transaction
- `connection.execute()`{.python}
- `connection.execute()`{.python}
- Aucune modification n'est persistée tant qu'on n'appelle pas `connection.commit()`{.python}
Le fait de devoir `cnx.commit()`{.python} par défaut n'est pas spécialement intuitif, notamment
lorsqu'on débute en bases de données...
----
Grâce à notre paramètre `isolation_level`, le comportement est plus intuitif par défaut :
- Ouvrir la connexion (avec autocommit)
- `connection.execute()`{.python} → modification immédiate
- `connection.execute()`{.python} → modification immédiate
- `connection.execute("BEGIN")`{.python} → Ouverture explicite d'une transaction
- `connection.execute()`{.python} → modification non persistée
- `connection.execute("COMMIT")`{.python} → ferme la transaction et applique les modifications si
possible
----
## Bibliothèques Python compatibles DB API 2.0 :
- MySQL : `pip install mysql-connector-python`{.shell} ([doc](https://dev.mysql.com/doc/connector-python/en/))
- PostgreSQL : `pip install psycopg`{.shell} ([doc](https://www.psycopg.org/docs/))
- SQL Server : `pip install pymssql`{.shell} ([doc](https://pymssql.readthedocs.io/en/stable/))
- Oracle : `pip install cx_Oracle`{.shell} ([doc](https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html))
----
## Navigateur de bases de données intégré à Pycharm
Vous pouvez naviguer dans vos bases de données graphiquement dans PyCharm
grâce à un plugin de l'IDE, nommé *Database Navigator*.
Pour l'installer :
1. Cliquez sur `File`, `Settings`, puis la section `Plugins`.
2. Cliquez sur l'onglet `Marketplace`
3. Juste en-dessous de l'onglet `Marketplace`, dans le champ de recherche, saisissez "Database"
4. Vous devriez voir `Database Navigator` apparaître dans les résultats.
5. En regard de ce résultat, cliquez sur `Install`.
6. Si l'installation fonctionne, cliquez en regard du résultat sur `Restart IDE` pour redémarrer PyCharm.
----
### Configurer DB Navigator
Si vous venez d'installer le plugin, vous devez révéler le panneau de l'extension.
Le plus simple est d'utiliser le menu de PyCharm pour le trouver :
![Afficher DB Browser](assets/images/database-show-db-browser.png)
----
Vous pouvez déplacer le panneau avec un clic droit sur sa barre de titre, puis en cliquant sur `Move`.
Cela vous permettra de ne pas masquer le panneau de projet lorsque vous travaillez sur votre base de données.
----
### Configurer une base de données
Dans le panneau DB Browser, cliquez sur l'icône d'ajout pour créer une configuration, puis choisissez SQLite.
1. Dans l'onglet `Connections` et le sous-onglet `Database`,
2. Choisissez un nom à votre connexion. Il vous servira juste visuellement
3. Dans le tableau de l'option `Database files`, cliquez sur `sqlite.db`, puis cliquez sur le bouton qui apparaît au bout de la ligne de texte, afin de choisir le bon fichier de base de données SQLite à surveiller.
4. Dans l'onglet `Connections`, cliquez le sous-onglet `Details`, puis :
5. Réduisez toutes les options `Idle time...` à `1`
6. Décochez `Connect automatically`
7. Terminez la configuration en cliquant le bouton `OK`.
----
Vous pouvez ensuite voir dans le panneau `DB Browser` que votre connexion est prête, dans une arborescence.
Cliquez sur le nœud principal de l'arborescence, puis cliquez sur `Connect`
(il faut se connecter à la base pour pouvoir en explorer le contenu)
----
![Onglet Database](assets/images/database-configure-connection.png)
----
![Onglet Details](assets/images/database-configure-connection-settings.png)

View File

@ -0,0 +1,146 @@
---
title: Interfaces graphiques avec Pyside
author: Steve Kossouho
---
# Développer avec Pyside et Qt Designer
----
## Fichiers .ui et utilisation avec Python
Nous avons vu précedemment comment concevoir graphiquement des fenêtres dans l'outil Qt Designer,
puis comment les exploiter dans notre code Python. Nous avons d'abord utilisé la classe `QUiLoader`
et récupéré un objet de type `QWidget` (généralement de type `QMainWindow` précisément).
```python
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication
def button_slot():
"""Fonction utilisée comme slot."""
pass
if __name__ == '__main__':
app = QApplication()
window = QUiLoader().load("file.ui")
button = window.button
button.clicked.connect(button_slot)
window.show()
...
app.exec()
```
----
### Problème de l'approche "non objet"
Cette approche simple ne permet malheureusement pas de profiter de certaines fonctionnalités
essentielles. Par exemple, il n'est pas possible de définir des _slots_ via de simples fonctions dans lesquels
il serait possible de retrouver un widget qui a généré un _signal_.
La raison principale à cette catégorie de problèmes provient du fait que l'application Qt est gérée via l'objet
`QApplication` dans un thread distinct. Ce thread n'est pas en mesure de transmettre certaines références au processus
principal de votre programme, notamment les références d'objets générant un signal.
----
Le seul moyen correct de procéder (assez mal documenté) afin que votre code gérant vos widgets soit connecté au thread
de l'application Qt : Coder la gestion de votre fenêtre dans une classe héritant au moins d'une classe de la bibliothèque
Qt (ex. `QObject`). Hériter d'une telle classe vous offre d'ailleurs une méthode essentielle pour retrouver une référence
d'objet ayant généré un signal : `sender()`
----
Voici un exemple de code correct important une conception graphique d'un fichier .ui :
```python
from PySide6.QtCore import QObject, Slot
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget
class WindowManager(QObject):
"""Classe gérant l'interface graphique et contenant des slots."""
window: QMainWindow | QWidget = None
button: QPushButton = None
def __init__(self, *args, **kwargs):
# Ligne nécessaire pour exécuter l'initiation du QObject
super().__init__(*args, **kwargs)
self.window = QUiLoader().load("fichier.ui")
self.button = getattr(self.window, "button")
# Configurer le clic sur le bouton peut être configuré dans Designer
self.button.clicked.connect(self.button_clicked)
@Slot() # marquer correctement la méthode comme slot
def button_clicked(self):
"""Slot du clic sur le bouton."""
print(self.sender())
if __name__ == '__main__':
application = QApplication()
manager = WindowManager()
manager.window.show()
application.exec()
```
----
Dans l'exemple précédent, plusieurs éléments importants ont été rigoureusement respectés :
- La classe de gestion de l'interface et des slots hérite de `QObject` au moins;
- les slots sont décorés par la classe `Slot`, qui possède une méthode `__call__` renvoyant un décorateur (parenthèses nécessaires);
----
## Utiliser des widgets personnalisés dans Designer
Supposons que vous avez rédigé du code pour créer votre propre widget (soit personnellement dessiné soit composé d'autres sous-widgets).
Vous souhaitez utiliser Qt Designer pour concevoir votre interface, faisant usage dudit widget.
Malheureusement pour vous, seuls les widgets standard proposés par la bibliothèque Qt seront disponibles dans la boîte à outils
de Qt Designer. Heureusement, il est possible de personnaliser votre conception pour intégrer vos propres classes de widgets.
Pour cela vous devez insérer dans l'interface un widget duquel hérite votre classe personnalisée (ex. `QFrame`) puis le "**promouvoir**".
----
Pour cela, sélectionnez votre widget dans l'interface d'aperçu ou dans l'explorateur d'objet avec un clic droit.
Cliquez ensuite sur l'option `Promouvoir en…`. Une boîte de dialogue listant les widgets déjà promus s'affichera et vous proposera
de promouvoir le widget que vous venez de sélectionner.
![Promotion de widget](assets/images/gui-qtdesigner-widget-promotion.png)
----
Vous devez sélectionner la classe de widget de base (probablement celle déjà utilisée pour le widget à promouvoir),
puis indiquer le nom de la classe de widget à utiliser à l'exécution. Le nom du fichier d'en-tête n'est utilisé que pour
le langage C++, donc vous pouvez y saisir un point (`.`).
----
Une fois le fichier .ui regénéré, il vous faudra configurer votre objet `QUiLoader` pour y enregistrer les classes de
widgets personnalisées :
```python
from PySide6.QtUiTools import QUiLoader
from mymodule import MyWidgetClass
loader = QUiLoader()
# Register custom widget class so that the promoted widget works.
loader.registerCustomWidget(MyWidgetClass)
widget = loader.load("fichier.ui")
```
Au chargement du fichier XML, les références du nom de classe généreront correctement les instances du type approprié.
Notez que si votre classe de widget définit des propriétés supplémentaires par rapport à la classe dont elle hérite,
vous pouvez les ajouter et les initialiser dans le panneau de l'_éditeur de propriétés_ de Qt Designer.
----
## CSS dans les widgets
### Propriétés CSS
### Appliquer correctement son CSS
### Appliquer depuis un fichier
## Ressources
### Créer une ressource
### Compiler une ressource

View File

@ -0,0 +1,251 @@
---
title: Faire sa première interface graphique
author: Steve Kossouho
---
# Faire sa première interface graphique avec Python
----
## Exemples d'outils pour les interfaces graphiques
Depuis la moitié des années 80, on a commencé à voir s'introduire dans le domaine public des
ordinateurs livrés avec des interfaces graphiques, destinées à interagir simplement avec le système.
Avec le temps, les outils de programmation se sont peaufinés, au point qu'à ce jour, il existe de
très nombreux outils avancés de développement pour créer des interfaces graphiques, même avec Python.
Parmi eux, on rencontre fréquemment :
- [Qt](https://www.qt.io/) (PyQt ou PySide)
- [GTK](https://www.gtk.org/) (Python-GTK)
- [tkinter](https://docs.python.org/fr/3/library/tkinter.html) (bibliothèque standard)
- [wxWidgets](https://www.wxwidgets.org/) (wxPython)
----
### Choix de la communauté
Parmi les choix disponibles pour créer des interfaces, les développeurs utilisent très fréquemment
la bibliothèque Qt, de par la relative intuitivité de l'API que cette dernière propose. Également,
elle est fournie avec de nombreuses applications très utiles pour concevoir ses applications, qui
fonctionnent d'ailleurs assez bien sur de nombreux systèmes d'exploitation, comme Windows,
Linux ou encore Mac OS.
----
### L'interface par défaut de Tkinter
![Tkinter sous Ubuntu](assets/images/gui-api-tkinter.png)
(_Il est possible d'appliquer des thèmes qui imitent un thème, comme Win XP, Win 10, etc._)
----
### Exemple simple d'application Linux avec PySide
![PySide sous Windows, Linux, Mac OS](assets/images/gui-api-pyside.png)
----
### Choix de bibliothèque pour la formation
Pour ce chapitre, nous allons utiliser la bibliothèque Qt. Avec Python, nous avons accès à plusieurs
bibliothèques qui calquent l'API proposée par la bibliothèque originale en C++, telles que `PyQt` ou
`PySide`.
Pour des raisons de prise en charge officielle (de contenu packagé, et probablement de licence),
le choix se porte depuis quelques années sur `PySide`.
- [Documentation de PySide](https://wiki.qt.io/PySideDocumentation)
- [Exemples PySide](https://wiki.qt.io/PySide_Example_Applications)
----
## PySide6 avec Python (sous Linux et Windows)
En principe, pour travailler avec PySide sous Windows et Linux, il vous suffira d'installer le
paquet avec l'outil `pip`, aucun autre téléchargement ne semble être requis.
(Une intégration du thème des applications GTK dans PySide nécessite souvent d'utiliser le paquet
`pyside` installable via le gestionnaire de paquets de votre distribution Linux, et d'installer
également une bibliothèque pour Qt destinée à imiter les thèmes GTK.)
----
## Installer PySide avec pip
```{.bash .numberLines}
pip install pyside6 # environ 200 Mo
```
La commande installe la bibliothèque précompilée pour votre système d'exploitation, avec des programmes de gestion
inclus (**Qt Designer**, **Qt Resource Compiler**, etc.).
----
## Écrire une fenêtre simple
Pour afficher sa première application fenêtrée avec PySide, nous **devons** suivre quelques directives :
- Créer un objet `QApplication` (singleton). Il gère notre application Qt.
- Tous les widgets "vivront" automatiquement dans cet objet d'application.
```{.python .numberLines}
from PySide6.QtWidgets import QApplication, QMainWindow
if __name__ == "__main__":
application = QApplication()
window = QMainWindow()
window.show() # Affiche la fenêtre
application.exec() # essayez sans cette ligne...
```
----
## Concepts des interfaces graphiques Qt
Pour pouvoir parcourir plus en détail l'univers de **Qt**, il nous faut aborder certains concepts nécessaires pour un projet
complet autour de la bibliothèque :
- `Signal` : représente un type d'événement disponible sur un widget, ex. _cliqué_, _texte modifié_, etc.
- `Slot` : fonction exécutable lorsqu'un `Signal` est émis.
- `Property` : attribut d'un widget défini sous forme de méthodes, ex. `QMainWindow.windowTitle`
----
## Concevoir graphiquement une fenêtre et l'utiliser avec Python
Il existe des outils graphiques pour concevoir vos fenêtres, et vous pouvez ensuite importer votre travail
dans vos propres applications Python, ce qui vous permet de ne vous occuper que du code métier (ou presque).
Lorsque vous installez `PySide` avec `pip`, le package vous propose en ligne de commande d'utiliser
l'application **Qt Designer**, un outil visuel qui vous aide à créer vos fenêtres et boîtes de dialogue.
```{.bash .numberLines}
pyside6-designer & # lancer l'outil depuis un terminal… retirez le "&" sous Windows
```
----
### Interface principale de Qt Designer
![Qt Designer en action](assets/images/gui-qtdesigner.png)
----
#### Zone principale
Lorsque vous utilisez Qt Designer, vous obtenez une interface **WYSIWYG** (What You See Is What You Get),
vous permettant de configurer visuellement et précisément un widget (qu'il s'agisse d'une fenêtre ou d'autre chose).
Vous pouvez y sélectionner, redimensionner ou "reparenter" un widget. C'est beaucoup plus facile que
de le faire à la main. Vous pouvez également y déposer de nouveaux widgets en effectuant un glisser-déposer depuis la **Boîte de widget**.
Note : Vous pouvez aussi générer du code Python via l'outil, mais il est assez inélégant.
----
#### Inspecteur d'objet
Le panneau d'inspecteur d'objet vous montre deux informations :
1. L'arborescence des widgets contenus dans votre fichier actuel;
2. Chaque élément est affiché sur deux colonnes, d'abord le nom du widget (ex. `pushButton`), puis son type (ex. `QPushButton`).
Il peut être utile pour sélectionner facilement un widget, par exemple lorsque ce dernier est difficile à sélectionner
avec la souris dans la zone d'aperçu.
----
#### Éditeur de propriétés
L'éditeur de propriétés est un panneau traditionnel (comme dans Visual Basic 4) affichant la liste des propriétés disponibles sur un widget, par exemple ses _dimensions_, la _police de caractères à utiliser_, le _texte du bouton_ etc…
Les propriétés sont souvent disponibles à la fois en lecture et en écriture, mais certaines peuvent n'exister
que pour consulter un état d'un widget (lecture seule).
----
### Générer un script Python depuis Qt Designer
L'outil Qt Designer permet de générer des fichiers Python qui construisent votre fenêtre. Vous pouvez par la suite utiliser
le code généré dans votre projet Python (même s'il y a mieux à faire).
TODO: Ajouter une capture d'écran
----
### Charger un widget enregistré au format .ui
Les fichiers `.ui` générés par Qt Designer sont en réalité des fichiers XML indiquant l'arborescence de l'interface et
les propriétés à définir sur les widgets.
Également, ces fichiers contiennent des informations supplémentaires, comme les fichiers de ressources associés, mais
pour le cadre de la formation, nous ne sommes pas obligés de nous y intéresser. (ça peut attendre)
TODO: Promotion, Custom Signals et Slots, Ajout de propriétés etc.
```{.python .numberLines}
from PySide6.QtUiTools import QUiLoader
# N'oubliez pas l'application, naturellement
window = QUiLoader().load("fichier.ui") # QMainWindow
```
----
## Interagir avec les contrôles
PySide propose un système simple de signaux (événements) et slots (fonctions cibles), qui permettent
de réagir lorsque des événements se produisent sur des widgets.
Pour associer au clic sur un bouton l'exécution d'une fonction, nous devons utiliser
une méthode `connect` sur une référence qui porte le nom d'un événement, par exemple `clicked` :
```{.python .numberLines}
from PySide6.QtWidgets import QPushButton
def some_function_reference():
pass
button = QPushButton()
button.clicked.connect(some_function_reference)
```
Notez que PyCharm ne peut pas autocompléter `connect` à cause de la nature de la bibliothèque
(écrite en C++, avec des _stubs_ incomplets pour Python).
Ce problème semble ne pas pouvoir être résolu ni du côté de **JetBrains**, ni du côté de **Microsoft**.
Il semble qu'il pourrait être résolu du côté de **Qt**, qui refuse de corriger ses stubs Python…
----
## Qt Designer : mise en page adaptée au redimensionnement de fenêtre
Sous Qt Designer (ou QtCreator), aucun moyen simple n'a l'air disponible pour associer un layout
au widget central d'une fenêtre (c'est-à-dire, un widget de mise en page qui possède toujours les mêmes
dimensions que la fenêtre)…
Ça n'est pas intuitif, mais y parvenir est beaucoup plus simple qu'il n'y paraît.
----
![Fenêtre avec un layout non redimensionné](assets/images/gui-qtdesigner-layout-1.png)
----
- Vous pouvez cliquer droit dans un espace vide de la fenêtre, et ajouter un layout au **widget central** :
- Allez sur le menu contextuel `Mettre en page` (ou `Lay out`);
- Puis choisissez le layout utilisé pour le widget central (généralement `QGridLayout`);
- Tous les éléments qui étaient présents dans le widget central vont se disposer dans le nouveau layout.
- Vous pouvez aussi sélectionner le widget central dans l'inspecteur puis cliquer sur un bouton de layout dans la barre d'outils de l'application.
Le layout étant associé au widget central de la fenêtre, il se redimensionnera avec la fenêtre.
----
![Tout mettre dans un layout associé au widget central](assets/images/gui-qtdesigner-layout-2.png)

View File

@ -0,0 +1,193 @@
---
title: Documenter du code Python
author: Steve Kossouho
---
# Documenter du code Python
----
## Documenter du code ?
En Python, comme dans d'autres langages, vous savez que l'on peut introduire des commentaires dans le code.
Il suffit d'utiliser le symbole `(#)` dans une ligne de code, et ce qui suit sera considéré comme commentaire.
Mais saviez-vous que Python considère le concept de documentation comme étant à part ?
----
```{.python .numberLines}
print(__doc__) # Ne provoque pas d'erreur, mais affiche `None`
```
Les modules, packages, classes, fonctions et méthodes sont toujours, lorsque vous exécutez l'interpréteur Python,
équipés d'attributs nommés `__doc__`. (C'est l'interpréteur qui ajoute l'attribut automatiquement, entre autres
fonctionnements magiques)
Tout module possède implicitement une variable `__doc__` disponible dans son namespace/scope.
Cet attribut « magique » laisse entendre que la notion de documentation fait partie intégrante du langage Python.
----
### Documentation dans la bibliothèque standard
```{.python .numberLines}
import sqlite3
print(sqlite3.__doc__) # documentation du module SQLite3
```
Ici, on importe un module écrit par un développeur tiers, pour voir si l'on est capable de
récupérer quelque chose dans son attribut `__doc__`{.python}.
----
## Comment on fait pareil qu'eux ?
Pour documenter un module, c'est finalement assez simple :
```{.python .numberLines}
"""Texte de documentation du module."""
import os, sys
print(os.linesep, sys.getwindowsversion())
```
Il faut, en première instruction du module, écrire une chaîne de caractères. Par convention, il faut écrire une chaîne à trois guillemets. En Python, on appelle cela une docstring. (consultez PEP8 docstrings)
----
## Qu'est-ce qu'on peut documenter ?
On peut documenter pas mal de choses avec cette méthode :
1. Modules et packages;
2. fonctions et méthodes;
3. classes.
Les variables ne sont pas documentables directement : elles
récupèrent automatiquement la **documentation de leur classe**.
----
## Qu'est-ce qu'on met ?
Pour documenter, par exemple, un module, il y a deux façons de faire :
1. Si votre documentation tient en une phrase courte.
2. Si votre documentation va contenir davantage de texte.
----
### Documentation super courte
Vous pouvez considérer qu'une seule petite phrase peut servir à décrire votre module :
```{.python .numberLines}
"""Traitement des données d'épuration des eaux."""
```
----
### Documentation plus longue
```{.python .numberLines}
"""
Traitement des données d'épuration des eaux.
Paragraphes de description, d'explication du contenu du module.
Possibilité d'ajouter des sections, des exemples, soyez exhaustifs si vous le souhaitez !
"""
```
- Une phrase courte de résumé, sur sa propre ligne.
- Une ligne vide.
- Le corps de la documentation.
----
## Documenter une classe, fonction, méthode, package
- Pour documenter une classe, il suffit d'ajouter une docstring, juste en-dessous de la signature de la classe.
- Pour une fonction, ajoutez vos triples guillemets en-dessous de la signature de méthode/fonction.
- Pour un package, mettez une docstring au sommet du module `__init__.py`
----
## Outils de génération de documentation HTML etc.
Mieux qu'un long discours, une démo s'impose, avec la bibliothèque `pdoc`.
Un outil beaucoup plus long à mettre en place est `sphinx`, qui permet encore davantage.
```{.bash .numberLines}
pip install pdoc # installer la bibliothèque
pdoc [nom du module ou nom du script python] # lance un serveur pour tester
```
- [Documentation de pdoc](https://pdoc.dev/docs/pdoc.html)
- [Documentation de Sphinx](https://www.sphinx-doc.org/en/master/)
----
## Bonus : Donner des indications sur le type des variables et arguments
Depuis Python 3.5 (fin 2015), le langage propose un moyen d'indiquer aux développeurs, le type attendu de variables et d'arguments de fonctions.
**Attention cependant** : les indications en question n'ont aucune incidence sur l'exécution du code, c'est-à-dire que l'interpréteur les ignore. Par contre, les développeurs et les environnements de développement, eux, peuvent se servir de ces indications pour proposer de l'autocomplétion dans certains cas.
----
Pour annoter une variable et définir quel est son type attendu, on peut :
```{.python .numberLines}
from tartempion import MyClass
variable1: int = 15 # on indique qu'on attend un entier
variable2: str = "Hello" # on attend une chaîne
variable3: list = [] # on attend une liste
object1: MyClass = MyClass() # on attend un objet d'une classe
```
Un éditeur (tel que PyCharm) ou un outil de vérification en ligne de commande saura vous dire
si ailleurs dans votre code, vous ne respectez pas ces indications.
----
Le module `typing` de la bibliothèque standard permet de décrire de façon plus avancée des types attendus :
```python {.numberLines}
from typing import Optional, Union, Any, Literal, Callable
variable: Optional[int] = None # on attend `None` ou un entier
multi_types: Union[int, float] = 25 # on attend un entier ou float
collection: list[str] = [] # ne fonctionne que dans Python 3.9+
shapeshifter: Any = [] # tout et n'importe quoi, explicitement
finite_values: Literal["Monday", "Tuesday"] = "Monday" # Valeurs finies
function_reference: Callable = range # on attend une fonction
```
À partir de Python 3.11 (novembre 2022), il est possible de remplacer l'`Union` par un opérateur `|` :
```{.python .numberLines}
variable: int | float = 15 # soit un entier soit un flottant devrait y être associé
```
[Section de la documentation officielle de Typing](https://docs.python.org/3/library/typing.html#module-contents)
----
On peut faire la même chose avec des arguments de fonctions :
```{.python .numberLines}
# value et default devraient être des entiers
def do_some_important_things(value: int, default: int = 1):
print(value, default)
return None
```
----
Et on peut également décrire le type attendu du retour des fonctions et méthodes :
```{.python .numberLines}
def do_trivial_things() -> float: # on indique que la fonction retourne un flottant
return 100 / 15
```

275
documentation/12-logging.md Normal file
View File

@ -0,0 +1,275 @@
---
title: Journalisation d'événements
author: Steve Kossouho
---
# Journaliser des événements en Python
----
## Journaliser ?
Lorsque l'on exécute un programme au long cours, souvent un service, un serveur ou même une application graphique,
il est souvent important de pouvoir retracer ce qui s'est produit avant qu'une exception soit levée, ou qu'un comportement
inattendu montre le bout de son nez.
----
La journalisation est un outil formidable à cet effet, puisqu'elle vous permet de consigner, dans la console ou tout
autre support, une liste chronologique de messages et d'événements que vous pouvez retracer _a posteriori_. Pour peu
que vous consigniez assez de messages, ils peuvent être capitaux, ne serait-ce que pour fournir du support à
vos utilisateurs.
----
## À quoi ça ressemble ?
La journalisation est sensiblement identique sur tous les systèmes. Vous consignez des messages avec des niveaux de sévérité divers.
En voici un exemple arbitraire :
```log
2021-01-01 23:51:15 - DEBUG - Logging message 3
2021-01-01 23:51:10 - DEBUG - Logging message 2
2021-01-01 23:50:37 - WARNING - Logging message 1
```
----
Un administrateur ou un outil peuvent consulter ce genre de fichier
pour en savoir plus sur leur système. En marquant les messages comme possédant un niveau de sévérité, il est aussi possible de voir
rapidement quels messages doivent être traités en priorité.
----
Les niveaux de sévérité d'un système de journalisation sont les suivants :
- `DEBUG` : Pour les développeurs, remplace le `print`.
- `INFO` : Information standard (version, action effectuée etc.)
- `WARNING` : Attention à porter à un problème potentiel.
- `ERROR` : Une erreur s'est produite, gênante pour les utilisateurs.
- `CRITICAL` : Une erreur irrécupérable à corriger immédiatement s'est produite.
----
## Le module `logging`
La bibliothèque standard de Python propose un module, qui semble inspiré d'une API C++ ou Java (typographie) et qui offre
les outils nécessaires à organiser une bonne journalisation.
----
```{.python .numberLines}
import logging
logger = logging.getLogger(__name__)
```
Récupère le logger portant le nom du module en cours. Attention, si le module est celui que vous avez lancé spécifiquement, la variable
`__name__` (ou l'attribut `__name__` du module) prendra la valeur `"__main__"`.
----
```{.python .numberLines}
import logging
logger = logging.getLogger(__name__)
logger.log(logging.WARNING, "Message.")
logger.warning("Same message as before.")
```
Consignation de nouveaux messages.
----
## Système de loggers
Un logger est un objet que vous utilisez pour consigner de nouveaux messages dans votre système. Cet objet doit être associé à un ou plusieurs objets de type `handler`, qui sont des canaux de sortie pour le logger.
----
## Hiérarchie des loggers
Les loggers portent tous un nom. Le système de journalisation de Python fonctionne avec un système de hiérarchie, où le nom d'un logger définit où il se trouve dans la hiérarchie. Un message consigné par un logger est transmis à son logger parent, jusqu'au logger racine.
<div style="text-align: right">→</div>
----
![Exemple de configuration](assets/images/logging-hierarchy.png)
----
Dans l'exemple précédent,
* Un message consigné avec le logger nommé `data.excel` remonte jusqu'au au logger racine.
* Tous les handlers associés aux loggers rencontrés en remontant la hiérarchie sont utilisés pour traiter le message.
* Si aucun handler n'a été rencontré après être arrivé au sommet, le handler de dernier recours (`lastResort`) est utilisé pour consigner l'événement.
* Ce handler affiche les messages de sévérité `>= logging.WARNING`{.python} dans la console d'erreur (rouge).
----
## Utiliser les handlers
Le clou du spectacle consiste à configurer des handlers pour les associer à des loggers. Pour configurer correctement un handler, il nous faut définir :
- Son type (sortie fichier, console);
- Le format textuel des événements consignés;
- Son niveau de sévérité minimum pour accepter des messages.
----
### Configuration d'un handler
```{.python .numberLines}
import logging
handler = logging.FileHandler("filename.log", encoding="utf-8")
# Chaîne de formatage des événements
formatting = logging.Formatter("%(asctime)s - %(message)s")
handler.setFormatter(formatting)
# Le handler acceptera tous les messages >= DEBUG
handler.setLevel(logging.DEBUG)
```
[Référence des spécifieurs pour le format de texte](https://docs.python.org/3/library/logging.html#logrecord-attributes)
----
## Types de handlers
Les types de handlers disponibles sont disponibles ici :
- `logging.FileHandler` : sérialise dans un fichier ([documentation](https://docs.python.org/3/library/logging.handlers.html#filehandler)).
- `logging.StreamHandler` : sérialise dans un flux, généralement console (voir slide suivant).
- `logging.handlers.*` : handlers plus avancés, dont fichiers avec rotation.
[Handlers supplémentaires](https://docs.python.org/3/library/logging.handlers.html)
----
## Types de flux console
Pour les handlers vers un flux, voici la liste des flux classiques disponibles :
- `sys.stdout` : sortie console normale.
- `sys.stderr` : sortie console d'erreur (affiché en rouge).
*(voir le slide suivant pour l'utilisation de ces flux)*
----
## Ajouter un handler à un logger
Une fois un handler configuré, vous pouvez l'associer à un logger (puisque c'est sur le logger que vous allez utiliser des méthodes de journalisation).
```{.python .numberLines}
import logging
import sys
logger = logging.getLogger("logger.name")
handler = logging.StreamHandler(sys.stdout)
# Imaginons que l'on configure notre handler correctement
# (ici, il manque un format à spécifier)
# On peut *ajouter* notre handler à notre logger
logger.addHandler(handler)
```
----
Un logger peut également définir son propre niveau minimal de sévérité, qui sera utilisé pour potentiellement limiter la portée de ses handlers.
```{.python .numberLines}
import logging
import sys
logger = logging.getLogger("logger.name")
handler = logging.StreamHandler(sys.stdout) # il faudrait spécifier un format
logger.addHandler(handler)
# Limiter tous les handlers associés au niveau WARNING
logger.setLevel(logging.WARNING)
```
----
## Le logger racine
Le logger racine est souvent utilisé pour configurer une journalisation globale pour toute votre application, puisque tout logger remonte toujours ses messages vers celui-ci.
Pour le configurer, utilisez la variable `root` du module `logging` :
```{.python .numberLines}
import logging
# Exemple minimaliste
handler = logging.FileHandler("filename")
# Tous les loggers vont passer par ce handler
logging.root.addHandler(handler)
```
----
## Récapitulatif minimal
```{.python .numberLines}
import logging
# Les 3 types d'objets nécessaires à la configuration
logger = logging.getLogger("myname") # nouveau logger nommé "myname"
handler = logging.FileHandler("myfile.log", encoding="utf-8") # handler fichier
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") # format de messages
# Configuration du handler
handler.setFormatter(formatter) # configurer le handler
handler.setLevel(logging.DEBUG) # niveau de sévérité minimum
# Configuration du logger
logger.addHandler(handler) # ajouter le handler au logger
logger.setLevel(logging.INFO) # niveau de sévérité minimum du logger
```
----
### Exemple de logger à deux handlers
![Exemple d'usage d'un logger](assets/images/logging-building.png)
---
## Plus : Fichier de configuration
On peut configurer sa journalisation et créer d'une traite un ensemble de `loggers` via un fichier
de configuration (il est possible d'utiliser un dictionnaire), qui
sera stocké sous la forme d'un fichier ini. Le contenu à insérer dans ce fichier est décrit dans la section
[Configuration file format](https://docs.python.org/3/library/logging.config.html#configuration-file-format)
de la documentation Python.
Les fichiers **ini** ont une structure qui est toujours de la forme :
```ini
[section1]
property1 = value
property2 = value
[section2]
property3 = value
```
----
### Charger un fichier de configuration
Lorsque vous avez défini un fichier de configuration avec tous ses `loggers`, `handlers` et
`formatters`, vous pouvez l'utiliser via la fonction [`logging.config.fileConfig()`](https://docs.python.org/3/library/logging.config.html#logging.config.fileConfig) :
```{.python .numberLines}
from logging.config import fileConfig
# Initialiser des loggers depuis une fonction
# Le fichier devrait être stocké dans un dossier à l'arborescence bien
# choisie.
fileConfig("logging.conf.ini", disable_existing_loggers=False, encoding="utf-8")
```
----
## Bibliothèques simplifiées de journalisation
- [Loguru](https://loguru.readthedocs.io/en/stable/overview.html)

View File

@ -0,0 +1,129 @@
---
title: Classes et méthodes magiques
author: Steve Kossouho
---
# Personnalisation des classes
En Python, une classe peut être considérée comme un plan pour créer des objets. Cependant, il est possible de personnaliser le comportement de ces classes de plusieurs manières. Cette personnalisation peut inclure la définition de méthodes spéciales pour gérer le cycle de vie des objets, ainsi que la conversion d'objets en chaînes pour un affichage plus lisible.
----
## Méthodes pour gérer le cycle de vie d'un objet
Dans le cycle de vie d'un objet, il existe plusieurs points clés où nous pouvons intervenir.
Ce sont la création de l'objet (via un constructeur), son initialisation (via un initialiseur), et sa destruction (via un destructeur).
----
### Écrire un constructeur
En Python, le constructeur est une méthode spéciale qui est utilisée pour créer les objets.
Il est défini en déclarant dans une classe la méthode spéciale `__new__`.
```python
# Exemple d'une classe avec un constructeur
class Personne:
def __new__(cls, nom, age):
instance = super().__new__(cls)
instance.nom = nom
instance.age = age
return instance
```
Dans cet exemple, le constructeur `__new__` prend trois paramètres : `cls` (la classe elle-même), `nom` et `age`. `cls` est l'objet sur lequel la méthode de classe a été appelée, tandis que `nom` et `age` sont des arguments passés lors de la création d'une instance de la classe.
```python
# Création d'une instance de la classe Personne
p = Personne("John", 20)
```
----
### Écrire un initialiseur
En Python, l'initialiseur est une méthode spéciale utilisée pour initialiser les objets. Il est défini en utilisant le mot-clé `__init__`.
```python
# Exemple d'une classe avec un initialiseur
class Voiture:
def __init__(self, marque, modele):
self.marque = marque
self.modele = modele
```
Ici, nous avons une classe `Voiture` avec un initialiseur qui initialise les variables d'instance `marque` et `modele`.
```python
# Création d'une instance de la classe Voiture
v = Voiture("Tesla", "Model 3")
```
----
### Écrire un destructeur
En Python, le destructeur est une méthode spéciale qui est utilisée pour nettoyer les ressources. Il est défini en utilisant le mot-clé `__del__`.
```python
# Exemple d'une classe avec un destructeur
class Fichier:
def __init__(self, nom):
self.fichier = open(nom, 'r')
def __del__(self):
self.fichier.close()
```
Dans cet exemple, nous avons une classe `Fichier` qui ouvre un fichier lors de sa création et le ferme lors de sa destruction.
```python
# Utilisation de la classe Fichier
f = Fichier("mon_fichier.txt")
del f # Ceci appelle le destructeur et ferme le fichier
```
----
## Méthodes pratiques de conversion d'un objet
Il peut souvent être utile de convertir nos objets en chaînes, que ce soit pour un affichage plus lisible pour les utilisateurs (méthode `__str__`) ou pour une représentation qui peut être utilisée pour recréer l'objet (méthode `__repr__`).
----
### Méthode `__str__`
La méthode `__str__` en Python représente la classe sous une forme qui peut être lisible par l'utilisateur. Elle est appelée par la fonction `str()` et implicitement utilisée par la fonction `print()`.
```python
# Exemple d'une classe avec la méthode __str__
class Personne:
def __init__(self, nom, age):
self.nom = nom
self.age = age
def __str__(self):
return f"Personne: {self.nom}, Age: {self.age}"
p = Personne("John", 20)
print(p) # Affiche "Personne: John, Age: 20"
```
----
### Méthode `__repr__`
La méthode `__repr__` en Python représente la classe sous une forme qui peut être utilisée pour recréer l'objet. Elle est appelée par la fonction `repr()`.
```python
# Exemple d'une classe avec la méthode __repr__
class Personne:
def __init__(self, nom, age):
self.nom = nom
self.age = age
def __repr__(self):
return f"Personne('{self.nom}', {self.age})"
p = Personne("John", 20)
print(repr(p)) # Affiche "Personne('John', 20)"
```
Dans cet exemple, `repr(p)` renvoie une chaîne qui montre l'expression à écrire dans du code pour recréer l'objet `p`.

View File

@ -0,0 +1,27 @@
---
title: Exercices et démos Github
author: Steve Kossouho
mainfont: JetBrains Mono
---
# Github formation
Pour la formation Python initiation et approfondissement, j'ai créé un dépôt Github.
Le dépôt contient une liste d'exercices pour les sujets abordés dans la semaine :
[Dépôt Github formation](https://github.com/sk-dwtoulouse/python-initiation-training/tree/main/exercices)
----
## Github démos de code
Vous trouverez aussi des démos de code sur des sujets que l'on a vus ou abordés, mais avec un code légèrement plus
avancé/propre :
[Dépôt Github Démos](https://github.com/sk-dwtoulouse/python-initiation-training)
----
## Sites d'exos ou tutos Python
- https://data-flair.training/blogs/

View File

@ -0,0 +1,179 @@
---
title: Les imports
author: Steve Kossouho
---
# Plus d'imports
----
## Packages et modules
Nous avons vu comment créer packages et modules dans le tronc principal de la formation, mais ça vaut toujours le coup
d'en préciser un peu plus sur le concept. Pour rappel :
- **Module** : Un fichier Python contenant (ou pas) du code.
- **Package** : Un répertoire contenant un fichier Python portant le nom `__init__.py`
L'intérêt du concept de package pour les développeurs est de pouvoir ranger du code en arborescence.
----
Du point de vue de l'interpréteur Python, les modules et packages sont tous des objets de type `module` : ce sont des
objets qui contiennent du code. La différence se trouve au niveau du développeur :
- Les packages auront leur code représenté dans leur fichier `__init__.py`,
- Les modules étant des fichiers, leur code se trouve directement à l'intérieur,
(1/2)
----
- Les packages peuvent contenir d'autres packages et modules, et permettent d'organiser du code dans une arborescence,
- Tous les modules et packages à l'intérieur d'un package sont accessibles comme des attributs de l'objet représentant le package, en plus de ce qui est défini dans le fichier `__init__.py`.
(2/2)
----
## L'import de modules en mémoire
Lorsque vous faites un import en Python, l'interpréteur va chercher dans la bibliothèque standard
(et ailleurs si possible) ce que vous lui demandez d'importer. L'outil se comporte d'une façon similaire à
la suivante :
```python
from csv import DictReader
reader = DictReader([])
```
----
- Python cherche un module/package `csv` dans le répertoire courant;
- Python cherche le module/package `csv` dans les dossiers de `PYTHONPATH`;
- Dans le répertoire `site-packages` de l'environnement virtuel,
- Puis la cherche dans la bibliothèque standard, etc.
Si trouvé, `csv` est ajouté à un index interne des éléments déjà chargés en mémoire.
Pour importer quoi que ce soit depuis le module, le code entier du module devra être exécuté.
L'interpréteur ne donne ensuite accès qu'à `DictReader` au script qui l'a demandé.
----
### Chargement et optimisation
Lorsque l'interpréteur charge un module en mémoire, il garde une référence du module chargé (avec son chemin), de
façon à ce que si lors de l'exécution de votre programme vous venez à exécuter à nouveau un import du même module,
l'interpréteur optimise en ignorant votre commande : le module et son contenu sont déjà connus et en mémoire.
Et heureusement : Si 20 modules de votre projet importaient le même module, beaucoup d'énergie et de temps serait perdus sans ce mécanisme.
----
## Le PYTHONPATH
Ce qu'on appelle le `PYTHONPATH`, est une variable d'environnement qui vous permet d'indiquer à un interpréteur
Python lancé en ligne de commande, que les imports peuvent être trouvés relativement à une liste de répertoires que vous
pouvez indiquer. Dans un terminal `bash` :
```shell
export PYTHONPATH=/mon/repertoire:$PYTHONPATH
```
La commande est valide pendant l'exécution du terminal en cours.
----
## Le PYTHONPATH dans PyCharm
PyCharm est capable de configurer automatiquement pour vous le PYTHONPATH juste avant
d'exécuter vos scripts. Cela permet d'écrire des imports relatifs à l'arborescence de votre projet sans erreur.
![Dialogue de configuration de lancement de scripts](assets/images/x-imports-pythonpath-pycharm.png)
----
Cette boîte de dialogue contient deux cases à cocher, pour ajouter au `PYTHONPATH` vos dossiers de projet marqués comme
racines des sources ou de contenu. Pour marquer dans l'IDE un dossier comme étant un dossier racine des sources, il
suffit de se rendre dans l'arborescence du projet, cliquer droit sur un répertoire, choisir `Mark directory as`, puis
`Sources root`.
----
## Les imports relatifs
Lorsque vous faites des imports en Python, le langage vous offre une notation vous permettant d'effectuer
des imports dits relatifs. Le nom indique que depuis un module ou un package, vous pouvez importer un module qui se
trouve à un emplacement relatif :
```text
- module_a.py
- module_b.py
- package_1/
- submodule_1a.py
```
----
Si depuis `module_a`, je souhaite importer depuis `module_b`, je peux écrire par exemple (notez le point) :
```python
from .module_b import symbol
```
Ce type d'import est pratique, mais provoquera une erreur dans un des cas suivants :
- Votre module ne se trouve pas dans un package (son chemin ne contient pas de point),
- Votre module est votre point d'entrée (vous l'avez exécuté spécifiquement), donc son nom devient `__main__` et ne fait donc pas partie d'un package
----
On peut aussi importer un module qui se trouve dans un répertoire parent. En partant de `submodule_1a`, on peut écrire (notez les deux points) :
```python
from ..module_b import function1
from ...module_top import functiona
```
Plus je souhaite remonter dans l'arborescence, plus je peux ajouter de points à mon import (même si c'est déconseillé).
----
## Les imports circulaires
Lorsque l'on écrit des projets complexes, il peut arriver au bout d'un moment que notre code finisse par effectuer les actions
suivantes :
. . .
- `module1` importe depuis `module2`,
- `module2` importe depuis `module3`,
- `module3` importe depuis `module1`.
. . .
Ici, `module1` importe indirectement `module3` et `module3` importe `module1`. Ceci est un import circulaire.
----
Dans un tel cas de figure, l'import est impossible car l'import depuis `module3` nécessite que `module1` ait fini d'être chargé... or `module1` ne peut se charger que si `module2` et indirectement `module3` ont été chargés.
Lorsque cela se produit, votre structure doit être modifiée, soit en déplaçant une partie du code qui serait écrite
dans `module1` et utilisée par `module3`, soit en déplaçant une partie de `module2` et `module3` dans `module1` (moins recommandable).
----
## J'écris quoi, package ou module ?
Vous pouvez vous demander, puisqu'au niveau de l'interpréteur, package ou module ne font aucune différence, si
dans certains cas, c'est raisonnable de créer un package à la place d'un module.
La règle générale peut se découper ainsi pour savoir si vous devriez utiliser un package :
- Le nom du module donne une indication sur le périmètre de son code,
- Si ce périmètre vous semble large (notamment en fonctionnalités et lignes de code),
- Si vous pensez que ce périmètre peut être découpé en sous-catégories,
- Alors vous devriez probablement utiliser un package. (et un module sinon)
----

View File

@ -0,0 +1,47 @@
---
title: Plus de langage
author: Steve Kossouho
---
# Je sais tout, je vais à la FAQ
----
## Le langage Python
----
### J'ai deux listes de même taille, comment les grouper en dictionnaire ?
Si vous avez deux listes, et que vous souhaitez en former un dictionnaire tel que `d1`, comme suit :
```{.python .numberLines}
l1 = ["free", "developer", "enterprise"]
l2 = ["0€", "9.99€", "12.99€"]
d1 = {"free": "0€", "developer": "9.99€", "enterprise": "12.99€"}
```
Vous pouvez utiliser la fonction `zip` de Python, de la façon suivante :
----
```{.python .numberLines}
l1 = ["free", "developer", "enterprise"]
l2 = ["0€", "9.99€", "12.99€"]
join = zip(l1, l2) # renvoie une liste contenant [l1[0], l2[0]], puis [l1[1], l2[1]], etc.
d1 = dict(join)
```
----
### Énumérer les éléments d'une séquence
Si j'ai une séquence :
```{.python .numberLines}
seq = [25, 40, 55, 19]
for idx, number in enumerate(seq):
print(f"Itération {idx} : {number}.")
```

View File

@ -0,0 +1,208 @@
---
title: Les paquets externes
author: Steve Kossouho
mainfont: Source Sans Pro
---
# Plus de paquets
----
L'écosystème de paquets tiers pour Python est regroupé essentiellement sur un dépôt officiel, nommé [PyPI](https://pypi.org) (Python Package Index).
Outre l'utilisation de `pip` pour y accéder, quelques questions sont occasionnellement posées quant à la perennité de ces paquets dans des environnements professionnels, notamment où la sécurité importe particulièrement.
----
## Critères d'un paquet de qualité
Plusieurs critères font d'une bibliothèque un atout de choix dans une pile technique (limitent le risque);
- Pris en charge par une société établie (l'argent appelle l'argent),
- Existe depuis longtemps (2 ans), et est mis à jour régulièrement avec une gestion active des tickets,
- Est correctement et régulièrement documenté,
- La communauté connaît et utilise suffisamment cette bibliothèque,
----
Ce n'est pas tout :
- Des sociétés privées proposent une prise en charge technique de la bibliothèque,
- Des sociétés établies s'en servent en production ! (bon, une des pires failles de l'histoire avec log4j en 2021 était potentiellement exploitable chez toutes les Fortune500)
----
## Rappel sur l'utilisation de `pip`
`pip` est un programme installé avec Python qui vous permet de télécharger et installer (dans votre virtualenv ou autre) des
paquets du dépôt PyPI. On peut l'utiliser de plein de façons différentes, mais l'essentiel, c'est d'ouvrir un terminal et de
taper (évidemment il faut remplacer les éléments nécessaires)
```shell
pip install <nom du paquet>
```
----
## Traitement du texte
- `unidecode` : retirer les accents et translittérer.
- `nltk` : usine à gaz pour le traitement du langage naturel (500+ Mo)
- `chardet` : bibliothèque capable de suggérer une page de code pour un fichier texte (devine s'il s'agit de UTF8, UTF16, 850, 1252, etc.)
----
## Dates et heures
- `pytz` : simplifier le cauchemar des fuseaux horaires dans la bibliothèque standard. (corrigé dans Python 3.9 avec `zoneinfo`)
- `pendulum` : traitement des dates, intervalles et dé/encodage de texte.
- `arrow` : traitement des dates etc.
- `ciso8601` : décodage rapide de chaînes de dates. Suivi en vitesse par `pendulum`.
----
## Web
- `flask` : microframework pour faire du web. Démarrage rapide, évolution plus compliquée.
- `django` : framework web efficace et complet avec son propre ORM.
- `fastapi` : framework pour écrire des API REST, récent.
- `jupyter` : console Python interactive avancée
- `jupyterlab` : console Python interactive avancée (encore plus avancée)
----
## Réseau
- `scapy` : manipulation de paquets réseau
- `paramiko` : implémentation du protocole SSHv2
----
## ORM
- `sqlalchemy` : l'ORM de prédilection utilisé avec Flask
- `peewee` : ORM inspiré de Django
- `ponyorm` : ORM bien pensé
----
## Automatisation
- `fabric` : exécution de scripts sur plusieurs machines distantes via SSH.
- `ansible` : très utilisé pour exécuter des trucs sur des machines via SSH.
- `celery` : une file de tâches en Python, utilisable comme un cron, avec monitoring.
- `selenium` : automatisation d'actions dans un navigateur web pour tests ou autres.
- `playwright` : automatisation de navigateur web, meilleure expérience que Selenium. Par Microsoft.
- `mouse` : émuler et/ou réagir à des actions souris.
- `keyboard` : émuler et/ou réagir à des actions clavier.
- `faker`: générer des données factices (noms, adresses, clés d'API, nationalité, lorem etc.)
----
## Fichiers structurés
- `pandas` : Analyse de données tabulaires : Excel, JSON, CSV... tri, calcul, et génération de graphiques.
- `dask`: Analyse de données parallélisable.
- `modin` : Pandas parallélisable avec plusieurs backends disponibles.
- `pyspark`: Analyse de données clusterisable (avec Spark).
- `optimus` : boîte à outils d'analyse de données, peut utiliser `pandas`.
- `spire.xls` : manipulation de fichiers Excel et export dans plusieurs formats dont PDF. [Lien](https://pypi.org/project/Spire.Xls/)
- `pdfkit` : générer du PDF depuis du HTML
- `weasyprint` : générer des documents PDFbdepuis du HTML. Full Python, plus stable.
- `reportlab` : usine à gaz pour créer du PDF.
- `pypdf2` : manipuler des PDF (rotation, découpage des pages etc.)
----
- `lxml` : manipuler des XML
- `beautifulsoup4` : manipuler du code HTML
----
## Manipuler des images
- `pillow` : bibliothèque classique pour créer et traiter des images.
- `pyside` : possède une classe QImage performante.
- `skia` : par Google, dessin vectoriel, depuis env. 2017.
- `cairo` : dessin vectoriel, depuis env. 2005.
- `wand` : utiliser les fonctionnalités de ImageMagick.
- `ffmpeg-python` : utiliser les fonctionnalités de FFMPeg
----
## HTTP
- `requests` : facilite le HTTP. Capable de désérialiser du JSON depuis une réponse HTTP.
- `python-dotenv` : permet de charger des informations depuis un fichier d'environnement (.env)
- `fastapi` : concevez rapidement une API REST en Python.
----
## IA et vision assistée par ordinateur
- `keras` : IA plus facile en Python. Dépend de TensorFlow.
- `tensorflow` : IA en Python.
- `pytorch` : IA en Python.
- `opencv` : incontournable pour le traitement d'images et la détection de caractéristiques.
----
## Calcul
- `numpy` : calcul rapide sur des données matricielles, tabulaires ou en n-dimensions.
- `scikit-learn` : apprentissage machine.
----
## Afficher des graphiques
- `matplotlib` : bibliothèque d'affichage de graphes
- `pygal` : bibliothèque d'affichage de graphes en SVG
- `seaborn` : plus élégant que Matplotlib pour afficher des graphiques
- `plotly` : affichage de graphiques via les technologies web
- `dash` : générer des tableaux de bord avec des graphiques (entre autres)
----
## Formatage du code Python et vérification stylistique
- `black` : formate votre code sans poser de question. Très utilisé professionnellement.
- `pylint` : indique tous les problèmes de normes et convention dans votre code.
- `flake8` : problèmes de normes et convention, plutôt orienté PEP8.
- `isort` : trie les imports automatiquement.
- `radon` : outil de génération de métriques sur le code.
- `mypy` : vérification des annotations de type.
----
## Boilerplate Python
- `attrs` : Bibliothèque de décorateurs pour `dataclasses`
----
## Spécialité Windows
- `winregistry` : accès simplifié à la base de registre Windows.
----
## Générer des exécutables
- `pyinstaller` : Convertisseur de scripts Python en exécutables autonomes.
- `py2exe` : Idem.
[Comparaison avec d'autres packagers](https://pyoxidizer.readthedocs.io/en/stable/pyoxidizer_comparisons.html)
----
## Awesome Python
[Curation des meilleurs paquets Python](https://github.com/vinta/awesome-python)
----
Ressources d'entraînement à Python
https://store.steampowered.com/app/1067220/RoboCo/

View File

@ -0,0 +1,283 @@
---
title: Exporter un environnement Python
author: Steve Kossouho
mainfont: Source Sans Pro
---
# Exporter un environnement virtuel entre deux machines
Ou comment recréer un environnement virtuel sans devoir réinstaller toutes les dépendances à la main…
----
## Exporter l'environnement virtuel
Les environnements virtuels sont contenus dans des dossiers, et sont généralement dépendants du système sur lequel ils sont créés :
- L'exécutable `bin/python` y est un lien symbolique vers l'interpréteur Python qui a servi à créer l'environnement (version, OS, architecture processeur)
- les paquets externes écrits en C, et qui sont donc compilés, ne sont utilisables que sur une configuration spécifique (par exemple. Linux sur x86/64 Python 3.11, ou Windows sur ARM64 Python 3.12, ou Linux x86/32 Python 3.9 etc.); une release d'un tel paquet va générer de nombreuses variantes, et une seule version de Numpy existe actuellement dans 44 variantes.
Il serait rare qu'une simple copie du répertoire d'environnement virtuel permette de le réutiliser ailleurs.
----
…Et d'ailleurs, il existe une bien meilleure idée, beaucoup plus économe en efforts :
`pip` propose justement un moyen d'échanger une liste des dépendances installées dans l'environnement virtuel, pour pouvoir très simplement les réinstaller sur un autre système. Cette liste est appelée `fichier de dépendances`, ou `requirements.txt`. Elle est très souvent fournie avec des projets Python, justement pour en simplifier le partage et la réutilisation.
----
```{.bash .numberLines}
pip freeze --local > requirements.txt
```
Dans le terminal de votre projet (si votre environnement virtuel est activé, ce qui est le cas dans PyCharm), cette commande génère un fichier texte, `requirements.txt` qui contiendra une liste des paquets installés dans votre environnement ainsi que leur version.
----
Sur votre système cible, vous pouvez ensuite réimporter ce fichier et installer toutes les dépendances qui y sont inscrites avec cette commande :
```{.bash .numberLines}
pip install -r requirements.txt
```
Elles seront installées dans l'environnement virtuel actuellement activé. (pensez bien à l'y créer et à l'activer si besoin !)
----
## Créer manuellement un environnement virtuel
Pour créer un environnement virtuel Python, dans un terminal, tapez :
```{.bash .numberLines}
python -m venv <nom du nouveau dossier venv>
```
Le dossier créé contiendra un répertoire `bin` (`scripts` sous Windows) et un répertoire `lib`.
----
## Comment activer/désactiver un environnement virtuel
Vos environnements virtuels possèdent un sous-répertoire `bin`.
Pour activer et utiliser automatiquement votre environnement virtuel, il faut se rendre dans le dossier `bin` et taper dans un terminal :
```{.bash .numberLines}
source ./activate # ou activate.csh ou activate.fish
```
Sous Windows, vous devez plutôt simplement exécuter une commande:
```bash {.numberLines}
./scripts/activate.ps1 # Dans le powershell si vous êtes dans le répertoire du venv
```
(ça peut être `activate.fish` si votre shell utilise fish, et ainsi de suite...)
Votre invite de commande devrait contenir un nouvel élément, au moins entre parenthèses, vous indiquant que l'environnement virtuel est bien activé et utilisé.
----
Pour ensuite désactiver cet environnement virtuel, il vous suffit, dans le terminal où l'environnement est activé, de saisir :
```{.bash .numberLines}
deactivate
```
L'invite de commande du shell ne contiendra plus l'ajout indiquant que votre environnement est actuellement utilisé.
(Quand PyCharm active automatiquement un venv, la commande `deactivate`{.bash} est indisponible)
----
## Comment uniquement télécharger des paquets externes pour les machines déconnectées ?
Effectivement, vous pourriez vouloir créer un environnement où des machines nécessitant des paquets Python n'ont
pas accès à internet. Dans ce cas, il est possible d'utiliser `pip` pour utiliser un répertoire comme dépôt en remplacement de
PyPI. Ce répertoire peut être un répertoire réseau, par exemple, si vous avez au moins accès au réseau local.
----
Dans l'ordre, pour appliquer une telle mesure, vous pouvez :
- Utiliser le fichier de dépendances pour télécharger les archives des paquets nécessaires
- Placer les archives dans un dossier accessible sur le réseau (ou localement)
- Utiliser pip sur la machine cible, en précisant de ne pas utiliser PyPI mais notre dossier
----
```{.bash .numberLines}
pip download -r requirements.txt
```
Attention, ça va tout télécharger dans le répertoire en cours !
----
Sur la machine cible, il ne vous reste plus qu'à utiliser `pip` et lui dire d'aller chercher les paquets depuis le répertoire
local ou réseau :
```{.bash .numberLines}
pip install -r requirements.txt --no-index --find-links=file://chemin/repertoire
```
----
Si vous souhaitez créer un dépôt compatible avec PyPI, il vous suffit de créer un projet Python et d'installer dans votre environnement virtuel le paquet `pypiserver`. Il est très simple à utiliser, et [cet article chez Linode sur les dépôts pour pip](https://www.linode.com/docs/guides/how-to-create-a-private-python-package-repository/) devrait vous aider à y voir plus clair.
----
## Bonus : PyCharm et la gestion de vos dépendances
PyCharm est capable de remplir automatiquement votre fichier `requirements.txt` (possiblement avec la version) en analysant le
code de votre projet.
![Remplissage automatique des dépendances](assets/images/x-requirements-auto-pycharm.jpg)
----
Dans le menu `Tools` → `Sync python requirements` (vous pouvez aussi utiliser le raccourci `Shift + Shift` pour rechercher rapidement l'entrée de menu), PyCharm vous offre un raccourci pour mettre à jour votre fichier de dépendances, conformément aux imports que vous avez utilisés dans votre code.
Il vous faudra préciser le chemin du fichier `requirements.txt` à mettre à jour, puis configurer la façon dont vous voudrez que PyCharm remplisse les dépendances (intégrer le numéro de version, supprimer les dépendances non utilisées, etc.)
----
## Extra : Gérer des paquets selon l'environnement
Selon la topologie dans laquelle vous évoluez (contraintes de sécurité incluses, ex. VPN), vous pouvez être amené à organiser vos projets de plusieurs manières. Du plus simple au plus difficile :
1. Projets Python sur un _dépôt Git public_
2. Projets Python sur un _dépôt Git privé_ (SSH etc.)
3. Projets Python packagés dans un dépôt brut sur un _répertoire réseau monté_
4. Projets Python packagés dans un serveur `pip`.
----
### Dépendances
Lorsque votre projet nécessite d'utiliser des bibliothèques Python pour fonctionner, il est rappelé qu'il est une bonne pratique de fournir avec celui-ci un fichier de _dépendances_. Ce fichier s'appelle par convention `requirements.txt`. Il contient la référence de toutes les bibliothèques que vous utilisez, dans la version dont vous avez besoin.
```{.requirements.txt .numberLines}
# Exemples de bibliothèques
ipython==8.15.0 # console Python avancée
pyside6==6.5.2 # Qt Framework
```
Une fois ce genre de fichier décrit, il est possible d'installer les dépendances qu'il référence avec la commande `pip install -r requirements.txt`.
----
### Dépôt Git public
Le fichier de dépendances de votre projet, **si vous avez accès à celles-ci**, peut contenir des entrées vers des branches ou des commits de projets présents sur un dépôt Git. Par exemple, si vous souhaitez inclure `Django==3.2.0`, vous pouvez rédiger une entrée similaire au contenu suivant :
```requirements.txt
# L'entrée est utilisée comme option d'un pip install
git+https://github.com/django/django.git@3.2
```
----
### Dépôt Git privé
Si votre entrée provient d'une usine logicielle et que votre dépôt est privé, l'entrée de votre fichier de dépendances reste la même, mais vous devrez peut-être posséder des identifiants pour y accéder. En général, autoriser une clé SSH configurée sur votre machine de développement sera nécessaire, mais probablement la seule étape nécessaire.
----
### Dossier monté
Monter un dossier distant consiste à rendre disponible localement l'accès à un répertoire présent sur une machine à distance (généralement un NAS).
Il est possible de demander à l'outil `pip` de télécharger et d'installer des paquets disponibles sur le protocole `file://`, à condition d'utiliser l'option `pip install -r requirements.txt --find-links=file:///path/to/dir`.
Les paquets proposés par vos collègues doivent être installables par `pip` et les archives (aux formats `.tar.gz` et `.whl`) doivent être disponibles dans ce répertoire. [Voir le protocole officiel pour la création de paquets Python](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
----
### Imports dynamiques
Il peut arriver que, lors de la création d'une bibliothèque, un développeur doive s'adapter à des situations où certains imports ne seront pas disponibles.
Un cas extrême serait d'introduire l'utilisation de `pathlib` (disponible uniquement dans Python 3.4+), alors que votre bibliothèque est censée pouvoir fonctionner dans un environnement Python 2.7.
Techniquement, c'est une mauvaise idée de s'y prendre ainsi, mais si le choix n'est pas permis, il faut passer par certaines stratégies.
----
#### Imports alternatifs
La façon la plus connue de venir à bout de la possibilité d'utiliser des imports différents selon l'environnement consiste à utiliser la gestion d'exceptions :
```python {.numberLines}
try:
import package1
print("Package1 importé !")
except ImportError:
print("Erreur lors de l'importation de package1.")
try:
import package2
print("Package2 importé !")
except ImportError:
print("Erreur lors de l'importation de package2.")
exit()
```
----
Une autre façon plus contrôlée, mais plus rarement vue en production, consiste à interroger la version de l'interpréteur pour choisir quel package ou module importer :
```python {.numberLines}
# Récupère un objet représentant l'interpréteur Python
from sys import version_info
if version_info.major == 3:
import pathlib
filename = pathlib.Path("/home/demo/readme.txt").parent
else:
from os import path
filename = path.dirname("/home/demo/readme.txt")
```
----
#### Imports dynamiques par nom
Vous pouvez, en Python, importer un module ou un package par son nom complet. La méthode fonctionne aussi bien dans Python 2 que Python 3 .
Elle est un peu compliquée à utiliser, et accepte plusieurs arguments :
```{.python .numberLines}
path = __import__("os.path", globals(), locals(), ["basename", "join"], 0)
basename = path.basename
join = path.join
```
----
#### Imports depuis des répertoires personnalisés
Lorsque vous avez des modules dans des répertoires personnalisés, vous pouvez ajouter lesdits répertoires au `PYTHONPATH` :
```{.python .numberLines}
import sys
# Ajouter le répertoire au PYTHONPATH
sys.path.append("/home/new_path/root")
import mon_module # dans le répertoire
```
----
### Configurations
Lire des informations sous la forme de fichiers .ini :
```{.python .numberLines}
import configparser
data = configparser.ConfigParser().read("/home/demo/file.ini")
```
----
### Variables d'environnement
Une technique utilisée dans certains projets pour configurer certaines options (mots de passe, clés d'API) consiste à créer un fichier de configuration, que certaines bibliothèques peuvent charger et appliquer aux variables d'environnement (variables stockées par le système d'exploitation, différentes de la base de registre Windows)

View File

@ -0,0 +1,289 @@
# Fonctions utiles en Python
----
## Fonctions de types de données
----
### `int`
La fonction `int()` convertit une valeur en un entier. Si aucun argument n'est fourni, elle retourne 0.
```{.python .numberLines}
x = int(2.8) # x sera 2
y = int("3") # y sera 3
z = int() # z sera 0
w = int("6f", base=16) # w sera 111
v = int("10101", base=2) # v sera 21
```
----
### `str`
La fonction `str()` convertit une valeur en une chaîne. Si aucun argument n'est fourni, elle retourne une chaîne vide.
```{.python .numberLines}
x = str(2) # x sera "2"
y = str(3.8) # y sera "3.8"
z = str() # z sera ""
```
----
### `float`
La fonction `float()` convertit une valeur en un nombre à virgule flottante. Si aucun argument n'est fourni, elle retourne 0.0.
```{.python .numberLines}
x = float(2) # x sera 2.0
y = float("3.8") # y sera 3.8
z = float() # z sera 0.0
```
----
### `list`
La fonction `list()` est utilisée pour créer une liste.
```{.python .numberLines}
x = list((1, 2, 3)) # x sera [1, 2, 3]
y = list("oui") # y sera ["o", "u", "i"]
z = list() # z sera une nouvelle liste vide
```
----
### `dict`
La fonction `dict()` est utilisée pour créer un dictionnaire.
```{.python .numberLines}
x = dict(name="John", age=36) # x sera {'name': 'John', 'age': 36}
y = dict([("name", "John"), ("age", 36)]) # y sera {'name': 'John', 'age': 36}
z = dict() # z sera un nouveau dictionnaire vide
```
----
## Fonctions d'introspection
----
### `locals`
La fonction `locals()` renvoie un dictionnaire contenant les variables locales courantes.
```{.python .numberLines}
def ma_fonction():
x = 2
print(locals()) # Dictionnaire des variables disponibles au niveau de la fonction
ma_fonction() # Affiche {'x': 2}
```
----
### `getattr`
La fonction `getattr()` renvoie la valeur de l'attribut nommé d'un objet.
```{.python .numberLines}
class MaClasse:
x = 2
objet = MaClasse()
print(getattr(objet, "x")) # Affiche 2
print(getattr(objet, "y")) # Provoque une erreur
print(getattr(objet, "y", None)) # Affiche None si y est introuvable comme attribut de objet
```
----
### `type`
La fonction `type()` renvoie le type d'un objet.
```{.python .numberLines}
x = 2
print(type(x)) # Affiche <class 'int'>
```
----
### `isinstance`
La fonction `isinstance()` vérifie si un objet est une instance d'une classe spécifique.
```{.python .numberLines}
class MaClasse:
pass
objet = MaClasse()
print(isinstance(objet, MaClasse)) # Affiche True
```
----
### `dir`
La fonction `dir()` tente de renvoyer une liste d'attributs valides de l'objet.
```{.python .numberLines}
print(dir("Hello")) # Affiche une liste de méthodes et attributs de la classe str
```
----
## Fonctions utiles
----
### `print`
La fonction `print()` affiche les arguments à l'écran.
```{.python .numberLines}
print("Bonjour, monde !") # Affiche Bonjour, monde !
print("Je voudrais", 2, "baguettes.")
```
----
### `all`
La fonction `all()` renvoie `True` si **tous** les éléments d'un itérable sont vrais (ou équivalents, à savoir non
nuls ou non vides).
```{.python .numberLines}
liste = [True, True, True]
print(all(liste)) # Affiche True
```
----
### `any`
La fonction `any()` renvoie `True` si au moins un élément d'un itérable est vrai.
```{.python .numberLines}
liste = [False, True, False]
print(any(liste)) # Affiche True
```
----
### `ord`
La fonction `ord()` renvoie un entier représentant le code Unicode du caractère fourni.
```{.python .numberLines}
print(ord('A')) # Affiche 65
print(ord('a')) # Affiche 97
```
----
### `max`
La fonction `max()` renvoie le plus grand élément d'un itérable ou le plus grand de deux ou plusieurs arguments.
```{.python .numberLines}
print(max(1, 2, 3)) # Affiche 3
```
----
### `min`
La fonction `min()` renvoie le plus petit élément d'un itérable ou le plus petit de deux ou plusieurs arguments.
```{.python .numberLines}
print(min(1, 2, 3)) # Affiche 1
```
----
### `input`
La fonction `input()` lit une ligne à partir de l'entrée de la console, et renvoie le texte saisi, toujours sous forme
de chaîne de caractères.
```{.python .numberLines}
nom = input("Entrez votre nom : ")
print(nom)
```
----
### `round`
La fonction `round()` arrondit un nombre à un certain nombre de décimales.
```{.python .numberLines}
print(round(3.14159, 2)) # Affiche 3.14
```
----
### `enumerate`
La fonction `enumerate()` permet à chaque itération sur un objet, de renvoyer un tuple de taille 2,
contenant toujours un numéro séquentiel, suivi de l'élément parcouru à l'itération en cours.
```{.python .numberLines}
for i, v in enumerate(['a', 'b', 'c']):
print(i, v)
```
Affiche `0 a`, `1 b` puis `2 c`.
----
### `len`
La fonction `len()` renvoie le nombre d'éléments dans un objet.
```{.python .numberLines}
print(len("Bonjour")) # Affiche 7
```
----
### `range`
La fonction `range()` génère une séquence de nombres. La valeur de fin n'est jamais incluse dans l'intervalle.
```{.python .numberLines}
for i in range(5):
print(i) # Affiche les nombres de 0 à 4
```
```{.python .numberLines}
for i in range(5, 15):
print(i) # Affiche les nombres de 5 à 14
```
```{.python .numberLines}
for i in range(5, 15, 3):
print(i) # Affiche les nombres 5, 8, 11 et 14
```
----
### `zip`
La fonction `zip()` combine les éléments de
plusieurs itérables en tuples.
```{.python .numberLines}
nombres = [1, 2, 3]
lettres = ['a', 'b', 'c']
for n, l in zip(nombres, lettres):
print(n, l)
```
----

View File

@ -0,0 +1,35 @@
---
title: Installer Python et PyCharm
author: Steve Kossouho
---
# Installer PyCharm et Python sous Windows
----
## Installer Python
Pour pouvoir exécuter vos scripts Python, vous devez naturellement installer Python (l'interpréteur et les outils associés).
Le [site officiel de Python](https://www.python.org/) propose des téléchargements pour plusieurs plateformes, mais seul Windows nécessite
d'effectuer une installation via un téléchargement.
----
### Étapes
1. Se rendre sur [Téléchargements Python](https://www.python.org/downloads/windows/);
2. Dans la section `Stable Releases` (à gauche), cliquer `Download Windows installer (64-bit)` pour la dernière version;
3. Après le téléchargement, exécuter le fichier; **Attention** :
4. Pendant l'exécution de l'installateur, **cochez** à tout prix l'option mentionnant un `PATH`
5. Après l'installation, vous pouvez vérifier que tout va bien en lançant l'application Windows `Powershell` et en y tapant `python`;
6. Si tout va bien, la console Python devrait se lancer. Si vous avez manqué une étape, Windows Store devrait s'ouvrir ou un message d'erreur devrait apparaître.
----
## Installer PyCharm
Le logiciel PyCharm est très facile à installer, et nécessite seulement de se rendre sur le site de JetBrains :
1. Rendez-vous sur [Téléchargement PyCharm pour Windows](https://www.jetbrains.com/fr-fr/pycharm/download/?section=windows);
2. Pour directement télécharger la version gratuite (plus bas dans la page), suivez ce lien : [Téléchargement PyCharm Community](https://www.jetbrains.com/fr-fr/pycharm/download/download-thanks.html?platform=windows&code=PCC)
3. L'installation devrait être simple à effectuer.

View File

@ -0,0 +1,94 @@
---
title: Python et PyCharm avec une brique et un trombone
author: Steve Kossouho
---
# Installer PyCharm et Python sous Windows
Dans un environnement contraint:
- Pas de droits administrateur sur la machine
- Pas d'accès à la majorité des noms de domaine
----
## Installer Python
Lorsqu'il est impossible d'**installer** Python sur une machine, il est généralement possible d'en exécuter tout de
même une version dite portable. Il existe deux packages portables, le package officiel du site Python (incomplet pour nos besoins),
ainsi qu'une version nommée **WinPython**:
- [WinPython 3.12 Windows 64-bit](https://github.com/winpython/winpython/releases/download/7.5.20240410final/Winpython64-3.12.3.0dot.exe) (19 avril 2024)
- [Python 3.12.4 Windows 64-bit embeddable](https://www.python.org/ftp/python/3.12.4/python-3.12.4-embed-amd64.zip) (6 juin 2024)
Un seul des deux liens est nécessaire, **préférez WinPython** car il contient des outils supplémentaires pour Python, notamment
la bibliothèque interne pour créer des environnements virtuels.
----
## Installer PyCharm
Le logiciel PyCharm semble s'installer même sans les droits administrateur, et nécessite seulement de se rendre sur le site de JetBrains :
1. Rendez-vous en bas de [Téléchargement PyCharm pour Windows](https://www.jetbrains.com/fr-fr/pycharm/download/?section=windows);
2. Pour directement télécharger la version gratuite (**plus bas dans la page**), suivez ce lien : [Téléchargement PyCharm Community](https://www.jetbrains.com/fr-fr/pycharm/download/download-thanks.html?platform=windows&code=PCC)
3. L'installation devrait se lancer malgré l'invite d'accès administrateur.
----
### Gérer ses environnements virtuels et projets
Nous avons vu comment créer des venv pour Python. Dans votre configuration, il est conseillé de se baser sur l'interface de PyCharm
pour les créer, en observant quelques précautions:
- Le dossier pour créer le venv devrait être un dossier vide
- Le nom de ce dossier peut débuter par un `.` pour le différencier des autres dossiers de projet
- Pour le reconnaître _a posteriori_, préférez le nommer avec le terme `venv`
- Préférez également ne pas le nommer avec des majuscules, des tirets ou des espaces
----
## Utiliser l'outil pip
----
### Installer des paquets depuis un répertoire local
Si vous souhaitez installer des paquets qui sont (avec leurs dépendances) disponibles dans un répertoire local avec `pip`, que ce
soit via un fichier `requirements.txt` ou manuellement, vous devez utiliser l'une des commandes suivantes (les options sont les plus importantes):
```bash {.numberLines}
pip install -r requirements.txt --no-index --find-links=<répertoire>
```
```bash {.numberLines}
pip install <fichier .whl> --no-index --find-links=<répertoire>
```
- L'option `--no-index` désactive la recherche de paquets sur le PyPI
- L'option `--find-links` permet d'indiquer où chercher des paquets
----
### Configurer pip pour utiliser automatiquement un dépôt personnalisé
Il est possible de créer un fichier de configuration que pip trouve automatiquement pour utiliser par défaut
certaines options.
- Sous Linux: `/home/<user>/.pip/pip.conf` ou [Dans le répertoire du venv](https://pip.pypa.io/en/stable/topics/configuration/#location)
- Sous Windows: [Dans le répertoire du venv](https://pip.pypa.io/en/stable/topics/configuration/#location)
----
### Contenu du fichier de configuration (Exemple)
Le fichier que vous devez créer est différent selon le système d'exploitation (voir slide précédent), mais son contenu
est toujours le même si vous êtes dans le même réseau d'entreprise:
```ini {.numberLines}
[global]
index = https://...
index-url = https://...
trusted-host = ...
```

View File

@ -0,0 +1,335 @@
<?xml version="1.0" encoding="utf-8"?>
<gaphor xmlns="http://gaphor.sourceforge.net/model" version="3.0" gaphor-version="2.7.1">
<StyleSheet id="44608934-d125-11eb-903c-9d2935721a2d"/>
<Package id="4460d308-d125-11eb-903c-9d2935721a2d">
<name>
<val>New model</val>
</name>
<ownedDiagram>
<reflist>
<ref refid="4460d6d2-d125-11eb-903c-9d2935721a2d"/>
</reflist>
</ownedDiagram>
<ownedType>
<reflist>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
<ref refid="9631801e-6046-11ec-83ef-9710676cdac9"/>
</reflist>
</ownedType>
</Package>
<Diagram id="4460d6d2-d125-11eb-903c-9d2935721a2d">
<element>
<ref refid="4460d308-d125-11eb-903c-9d2935721a2d"/>
</element>
<name>
<val>main</val>
</name>
<ownedPresentation>
<reflist>
<ref refid="48aab9d8-d125-11eb-903c-9d2935721a2d"/>
<ref refid="45b9f162-6046-11ec-821d-9710676cdac9"/>
<ref refid="959a81f3-6046-11ec-be20-9710676cdac9"/>
</reflist>
</ownedPresentation>
</Diagram>
<ClassItem id="48aab9d8-d125-11eb-903c-9d2935721a2d">
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 612.0, 100.0)</val>
</matrix>
<width>
<val>172.0</val>
</width>
<height>
<val>159.0</val>
</height>
<diagram>
<ref refid="4460d6d2-d125-11eb-903c-9d2935721a2d"/>
</diagram>
<subject>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</subject>
</ClassItem>
<Class id="48aa7c98-d125-11eb-903c-9d2935721a2d">
<name>
<val>Dog</val>
</name>
<ownedAttribute>
<reflist>
<ref refid="58cb6f74-d125-11eb-903c-9d2935721a2d"/>
<ref refid="61aa0ec0-d125-11eb-903c-9d2935721a2d"/>
</reflist>
</ownedAttribute>
<ownedOperation>
<reflist>
<ref refid="8f1f1b34-d125-11eb-903c-9d2935721a2d"/>
<ref refid="998bde4a-d125-11eb-903c-9d2935721a2d"/>
<ref refid="9e5ca0e4-d125-11eb-903c-9d2935721a2d"/>
<ref refid="370726e2-6046-11ec-9ff9-9710676cdac9"/>
</reflist>
</ownedOperation>
<package>
<ref refid="4460d308-d125-11eb-903c-9d2935721a2d"/>
</package>
<presentation>
<reflist>
<ref refid="48aab9d8-d125-11eb-903c-9d2935721a2d"/>
</reflist>
</presentation>
</Class>
<Property id="58cb6f74-d125-11eb-903c-9d2935721a2d">
<class_>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</class_>
<name>
<val>name</val>
</name>
</Property>
<Property id="61aa0ec0-d125-11eb-903c-9d2935721a2d">
<class_>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</class_>
<name>
<val>age</val>
</name>
</Property>
<Operation id="8f1f1b34-d125-11eb-903c-9d2935721a2d">
<class_>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</class_>
<name>
<val>eat</val>
</name>
<ownedParameter>
<reflist>
<ref refid="2f346fc0-6046-11ec-92b9-9710676cdac9"/>
</reflist>
</ownedParameter>
</Operation>
<Operation id="998bde4a-d125-11eb-903c-9d2935721a2d">
<class_>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</class_>
<name>
<val>bark</val>
</name>
<ownedParameter>
<reflist>
<ref refid="29f7f101-6046-11ec-a699-9710676cdac9"/>
</reflist>
</ownedParameter>
</Operation>
<Operation id="9e5ca0e4-d125-11eb-903c-9d2935721a2d">
<class_>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</class_>
<name>
<val>wag_tail</val>
</name>
<ownedParameter>
<reflist>
<ref refid="25b7c685-6046-11ec-b51f-9710676cdac9"/>
</reflist>
</ownedParameter>
</Operation>
<Parameter id="25b7c685-6046-11ec-b51f-9710676cdac9">
<name>
<val>speed</val>
</name>
<ownerFormalParam>
<ref refid="9e5ca0e4-d125-11eb-903c-9d2935721a2d"/>
</ownerFormalParam>
</Parameter>
<Parameter id="29f7f101-6046-11ec-a699-9710676cdac9">
<name>
<val>loudness</val>
</name>
<ownerFormalParam>
<ref refid="998bde4a-d125-11eb-903c-9d2935721a2d"/>
</ownerFormalParam>
</Parameter>
<Parameter id="2f346fc0-6046-11ec-92b9-9710676cdac9">
<name>
<val>quantity</val>
</name>
<ownerFormalParam>
<ref refid="8f1f1b34-d125-11eb-903c-9d2935721a2d"/>
</ownerFormalParam>
</Parameter>
<Operation id="370726e2-6046-11ec-9ff9-9710676cdac9">
<class_>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</class_>
<isStatic>
<val>1</val>
</isStatic>
<name>
<val>create</val>
</name>
</Operation>
<Class id="45b9e0d9-6046-11ec-a4d8-9710676cdac9">
<name>
<val>DogOwner</val>
</name>
<ownedAttribute>
<reflist>
<ref refid="5fe6ef5c-6046-11ec-bf40-9710676cdac9"/>
<ref refid="61b6bf64-6046-11ec-95ae-9710676cdac9"/>
<ref refid="667b4afa-6046-11ec-9573-9710676cdac9"/>
</reflist>
</ownedAttribute>
<ownedOperation>
<reflist>
<ref refid="6a509916-6046-11ec-9175-9710676cdac9"/>
<ref refid="6d5b90de-6046-11ec-97c7-9710676cdac9"/>
</reflist>
</ownedOperation>
<package>
<ref refid="4460d308-d125-11eb-903c-9d2935721a2d"/>
</package>
<presentation>
<reflist>
<ref refid="45b9f162-6046-11ec-821d-9710676cdac9"/>
</reflist>
</presentation>
</Class>
<ClassItem id="45b9f162-6046-11ec-821d-9710676cdac9">
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 190.0, 99.0)</val>
</matrix>
<width>
<val>176.0</val>
</width>
<height>
<val>159.0</val>
</height>
<diagram>
<ref refid="4460d6d2-d125-11eb-903c-9d2935721a2d"/>
</diagram>
<subject>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</subject>
</ClassItem>
<Property id="5fe6ef5c-6046-11ec-bf40-9710676cdac9">
<class_>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</class_>
<name>
<val>name</val>
</name>
</Property>
<Property id="61b6bf64-6046-11ec-95ae-9710676cdac9">
<class_>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</class_>
<name>
<val>age</val>
</name>
</Property>
<Property id="667b4afa-6046-11ec-9573-9710676cdac9">
<class_>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</class_>
<name>
<val>address</val>
</name>
</Property>
<Operation id="6a509916-6046-11ec-9175-9710676cdac9">
<class_>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</class_>
<name>
<val>walk_dog</val>
</name>
</Operation>
<Operation id="6d5b90de-6046-11ec-97c7-9710676cdac9">
<class_>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</class_>
<name>
<val>feed_dog</val>
</name>
</Operation>
<AssociationItem id="959a81f3-6046-11ec-be20-9710676cdac9">
<diagram>
<ref refid="4460d6d2-d125-11eb-903c-9d2935721a2d"/>
</diagram>
<head_subject>
<ref refid="96318f4a-6046-11ec-9fa0-9710676cdac9"/>
</head_subject>
<horizontal>
<val>0</val>
</horizontal>
<orthogonal>
<val>1</val>
</orthogonal>
<subject>
<ref refid="9631801e-6046-11ec-83ef-9710676cdac9"/>
</subject>
<tail_subject>
<ref refid="96319939-6046-11ec-9a17-9710676cdac9"/>
</tail_subject>
<matrix>
<val>(1.0, 0.0, 0.0, 1.0, 264.0, 165.0)</val>
</matrix>
<points>
<val>[(102.0, 4.784901835968924), (102.0, 37.88461538461539), (348.0, 37.88461538461539)]</val>
</points>
<head-connection>
<ref refid="45b9f162-6046-11ec-821d-9710676cdac9"/>
</head-connection>
<tail-connection>
<ref refid="48aab9d8-d125-11eb-903c-9d2935721a2d"/>
</tail-connection>
</AssociationItem>
<Association id="9631801e-6046-11ec-83ef-9710676cdac9">
<memberEnd>
<reflist>
<ref refid="96318f4a-6046-11ec-9fa0-9710676cdac9"/>
<ref refid="96319939-6046-11ec-9a17-9710676cdac9"/>
</reflist>
</memberEnd>
<name>
<val>owns</val>
</name>
<ownedEnd>
<reflist>
<ref refid="96318f4a-6046-11ec-9fa0-9710676cdac9"/>
<ref refid="96319939-6046-11ec-9a17-9710676cdac9"/>
</reflist>
</ownedEnd>
<package>
<ref refid="4460d308-d125-11eb-903c-9d2935721a2d"/>
</package>
<presentation>
<reflist>
<ref refid="959a81f3-6046-11ec-be20-9710676cdac9"/>
</reflist>
</presentation>
</Association>
<Property id="96318f4a-6046-11ec-9fa0-9710676cdac9">
<aggregation>
<val>shared</val>
</aggregation>
<association>
<ref refid="9631801e-6046-11ec-83ef-9710676cdac9"/>
</association>
<owningAssociation>
<ref refid="9631801e-6046-11ec-83ef-9710676cdac9"/>
</owningAssociation>
<type>
<ref refid="45b9e0d9-6046-11ec-a4d8-9710676cdac9"/>
</type>
</Property>
<Property id="96319939-6046-11ec-9a17-9710676cdac9">
<association>
<ref refid="9631801e-6046-11ec-83ef-9710676cdac9"/>
</association>
<owningAssociation>
<ref refid="9631801e-6046-11ec-83ef-9710676cdac9"/>
</owningAssociation>
<type>
<ref refid="48aa7c98-d125-11eb-903c-9d2935721a2d"/>
</type>
</Property>
</gaphor>

View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2021-05-02T15:21:04.342Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.5.1 Chrome/89.0.4389.82 Electron/12.0.1 Safari/537.36" etag="NQly_f9DtUGSSGvz9f_T" version="14.5.1" type="device"><diagram id="C5RBs43oDa-KdzZeNtuy" name="UML Class Diagram">7VvZcuI4FP0aHpPywpZHIEt3TzpDN5lkXgUWoEG2GFmE0F/fV7a8KoCd4HZCqEpVrGtZtnSOro6OTcMeuM83HC3n35mDacMynOeGfdmwLLNpdOGfjGzCSOfCCAMzThxVKQmMyC+sglG1FXGwn6koGKOCLLPBCfM8PBGZGOKcrbPVpoxm77pEM6wFRhNE9egjccQ8jHatThL/gslsHt3ZbF+EZ1wUVVY98efIYetUyL5q2APOmAiP3OcBpnLwonF5/Lp5pLeL9s23H/7/6J/+X/d3D2dhY9dlLom7wLEnXt30r8X0+stD879/l8PrH2vjpjf+dqYuMZ4QXanxGmLuM0/1WGyiYfTXxKXIg1J/yjwxUmdgEPqIkpkHxxN4Oswh8IS5IIBAT50QbAnRyZxQ5xZt2Er2wRdosohK/Tnj5Bc0iyicMiEAp7lQZLLamRojeSWEDYhy7EOdYTQwZhy6Rb5QdSaMUrT0yTh4YFnFRXxGvD4TgrlRQ2zlOdhRpRjpoCA4W8TckdcXhEPBJkcDP6fIqOC5wczFgm+gijobM01NNTMqrxPimm0Vm6dJa6tJitRkmcVtx7f7CZMLeTMYhOR+du5+zYL3A0Qyt0MUgPeQwH05jH6ahnCQ6moSCshZgqimRtQ75GKNpjDSIkVJiqdiKyH9JZoQb3Yb1LlsJpGfqqcyxODaKQ3IMCeOg72ALAIJFPJJMmTJiCeCoWj14Q8GbGCctxoteKABlM2kDH+yOhcD5gGvEAkIhIGsaywJ+wK1dk7b/dTaZBEri2yaSBlIy+Jn6YlmzjxJxruVO4a0cWxI7sgoc+FSdVgV3i2rZrxtDe8rFxF5Wc9xIEf7J8APCninWzPgTQ1wDWFKAv2gRsN8cW3dA78LQMrmIrzvJR0uz0yNE7bOCfsF/CkaYzpkPhGEyfZ5WDfHi7qytmk0i6G6Y/l/E6gtPWuv+GSOfJm4QXYtYHiDoyOcz5WBatadm9saqiOxcmQ3C6p+46T6D6f6zZzqt9vVqn4zp/rt7vtV/Z3tTD1W3Vgw7bQL0+y97AC6GpY9eHrp3ljGd1hKjg7JgwrC0njXvgO4OAnCg4NaVOVXpQdN3S/8ConfuAI0yBgFy8w9kwGPM9nckU3ptAhqVgVyt+5Mbepm2w2WS+4Iu8RDXCJ+jxZYd4k/Or5VQVq/5o8eYFc6xp7Tk29coDSmTMrtPoSUHjeNsHhNaLR07RHM2fUPOzMcTR1Mx2x9lQT6QQBOREwpLbd9BvtSvB8y2GDMcIHtnHzancC+BCTHFAnylH3xtEOcDyVxE2FuGVlhbhl2tomwk+qq9Juesg2Fo6A1dDCvXjf/hpxNse8zXa+ftpjVbzHt/Jav6BazY+wl8Qs7TLtZYIfZeR87TFP3LUeIIpVxP+HaFk/ej7OzNHWbUkPvs281ysNadANZ2V5Dtyk/lWKJEHvXksXMZfrOKyWLnWvI6uSepWrJojuN295TngRL9YKlFX11VvZLGMvcschsVyyt1gf6EkY3UkeCY6y/vfksiqVTmF7vRrHo5uiAiOPTnAe1wMvjXLsHbul+KSRtcXzfrdUMdO1fv1i6ZQrKXq7iljFgzgnwAwNe+MuY6hDXDdUBjIk4QuegbqztV4qxw2GtG5oayJ/eYCiPa6ugDKvKYbB07+8BIHKOcYGuDsV27ZlYt//+XonlStoLPfmu8lbOgxOihRG9qD3flrP+2DIY63LmXuwLmvVbfWps9zp9EZZ7rT6Fs6RS2tZoqgd6o+sXv8+JCWNlmyjq+rWaexra4vppDZ3l3zjl2mHTqY8rMQ4t3Tg0zs8j4BO6BmZcKhHsSTtjZcmFa3gfTRazgL8DRhmH8x5LDEjldlvFc0I0v9RvLtVjNeLfnxU36s7eyKiImH8MLt08K45VpJ8+KliHmf3RR7l/HDrdM6PQC7nAo63eZ0qO51HVVXARBz/W6LuxLuWiv5EYGgG2caX4OnFxGKbYWRc/StJv4QoUk19ch9WT363bV78B</diagram></mxfile>

View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2022-04-29T07:48:28.855Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/17.4.2 Chrome/100.0.4896.60 Electron/18.0.1 Safari/537.36" etag="4w4kaevo2eMwaoHMQyWn" version="17.4.2" type="device"><diagram id="3q7EresR70bxdE-DzO8g" name="Page-1">7Vhdb+I4FP01SLsPE+WDQHhsAmwfWmk0aDS7T5WbmMRTJ2ZsQ8n8+rmOHUgw7dIVncxKQ6ViXzv29T33HG48CpJy/xdHm+KeZZiOfDfbj4L5yPe9sRvBl7LU2hKNp9qQc5KZSUfDinzHxuga65ZkWPQmSsaoJJu+MWVVhVPZsyHO2XN/2prR/q4blGPLsEoRta1fSCYLcwp/erTfYpIX7c7eZKZHStRONicRBcrYc8cULEZBwhmTulXuE0xV8Nq46OeWL4weHOO4kpc8sKS7Wy++uQ+eS5Z8+1Z/QpvZB1+vskN0aw68+pwki9XK+CzrNhAZEgVWS7mjIC5kSaHpQZOzbZU1A6onJGdPOGGU8eaxwHWjYDKGkTWrpIHXg7PHiJK8gk4K7mPeTjD7HZdqg+6bGfbS+gCYS7x/MTLeId6QqJiVWPIappgHgqnBrD5kre4/HxGfGlPRAbtFFpkcyw8rH2GAhkHiDahEVvRxBllpuozLguWsQnRxtHZwUAAd59wxtjER/YqlrA0GaCtZH0i9p9ro9TCCX2zLU/yK/4EhKuI5lq/MC8/DwjFFkuz6flw9yIGV+vdYCCUHgGsSjG5cLSsC8hQS9L/xYU0o7aSsN/Xj8fwcTw4jb+KJewlP1s3nGjwJx32eTG2eeP7PJIoX/s+ZEl7IFG88JFW8yRBhhmDy+m/1vBO23X+g+8F1DsPzvVlf9+pu7yPmBM6vSNMYB4BtNiRqoSVwdyzPlZRNKDgdP6pWrlp/gExJrqVPNNo32+kvTqRu/GllgJISAoXSHXrE9CMTRBKmhOqRSclKJUxPWKaFrZAnmgjFFPxZmlixSmVRu8uNkUGpkifeMKLUcLED5IRZ1hJKKLg2ytdyn6va1EmJSNmDQGvsCJxu4Wj1A1EC71CWP5SoggCUTSrE1xDLqC+WfmiL5eSMVkbvpZWHOvSXILHruP5sYBbPLhXfaEgazywa36Iqo1fjcQesf6W0xTGqZsYofcqbTLGrjpcYbFct514d3KWriazfnFSnV3edUpwwMXU0pwWkJalycSU2u26PzcHMZnNwhs3Bu7HZs4D8yWz2opMfZTeMBia0N76U0YP+MLdu/qb0kJQOvV+M0l5kpcUKOEqweQ9lAO8p0khs9K3XmuxVCLtkbYqkxskwHoXzc1iTsrn+sl46jX1OyhyOQskj/EfftxyrI+YY3ocRHGc5x7uHRHvmiF1+HVygGnTCHjIgNa2lWzyFNjah/17Y2L/CB2zWJC3ImRuCYbFZkmuCMp5NTyCxyRJOHLf78Wx82svRN+AD3eMdaTPWuWkOFj8A</diagram></mxfile>

View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2021-09-24T08:22:48.526Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/15.2.7 Chrome/93.0.4577.63 Electron/14.0.1 Safari/537.36" etag="QyKkeK-3x-7k9qJw9VZu" version="15.2.7" type="device"><diagram id="PvxKfyiyyjuS7ueLP8cr" name="Page-1">7VnJbtswEP0aHxtYu3xsnO2QogUCtEkvBi2OJDaUKNB0LPfrS1qUtTCJncaBHMAncx4XkfPejDT0yJlm5TVHRfqNYaAje4zLkXMxsm3LHYfyRyHrCgkmXgUknGA9qAHuyF/Q4FijS4Jh0RkoGKOCFF0wYnkOkehgiHO26g6LGe0+tUAJGMBdhKiJ/iJYpBUa2kGD3wBJ0vrJlj+pejJUD9YnWaQIs1ULci5HzpQzJqpWVk6BKufVfqnmXb3Qu90Yh1zsM2FZ3FNy/dO7PXd+30+vHlCGH7/oVZ4QXeoD37IkAS4xjiKSg967WNcOkccoVLPgLIKF9O75KiUC7goUKXglZSCxVGRUWpZszlH0mHC2zPH3paBqyQqPCaVTRhnfrOvEcWxHkcQXgrNHaPVgf+57vuzRewUuoHzRCdbWtVKTwDIQfC2H6Am2q9nQcqzNVcOtVROWtnj1NYa0nJLtyo3HZUM7/Q0EeIaDAUsBapNxkbKE5YheNuj5xpmgVh1Lqxlzy1ihnfsHhFjraEJLwbqUQEnEvZp+5mnrQS+m2hdl21jXRi6P25qkzId6PWU00zZWPa86nzrU65RJH7Alj+AVX9k6/hFPQOwStSkBDhQJ8tTdx8EJtY2Ims0yRPLZzGC6y+OOMDqA+MOu9i1vaPGHJ/HvLX7nM4jfMcSPkUCDC7+f9YdXfnBS/t7Kdz+D8q1Bc5nV5nPL7i5G23y26H2B0frDL6ZQflVf1gOxHAxJs2skOFmgDJ7f3PGx5TfLPSW4vaVv1YXtLu07g2Y4s1ZUL/czKCNV8x/ZK94ePgROpd0bQmDf2s4dNATM4k6m/7OISbcwuhg8CPrvgSMIAv+zBMEhxfzePK2n/mBEPrph1++xa/VYq4JHz+oRt93GO7g0qzulfigFH77E844v/weGu25QjunmctU2w4JSUixgt7N696ZgYQ+C5+5NJ37goAPdm/r9u6N9L07DD3Pv5BX3YhipzfhUqAtoifiJamHgOVED+h0cIhnUZgL/X1LiMILnL7Pnoed6448h5QM5kWbzT0WVUJr/e5zLfw==</diagram></mxfile>

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmlns:UML="http://schema.omg.org/spec/UML/1.4" timestamp="2021-02-24T09:45:59" xmi.version="1.2" verified="false">
<XMI.header>
<XMI.documentation>
<XMI.exporter>umbrello uml modeller 2.32.2 http://umbrello.kde.org</XMI.exporter>
<XMI.exporterVersion>1.6.19</XMI.exporterVersion>
<XMI.exporterEncoding>UnicodeUTF8</XMI.exporterEncoding>
</XMI.documentation>
<XMI.metamodel xmi.name="UML" href="UML.xml" xmi.version="1.4"/>
</XMI.header>
<XMI.content>
<UML:Model isRoot="false" isLeaf="false" isAbstract="false" xmi.id="m1" name="Modèle UML" isSpecification="false">
<UML:Namespace.ownedElement>
<UML:Stereotype isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="folder" name="folder" isSpecification="false" namespace="m1"/>
<UML:Model isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="Logical_View" name="Logical View" isSpecification="false" namespace="m1">
<UML:Namespace.ownedElement>
<UML:Package isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="Datatypes" name="Datatypes" stereotype="folder" isSpecification="false" namespace="Logical_View">
<UML:Namespace.ownedElement>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uavdK7q37LkOh" name="char" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="ufkawOKLwFVVB" name="int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uzxy9VAQAl8Zo" name="float" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uM3cNBXD7rwQp" name="double" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="ucTbxKcoSfjLK" name="bool" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="utJVEyz1OoGNE" name="string" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uU5MRxMdrwAtv" name="unsigned char" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uQT2kLPsjWxg8" name="signed char" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uZK5Y7Hj5gqzZ" name="unsigned int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="utRoC9MI8fjPm" name="signed int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="u8UuqgumCLyeU" name="short int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uA8cQJeKqJz5Z" name="unsigned short int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uHuwjbPmYkFc9" name="signed short int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="ulgMyXtgpbhxB" name="long int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="u7jSPncGk7wmI" name="signed long int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="u3Dtf2l1NvklJ" name="unsigned long int" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="ujcYRZYXjjA1t" name="long double" isSpecification="false" namespace="Datatypes"/>
<UML:DataType isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="uwpQeKnIRYw04" name="wchar_t" isSpecification="false" namespace="Datatypes"/>
</UML:Namespace.ownedElement>
</UML:Package>
<UML:Class isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="u18MnAndGoIap" name="Person" isSpecification="false" namespace="Logical_View">
<UML:Classifier.feature>
<UML:Attribute visibility="public" xmi.id="uMOT45hn1d97P" name="nom" isSpecification="false" type="utJVEyz1OoGNE"/>
<UML:Attribute visibility="public" xmi.id="u4WNVgIiBJnIT" name="prenom" isSpecification="false" type="utJVEyz1OoGNE"/>
<UML:Attribute visibility="public" xmi.id="uErVVOE3Edi65" name="age" isSpecification="false" type="ufkawOKLwFVVB"/>
<UML:Attribute visibility="public" xmi.id="uIv0N06mU024p" name="telephone" isSpecification="false" type="utJVEyz1OoGNE"/>
<UML:Operation isRoot="false" visibility="public" isQuery="false" isLeaf="false" isAbstract="false" xmi.id="ug9AZ7ZtoNS7P" isOverride="false" isVirtual="false" name="set_fullname" isSpecification="false" isInline="false">
<UML:BehavioralFeature.parameter>
<UML:Parameter value="" visibility="private" xmi.id="udlQ9K1hsxsIK" name="nom" isSpecification="false" type="utJVEyz1OoGNE"/>
<UML:Parameter value="" visibility="private" xmi.id="uhlf5uli54pXP" name="prenom" isSpecification="false" type="utJVEyz1OoGNE"/>
</UML:BehavioralFeature.parameter>
</UML:Operation>
</UML:Classifier.feature>
</UML:Class>
<UML:Class isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="u8lMOUL6ex4mF" name="Professional" isSpecification="false" namespace="Logical_View">
<UML:GeneralizableElement.generalization>
<UML:Generalization xmi.idref="u9cRUefGVxlkN"/>
</UML:GeneralizableElement.generalization>
<UML:Classifier.feature>
<UML:Attribute visibility="public" xmi.id="uZKeH0vU5FWVi" name="telephone_pro" isSpecification="false" type="utJVEyz1OoGNE"/>
<UML:Attribute visibility="public" xmi.id="uGC95d04uurX1" name="company" isSpecification="false" type="utJVEyz1OoGNE"/>
<UML:Attribute visibility="public" xmi.id="uY5eKwrnXYHQV" name="job" isSpecification="false" type="utJVEyz1OoGNE"/>
</UML:Classifier.feature>
</UML:Class>
<UML:Generalization visibility="public" child="u8lMOUL6ex4mF" xmi.id="u9cRUefGVxlkN" name="" isSpecification="false" discriminator="" parent="u18MnAndGoIap" namespace="Logical_View"/>
</UML:Namespace.ownedElement>
<XMI.extension xmi.extender="umbrello">
<diagrams resolution="96">
<diagram documentation="" isopen="1" showpubliconly="0" linecolor="#ff0000" backgroundcolor="#ffffff" showattsig="1" type="1" font="Cantarell,11,-1,5,50,0,0,0,0,0" griddotcolor="#d3d3d3" showattribassocs="1" textcolor="#000000" showops="1" showpackage="1" showscope="1" snapgrid="0" showopsig="1" snapy="25" localid="-1" showstereotype="1" showgrid="0" snapcsgrid="0" fillcolor="#ffff00" zoom="100" showatts="1" canvasheight="291" name="Diagramme de classes" canvaswidth="236" xmi.id="uXG2Qk9EYRoTK" linewidth="0" usefillcolor="1" snapx="25">
<widgets>
<classwidget showattsigs="601" width="236" showopsigs="601" showpubliconly="0" showattributes="1" linecolor="#ff0000" font="Cantarell,11,-1,5,50,0,0,0,0,0" isinstance="0" textcolor="#000000" showpackage="1" showscope="1" localid="ujFt023zvo1vJ" y="-5880" showstereotype="1" x="-5652" fillcolor="#ffff00" xmi.id="u8lMOUL6ex4mF" linewidth="0" height="131" usesdiagramfillcolor="0" showoperations="1" usefillcolor="1" usesdiagramusefillcolor="0" autoresize="0"/>
<classwidget showattsigs="601" width="218" showopsigs="601" showpubliconly="0" showattributes="1" linecolor="#ff0000" font="Cantarell,11,-1,5,50,0,0,0,0,0" isinstance="0" textcolor="#000000" showpackage="1" showscope="1" localid="uhCQp0wny5NFv" y="-6040" showstereotype="1" x="-5643" fillcolor="#ffff00" xmi.id="u18MnAndGoIap" linewidth="0" height="119" usesdiagramfillcolor="0" showoperations="1" usefillcolor="1" usesdiagramusefillcolor="0" autoresize="0"/>
</widgets>
<messages/>
<associations>
<assocwidget widgetaid="u8lMOUL6ex4mF" indexa="1" linecolor="#ff0000" font="Cantarell,11,-1,5,50,0,0,0,0,0" type="500" textcolor="none" widgetbid="u18MnAndGoIap" localid="usOwtR7AIwfHo" seqnum="" indexb="1" fillcolor="none" totalcounta="2" xmi.id="u9cRUefGVxlkN" linewidth="0" usesdiagramfillcolor="1" usefillcolor="1" usesdiagramusefillcolor="1" totalcountb="2" autoresize="1">
<linepath layout="Direct">
<startpoint startx="-5513" starty="-5880"/>
<endpoint endx="-5513" endy="-5921"/>
</linepath>
</assocwidget>
</associations>
</diagram>
</diagrams>
</XMI.extension>
</UML:Model>
<UML:Model isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="Use_Case_View" name="Use Case View" isSpecification="false" namespace="m1">
<UML:Namespace.ownedElement/>
</UML:Model>
<UML:Model isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="Component_View" name="Component View" isSpecification="false" namespace="m1">
<UML:Namespace.ownedElement/>
</UML:Model>
<UML:Model isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="Deployment_View" name="Deployment View" isSpecification="false" namespace="m1">
<UML:Namespace.ownedElement/>
</UML:Model>
<UML:Model isRoot="false" visibility="public" isLeaf="false" isAbstract="false" xmi.id="Entity_Relationship_Model" name="Entity Relationship Model" isSpecification="false" namespace="m1">
<UML:Namespace.ownedElement/>
</UML:Model>
</UML:Namespace.ownedElement>
</UML:Model>
</XMI.content>
<XMI.extensions xmi.extender="umbrello">
<docsettings uniqueid="uY5eKwrnXYHQV" viewid="uXG2Qk9EYRoTK" documentation=""/>
<listview>
<listitem id="Views" open="1" type="800">
<listitem id="Entity_Relationship_Model" open="1" type="836"/>
<listitem id="Use_Case_View" open="1" type="802"/>
<listitem id="Component_View" open="1" type="821"/>
<listitem id="Deployment_View" open="1" type="827"/>
<listitem id="Logical_View" open="1" type="801">
<listitem id="uXG2Qk9EYRoTK" open="0" label="Diagramme de classes" type="807"/>
<listitem id="u18MnAndGoIap" open="1" type="813">
<listitem id="uErVVOE3Edi65" open="0" type="814"/>
<listitem id="uMOT45hn1d97P" open="0" type="814"/>
<listitem id="u4WNVgIiBJnIT" open="0" type="814"/>
<listitem id="ug9AZ7ZtoNS7P" open="0" type="815"/>
<listitem id="uIv0N06mU024p" open="0" type="814"/>
</listitem>
<listitem id="u8lMOUL6ex4mF" open="1" type="813">
<listitem id="uGC95d04uurX1" open="0" type="814"/>
<listitem id="uY5eKwrnXYHQV" open="0" type="814"/>
<listitem id="uZKeH0vU5FWVi" open="0" type="814"/>
</listitem>
<listitem id="Datatypes" open="0" type="830">
<listitem id="ucTbxKcoSfjLK" open="0" type="829"/>
<listitem id="uavdK7q37LkOh" open="0" type="829"/>
<listitem id="uM3cNBXD7rwQp" open="0" type="829"/>
<listitem id="uzxy9VAQAl8Zo" open="0" type="829"/>
<listitem id="ufkawOKLwFVVB" open="0" type="829"/>
<listitem id="ujcYRZYXjjA1t" open="0" type="829"/>
<listitem id="ulgMyXtgpbhxB" open="0" type="829"/>
<listitem id="u8UuqgumCLyeU" open="0" type="829"/>
<listitem id="uQT2kLPsjWxg8" open="0" type="829"/>
<listitem id="utRoC9MI8fjPm" open="0" type="829"/>
<listitem id="u7jSPncGk7wmI" open="0" type="829"/>
<listitem id="uHuwjbPmYkFc9" open="0" type="829"/>
<listitem id="utJVEyz1OoGNE" open="0" type="829"/>
<listitem id="uU5MRxMdrwAtv" open="0" type="829"/>
<listitem id="uZK5Y7Hj5gqzZ" open="0" type="829"/>
<listitem id="u3Dtf2l1NvklJ" open="0" type="829"/>
<listitem id="uA8cQJeKqJz5Z" open="0" type="829"/>
<listitem id="uwpQeKnIRYw04" open="0" type="829"/>
</listitem>
</listitem>
</listitem>
</listview>
<codegeneration>
<codegenerator language="C++"/>
</codegeneration>
</XMI.extensions>
</XMI>

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

31
logo.svg Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="110.4211" height="109.8461" id="svg2169" sodipodi:version="0.32" inkscape:version="0.45.1" version="1.0" sodipodi:docbase="/home/bene/Desktop" sodipodi:docname="dessin-1.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs id="defs2171">
<linearGradient id="linearGradient11301" inkscape:collect="always">
<stop id="stop11303" offset="0" style="stop-color:#ffe052;stop-opacity:1"/>
<stop id="stop11305" offset="1" style="stop-color:#ffc331;stop-opacity:1"/>
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" y2="168.1012" x2="147.77737" y1="111.92053" x1="89.136749" id="linearGradient11307" xlink:href="#linearGradient11301" inkscape:collect="always"/>
<linearGradient id="linearGradient9515" inkscape:collect="always">
<stop id="stop9517" offset="0" style="stop-color:#387eb8;stop-opacity:1"/>
<stop id="stop9519" offset="1" style="stop-color:#366994;stop-opacity:1"/>
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" y2="131.85291" x2="110.14919" y1="77.070274" x1="55.549179" id="linearGradient9521" xlink:href="#linearGradient9515" inkscape:collect="always"/>
</defs>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.24748737" inkscape:cx="-260.46312" inkscape:cy="316.02744" inkscape:document-units="px" inkscape:current-layer="layer1" width="131.10236px" height="184.25197px" inkscape:window-width="872" inkscape:window-height="624" inkscape:window-x="5" inkscape:window-y="48"/>
<metadata id="metadata2174">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<g inkscape:label="Calque 1" inkscape:groupmode="layer" id="layer1" transform="translate(-473.36088,-251.72485)">
<g id="g1894" transform="translate(428.42338,184.2561)">
<path style="opacity:1;color:#000000;fill:url(#linearGradient9521);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" d="M 99.75,67.46875 C 71.718268,67.468752 73.46875,79.625 73.46875,79.625 L 73.5,92.21875 L 100.25,92.21875 L 100.25,96 L 62.875,96 C 62.875,96 44.9375,93.965724 44.9375,122.25 C 44.937498,150.53427 60.59375,149.53125 60.59375,149.53125 L 69.9375,149.53125 L 69.9375,136.40625 C 69.9375,136.40625 69.433848,120.75 85.34375,120.75 C 101.25365,120.75 111.875,120.75 111.875,120.75 C 111.875,120.75 126.78125,120.99096 126.78125,106.34375 C 126.78125,91.696544 126.78125,82.125 126.78125,82.125 C 126.78125,82.124998 129.04443,67.46875 99.75,67.46875 z M 85,75.9375 C 87.661429,75.937498 89.8125,78.088571 89.8125,80.75 C 89.812502,83.411429 87.661429,85.5625 85,85.5625 C 82.338571,85.562502 80.1875,83.411429 80.1875,80.75 C 80.187498,78.088571 82.338571,75.9375 85,75.9375 z " id="path8615"/>
<path id="path8620" d="M 100.5461,177.31485 C 128.57784,177.31485 126.82735,165.1586 126.82735,165.1586 L 126.7961,152.56485 L 100.0461,152.56485 L 100.0461,148.7836 L 137.4211,148.7836 C 137.4211,148.7836 155.3586,150.81787 155.3586,122.53359 C 155.35861,94.249323 139.70235,95.252343 139.70235,95.252343 L 130.3586,95.252343 L 130.3586,108.37734 C 130.3586,108.37734 130.86226,124.03359 114.95235,124.03359 C 99.042448,124.03359 88.421098,124.03359 88.421098,124.03359 C 88.421098,124.03359 73.514848,123.79263 73.514848,138.43985 C 73.514848,153.08705 73.514848,162.6586 73.514848,162.6586 C 73.514848,162.6586 71.251668,177.31485 100.5461,177.31485 z M 115.2961,168.8461 C 112.63467,168.8461 110.4836,166.69503 110.4836,164.0336 C 110.4836,161.37217 112.63467,159.2211 115.2961,159.2211 C 117.95753,159.2211 120.1086,161.37217 120.1086,164.0336 C 120.10861,166.69503 117.95753,168.8461 115.2961,168.8461 z " style="opacity:1;color:#000000;fill:url(#linearGradient11307);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,40 @@
def base_conversions():
"""
Conversions entre types via les fonctions de base de python.
"""
# Convertir une chaîne en valeur entière
print(int("26"))
print(float("36.5"))
# Convertir une valeur en chaîne
print(str(36.5))
# Convertir une valeur complexe en chaîne
print(str([1, 2, 3, 4, 5]))
# Convertir un entier en valeur hexadécimale
print(hex(571))
# Conversions mathématiques
print(round(2.5))
print(round(2.45678, 1))
print(round(2.45678, 2))
# Les valeurs dites fausses
print("Conversions booléennes qui renvoient faux")
print(bool(None))
print(bool(0))
print(bool(""))
print(bool(False))
print(bool([]))
# Les valeurs dites vraies (autres que les valeurs fausses)
print("Conversions booléennes qui renvoient vrai")
print(bool(1)) # nombre non nul
print(bool(-1)) # nombre non nul
print(bool("Bonjour")) # chaîne non vide
print(bool(True))
print(bool([1, 3, 5, 7, 9])) # liste non vide
if __name__ == "__main__":
base_conversions()

View File

@ -0,0 +1,3 @@
seq = [25, 40, 55, 19]
for idx, number in enumerate(seq, start=1):
print(f"Itération {idx} : {number}.")

View File

@ -0,0 +1,30 @@
def builtin_introspection():
"""
Test des fonctions natives d'introspection de Python.
Ici on teste les fonctions accessibles sans faire d'import,
et qui permettent d'en savoir plus sur l'état du programme ou de
ses variables.
"""
data1 = "chaîne de caractères"
# Fonction pour vérifier le type d'une variable
print(isinstance(data1, str))
print(issubclass(str, object))
print(issubclass(str, int))
print(type(data1))
# Fonction pour accéder aux attributs d'une variable
print(hasattr(data1, "manger"))
print(hasattr(data1, "upper"))
print(getattr(data1, "upper"))
# Fonctions pour accéder à toutes les variables locales ou globales
print(locals())
print(globals())
# Fonction qui affiche le texte d'aide pour une fonction, méthode etc.
print(help(data1))
print(help(data1.upper))
if __name__ == "__main__":
builtin_introspection()

View File

@ -0,0 +1,34 @@
def base_iterable_tests():
"""
Fonctions de base de python utilisables sur les itérables.
"""
# Tester any, all
iterable1 = [0, 1, 0, 1, 0, 1]
iterable2 = [0, 0, 0, 0, 0, 0]
iterable3 = [1, 1, 1, 1, 1, 1]
iterable4 = [0, 1, 2, 3, 4, 5]
iterable5 = ["a", "b", "c", "d", "e", "f"]
# Vérifier que toutes les valeurs sont "vraies"
print(all(iterable1))
print(all(iterable3))
# Vérifier qu'au moins une valeur est vraie
print(any(iterable1))
print(any(iterable2))
print(any(iterable4))
# Vérifier la longueur de l'itérable
print(len(iterable1))
# Calculer la somme des éléments
print(sum(iterable1))
print(sum(iterable4))
# Trouver la valeur minimale/maximale des éléments
print(min(iterable4))
print(max(iterable4))
print(min(iterable5))
print(max(iterable5))
if __name__ == "__main__":
base_iterable_tests()

View File

@ -0,0 +1,16 @@
"""Example module for variable declaration."""
if __name__ == '__main__':
# Define simple variables
integer1 = 15 # integer value
float1 = 15.0 # floating value
text1 = "Simple text" # string value
boolean1 = True # boolean value
undefined1 = None # special None value
# Display variable contents
print(integer1)
print(float1)
print(text1)
print(boolean1)
print(undefined1)

View File

@ -0,0 +1,62 @@
def base_exception_handling():
"""Code de base pour gérer une exception."""
try:
15 / 0
except ZeroDivisionError:
print("Nous avons eu une erreur de division par zéro !")
except Exception:
print("Exception non prise en charge !")
def exception_with_else():
"""
Gérer une exception, avec la clause else.
La clause else contient du code qui est exécuté si tout se passe
bien.
"""
try:
15 / 1
except ZeroDivisionError:
print("Division par zéro !")
else:
print("Tout s'est bien passé !")
def exception_with_finally():
"""
Gérer une exception, avec la clause finally.
Le bloc de la clause finally est toujours exécuté,
quand une exception est levée ou que tout se passe bien.
le bloc finally est même exécuté si vous essayez de faire un
`return` dans votre bloc `try`, alors que pourtant la directive
`return` interrompt normalement l'exécution de la fonction.
Cas de figure : fermer un fichier ouvert quoi qu'il arrive.
"""
try:
15 / 0
except ZeroDivisionError:
print("Division par zéro !")
return None
finally:
print("Pas si vite, monsieur return !")
def raise_exception():
"""
Lever manuellement une exception simple.
Si vous voyez une erreur dans la console, c'est normal.
"""
raise ValueError("Valeur incorrecte.")
if __name__ == "__main__":
base_exception_handling()
exception_with_else()
exception_with_finally()
raise_exception()

View File

View File

@ -0,0 +1,56 @@
from datetime import date, datetime, timezone
def base_dates():
"""
Gérer des dates de base.
"""
# Créer deux dates
today = date.today()
before = date(2018, 7, 15) # 15 juillet 2018
# Afficher la date du jour, et aussi la formater
print(f"Aujourd'hui nous sommes le {today}")
print(f"Aujourd'hui nous sommes le (formaté) {today:%d/%m/%Y}")
# Afficher la date initialisée manuellement
print(f"La finale de Coupe du monde de football a eu lieu le {before}")
def base_datetimes():
"""
Gérer des dates avec heure.
"""
# Créer deux moments dans le temps
now = datetime.now()
before = datetime(2018, 7, 15, 20, 0) # 15 juillet 2018 à 20h
# Afficher la date du jour, et aussi la formater
print(f"Maintenant nous sommes le {now}")
print(f"Maintenant nous sommes le (formaté) {now:%d/%m/%Y %H:%M:%S}")
# Afficher la date initialisée manuellement
print(f"La finale de Coupe du monde de football a eu lieu à {before}")
def timezone_datetimes():
"""
Gérer des dates avec heure, et avec fuseau horaire.
"""
now = datetime.now(timezone.utc)
before = datetime(2018, 7, 15, 20, 0, tzinfo=timezone.utc) # 15 juillet 2018 à 20h
# Afficher la date du jour, et aussi la formater
# Voir https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
print(f"Maintenant nous sommes le {now}")
print(f"Maintenant nous sommes le (formaté) {now:%d/%m/%Y %H:%M:%S (%Z)}")
# Afficher la date initialisée manuellement
print(f"La finale de Coupe du monde de football a eu lieu à {before}")
if __name__ == "__main__":
# Lancer la fonction qui teste des dates
base_dates()
# Lancer la fonction qui teste des dates avec heure
base_datetimes()
# Lancer la fonction qui teste des dates avec heure et fuseau horaire
timezone_datetimes()

View File

@ -0,0 +1,21 @@
from datetime import datetime, timedelta
def base_datetime_timedelta():
"""
Gérer des dates avec heure et ajouter des intervalles de temps.
"""
# Créer deux moments dans le temps
now: datetime = datetime.now()
interval: timedelta = timedelta(hours=6)
in_six_hours: datetime = now + interval
# Afficher la date du jour, et aussi la formater
print(f"Dans 6 heures on sera le (formaté) {in_six_hours:%d/%m/%Y %H:%M:%S}")
# Afficher la représentation texte de l'intervalle
print(f"L'intervalle de temps est {interval}")
if __name__ == "__main__":
# Lancer la fonction qui teste des dates
base_datetime_timedelta()

View File

@ -0,0 +1,115 @@
from typing import List, Set
class User(object):
"""
Classe représentant un utilisateur.
"""
name: str = None
first_name: str = None
email: str = None
password: str = None
def __init__(self, name, first_name, email, password):
"""
Initialiser un nouvel utilisateur.
Args:
name: nom
first_name: prénom
email: email
password: mot de passe
"""
self.name = name
self.first_name = first_name
self.email = email
self.password = password
def __repr__(self):
"""
Texte affiché pour l'objet quand on le passe dans `print()` ou surtout
que sa représentation textuelle est demandée.
Returns:
Le nom de l'utilisateur.
"""
return self.name
class Group(object):
"""
Classe de groupe d'utilisateurs.
"""
name: str = None
description: str = None
# Ici on a un attribut qui est une liste d'objets d'une autre classe.
# C'est ce qu'on appelle l'agrégation.
# Si on supprime le groupe, les objets utilisateurs existent toujours indépendamment,
# on a ici simplement un lien vers des utilisateurs.
users: Set[User] = None
def __init__(self, name, description=None):
"""
Initialiser un nouveau groupe d'utilisateurs.
Args:
name: nom du groupe
description: texte descriptif, facultatif
"""
self.name = name
self.description = description
def __repr__(self):
"""
Texte affiché pour l'objet quand on le passe dans `print()`
Returns:
Le nom du groupe.
"""
return self.name
def add_user(self, user: User):
"""
Ajouter un utilisateur au groupe.
Args:
user: instance d'utilisateur à ajouter
"""
self.users = self.users or set()
self.users.add(user)
def get_users(self):
"""
Renvoie la liste des utilisateurs dans le groupe.
Returns:
La liste des utilisateurs ajoutés au groupe.
"""
return self.users
if __name__ == "__main__":
# Créer quelques utilisateurs
user1 = User("Martin", "Jacques", "jmartin@example.com", "jmartin")
user2 = User("Carletti", "Audrey", "acarletti@example.com", "acarletti")
user3 = User("Richard", "Luc", "lrichard@example.com", "lrichard")
user4 = User("Havel", "Vaclav", "vhavel@example.com", "vhavel")
# Créer un groupe ou deux
group1 = Group("Amicale des amateurs de froid")
group2 = Group("Amicale des amateurs de chaud")
# Ajouter des utilisateurs aux groupes
group1.add_user(user1)
group1.add_user(user2)
group1.add_user(user3)
group2.add_user(user4)
# Afficher les utilisateurs pour chaque groupe
print(f"Utilisateurs du groupe {group1.name} : {group1.get_users()}")
print(f"Utilisateurs du groupe {group2.name} : {group2.get_users()}")

View File

@ -0,0 +1,64 @@
from typing import Optional
class Animal(object):
"""
Classe de base pour tous les animaux.
"""
pattes: int = None
vertebre: Optional[bool] = None
def manger(self):
"""
Méthode de base pour que l'animal mange.
On ne définit aucun comportement particulier ici.
"""
print("Je suis un animal et je mange.")
class Chien(Animal):
"""
Classe pour le chien.
"""
pattes: int = 4
vertebre = True
def manger(self):
"""
Méthode pour faire manger le chien.
"""
super().manger() # Exécute le code ligne 19
print("Je dirais même plus *wouf* ! Je suis un chien qui mange.")
class Escargot(Animal):
"""
Classe pour l'escargot.
"""
pattes: int = 0
vertebre = False
def manger(self):
"""
Méthode pour faire manger l'escargot.
"""
print("Slurp *mange, mais on ne sait pas comment*")
if __name__ == "__main__":
# Créer des nouvelles instances d'animaux
escargot = Escargot()
chien = Chien()
# Et voir comment ça se comporte quand on appelle la méthode `manger`.
# Le fait que plusieurs classes héritant d'une autre classe ont des comportements
# différents pour les mêmes méthodes s'appelle du polymorphisme.
# Les méthodes peuvent aussi avoir des arguments différents mais le même nom.
print(escargot.manger())
print(chien.manger())

View File

@ -0,0 +1,92 @@
class Person(object):
"""
Classe représentant une personne.
"""
nom: str = None
prenom: str = None
age: int = None
def __init__(self, *_args, **kwargs):
"""
Initialiser les valeurs d'une nouvelle personne.
Args:
*_args: Liste d'arguments non positionnels
**kwargs: Arguments nommés, utilisés pour initialiser les attributs de notre objet.
"""
self.nom = kwargs.get("nom", None)
self.prenom = kwargs.get("prenom", None)
self.age = kwargs.get("age", None)
def set_nom_complet(self, nom, prenom):
"""
Méthode pour définir les noms et prénom de l'individu.
Args:
nom: Nom de famille à attribuer
prenom: Prénom à attribuer à l'objet
"""
self.prenom = prenom
self.nom = nom
def get_nom_complet(self):
"""
Méthode pour retrouver d'un coup le nom complet de l'individu.
Returns:
Nom complet, sous la forme "<prenom> <nom>"
"""
if self.prenom and self.nom:
return f"{self.prenom} {self.nom}"
else:
return "Nom complet non renseigné"
class Professional(Person):
"""
Classe représentant un professionel, héritant de `Person`.
"""
telephone_pro: str = None
job: str = None
def __init__(self, *_args, **kwargs):
"""
Initialiser les données d'un nouveau professionel.
On peut également initialiser le prénom, nom et âge.
Args:
*_args: Liste d'arguments non positionnels
**kwargs: Liste d'arguments nommés, utilisés pour initialiser les attributs de notre objet.
"""
super().__init__(*_args, **kwargs)
self.telephone_pro = kwargs.get("telephone_pro", None)
self.job = kwargs.get("job", None)
def get_nom_complet(self):
"""
Méthode pour retrouver d'un coup le nom complet de l'individu.
Returns:
Nom complet, sous la forme "<prenom> <nom>"
"""
base_nom = super().get_nom_complet()
return f"{base_nom} ({self.job})"
if __name__ == "__main__":
# Ici on se crée plusieurs objets, 2 professionnels et une personne
pro1 = Professional(nom="Mario", prenom="Mario", age=38, telephone_pro="0799999999", job="Jardinier")
pro2 = Professional(nom="Mario", prenom="Luigi", age=37, telephone_pro="0799999999", job="Plombier")
mec = Person(nom="Bertrand", prenom="Julien", age=30)
# Les deux classe Person et Professional ont des méthodes `get_nom_complet()` différentes, tester
print(mec.get_nom_complet())
print(pro1.get_nom_complet())

View File

@ -0,0 +1,42 @@
import pdir
def use_dir():
"""
Découvrir l'introspection via la fonction `dir()`
Nécessite pdir2 pour montrer une sortie plus sympa aux étudiants.
"""
# En python, tout est un objet, et a donc des attributs et des méthodes.
# Le truc bien, c'est qu'en Python, on peut aussi manipuler et retrouver les
# propriétés de ces attributs et méthodes.
chaine = "Bonjour"
# À la place de `pdir` on pouvait utiliser `dir` qui fait partie de python,
# mais qui affiche la liste des attributs de l'objet de façon beaucoup moins
# lisible.
print(pdir(chaine))
def check_attrs():
"""
Via l'introspection, accéder à des attributs d'objets programmatiquement.
"""
chaine = "Bonjour"
# Une chaîne a toujours une méthode `capitalize`, donc ça va fonctionner
if hasattr(chaine, "capitalize"):
print("L'objet a bien une fonction `capitalize`.")
print(getattr(chaine, "capitalize"))
# Mais une chaîne n'a pas d'attribut `doesnotexist`.
if hasattr(chaine, "doesnotexist"):
print("L'objet a un attribut `doesnotexist`.")
else:
print("L'objet n'a pas d'attribut `doesnotexist`.")
if __name__ == "__main__":
print("Utilisation de la fonction `dir` :")
use_dir()
print("Utilisation de l'introspection d'attributs :")
check_attrs()

View File

View File

@ -0,0 +1,11 @@
class DelTest:
def __del__(self):
print("Suppression.")
a = DelTest()
b = a
# On s'attend à afficher Suppression avant d'afficher la référence de b.
del a
print(b)

View File

@ -0,0 +1,6 @@
import csv
with open("demo-file-985.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
for row in reader: # row sera une séquence, contenant un élement par colonne de la ligne en cours
print(row) # Affiche la ligne courante du CSV séparée dans une liste.

File diff suppressed because one or more lines are too long

View File

View File

@ -0,0 +1,42 @@
def write_file():
"""
Écrire un fichier binaire très simple.
En Python, on ne peut pas tout simplement écrire des chaînes dans un
fichier dit binaire. Dans Python 3, on fait la différence entre binaire et texte.
Quand on manipule des fichiers texte, on peut y écrire des données `str`.
Par contre, quand on manipule des fichiers binaires, on manipule des données plus
brutes, de type `bytes` (ou `bytearray`)
"""
f = open("demo.bin", "wb")
f.write(bytearray([32, 33, 34, 35, 36, 37, 38, 39, 40])) # (9)
f.write(b"Bonjour les amis") # (16) ça passe car les caractères sont ASCII et leur code tient dans un byte
f.write("Bonjour les héros, ça va bien ?".encode("utf-8")) # (33)
f.close()
def read_file():
"""
Lire dans un fichier binaire très simple.
"""
f = open("demo.bin", "rb")
tableau = f.read(9)
chaine1 = f.read(16)
chaine2 = f.read(33).decode("utf-8")
f.seek(0)
data = f.read()
f.close()
# Afficher les données lues dans le fichier
print(tableau)
print(chaine1)
print(chaine2)
# Pour tester, revenir au début du fichier et tout lire d'une traite
print(data)
if __name__ == "__main__":
write_file()
read_file()

View File

@ -0,0 +1 @@
!"#$%&'(Bonjour les amisBonjour les héros, ça va bien ?

View File

@ -0,0 +1 @@
Contenu de notre fichier texte de démo !

View File

@ -0,0 +1,43 @@
with open("demo.txt", "r", encoding="utf-8") as f:
pass
# écrire un manager
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
# Just by defining __enter__ and __exit__ methods we can use our new class in a with statement. Lets try:
#
# with File('demo.txt', 'w') as opened_file:
# opened_file.write('Hola!')
#
# Our __exit__ method accepts three arguments. They are required by every __exit__ method which is a part of a Context Manager class. Lets talk about what happens under-the-hood.
#
# The with statement stores the __exit__ method of the File class.
# It calls the __enter__ method of the File class.
# The __enter__ method opens the file and returns it.
# The opened file handle is passed to opened_file.
# We write to the file using .write().
# The with statement calls the stored __exit__ method.
# The __exit__ method closes the file.
# We did not talk about the type, value and traceback arguments of the __exit__ method. Between the 4th and 6th step, if an exception occurs, Python passes the type, value and traceback of the exception to the __exit__ method. It allows the __exit__ method to decide how to close the file and if any further steps are required. In our case we are not paying any attention to them.
#
# What if our file object raises an exception? We might be trying to access a method on the file object which it does not supports. For instance:
#
# with File('demo.txt', 'w') as opened_file:
# opened_file.undefined_function('Hola!')
#
# Lets list the steps which are taken by the with statement when an error is encountered:
#
# It passes the type, value and traceback of the error to the __exit__ method.
# It allows the __exit__ method to handle the exception.
# If __exit__ returns True then the exception was gracefully handled.
# If anything other than True is returned by the __exit__ method then the exception is raised by the with statement.

View File

@ -0,0 +1,12 @@
import os, glob
# traverse root directory, and list directories as dirs and files as files
for root, dirs, files in os.walk("/home/steve/Code/python/initiation"):
for filename in files:
path = os.path.join(root, filename)
print(path)
# https://docs.python.org/fr/3/library/glob.html
files = glob.glob("/home/steve/Code/python/initiation/**/*.py", recursive=True)
print(files)

Some files were not shown because too many files have changed in this diff Show More