Update chapters

Updated chapters 2, 6, 8, 9 and 11.
This commit is contained in:
2025-07-11 21:36:41 +02:00
parent 77aa231f5b
commit 086da10d79
5 changed files with 182 additions and 192 deletions

View File

@ -296,6 +296,8 @@ 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
print(9 in b) # Vérifier que 9 fait partie des clés de b
print(9 in b.values()) # Vérifier que 9 fait partie des valeurs de b
```
Les dictionnaires ont les mêmes contraintes que les ensembles uniquement au niveau de leurs clés; vous

View File

@ -7,189 +7,213 @@ author: Steve Kossouho
----
## Un type pour les dates et heures
## `datetime` : Des moments dans le temps
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}.
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, un type pour stocker des moments dans le temps, nommé `datetime`,
est fourni.
```python {.numberLines}
from datetime import datetime
timestamp = 1577836800
as_datetime = datetime.fromtimestamp(timestamp)
print(as_datetime)
now = datetime.now() # l'instant actuel
midnight = datetime(2021, 1, 31) # 31 janvier 2021 à minuit
noon = datetime(2021, 1, 31, hour=12) # 31 janvier 2021 à 12h00
print(now)
print(midnight)
print(noon)
```
----
Les objets de type `datetime`{.python} sont exploitables grâce à plusieurs
méthodes et attributs :
## Convertir une date en chaîne
```python {.numberLines}
from datetime import datetime
Pour convertir une date vers une chaîne, ou convertir une chaîne vers un objet de date, consultez toujours la même ressource :
now = datetime.now()
print(now.year) # Année
print(now.month) # Mois (int)
print(now.day) # Jour (int)
print(now.hour) # Heure (int)
print(now.weekday()) # Jour de la semaine (int)
print(now.date()) # objet qui représente une journée
print(now.timestamp()) # Timestamp Unix (float)
```
[Table de référence pour le format de dates](https://docs.python.org/fr/3/library/datetime.html#strftime-and-strptime-format-codes)
----
## `date` : Une journée
Le type `date` du module `datetime` permet de représenter des jours,
sans notion d'heure.
```python {.numberLines}
from datetime import date
event_1 = date(1944, 6, 6) # 6 juin 1944
event_2 = date(1986, 4, 26) # 26 avril 1986
print(event_1.year)
print(event_1.month)
print(event_1.day)
```
----
## `timedelta` : Intervalles de temps
Un troisième type de données, `timedelta`, permet de représenter des durées.
Vous pouvez obtenir des objets de ce type de deux façons :
- Soit en calculant la différence entre deux `datetime` ou `date`;
- Soit en créant explicitement un objet de ce type;
- Soit en ajoutant des objets de ce type.
```python {.numberLines}
from datetime import timedelta, datetime, date
event_1 = date(2003, 6, 11) # 11 juin 2003
event_2 = date(2003, 9, 27) # 27 septembre 2003
elapsed = event_2 - event_1 # 108 jours
custom_duration = timedelta(weeks=4, days=2, hours=5, minutes=10)
double_duration = custom_duration * 2.0
event_end = event_1 + custom_duration
```
----
Les objets `timedelta` possèdent peu d'attributs et de méthodes.
Nous pouvons récupérer d'un tel objet les informations suivantes :
- `timedelta.days`
- `timedelta.seconds`
- `timedelta.microseconds`.
Ces informations additionnées donnent la durée complète.
Cette dernière peut être obtenue en secondes grâce à la méthode `total_seconds()` :
```python {.numberLines}
from datetime import timedelta
interval = timedelta(weeks=4.3, hours=14, minutes=31, seconds=53.234_567)
print(f"Nombre de jours : {interval.days}")
print(f"Secondes restantes : {interval.seconds}")
print(f"Microsecondes restantes : {interval.microseconds}")
print(f"Durée totale (s) : {interval.total_seconds():.3f}")
```
----
## Récupérer un `datetime` depuis un [timestamp]{.naming}
Un [timestamp]{.naming} est un instant exprimé sous la forme d'un nombre entier (ou flottant).
Dans la plupart des environnements de programmation, un timestamp s'exprime en nombre de secondes
écoulées depuis le `1er janvier 1970 à 00:00:00 UTC`. On appelle ce moment l'[epoch Unix]{.naming}.
On peut calculer un objet `datetime`{.python} depuis un timestamp, ou inversément, un timestamp depuis un objet `datetime`{.python}.
```python {.numberLines}
from datetime import datetime
stamp_1 = 1577836800.24 # 1 janvier 2020 à 01h00
event_1 = datetime.fromtimestamp(stamp_1) # 1 janvier 2020 à 01h00
event_2 = datetime(1996, 4, 1) # I Got the Vibration
stamp_2 = event_2.timestamp() # 828309600
print(f"La date du timestamp 1 est : {event_1}")
print(f"Le timestamp de la date 2 est : {stamp_2}")
```
----
## Générer une chaîne depuis une date
Pour convertir des informations d'un objet `date` ou `datetime` et générer une chaîne (ou vice-versa)
vous devez vous référer en premier lieu à une ressource de référence :
[Table de référence pour le formatage de dates](https://docs.python.org/fr/3/library/datetime.html#strftime-and-strptime-format-codes)
Ce tableau de référence reprend les spécifications des mêmes fonctionnalités telles que fournies par la
bibliothèque standard du C.
```python {.numberLines}
from datetime import datetime
event = datetime.now()
print(event.strftime("%d/%m/%Y %H:%M et %S secondes."))
print(f"{event:%d/%m/%Y %H:%M et %S secondes}") # via des f-strings
```
----
### É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.
Par défaut, Python n'utilise pas la langue configurée sur votre système d'exploitation,
mais l'anglais pour générer des chaînes de caractères depuis vos objets `datetime`;
Pour demander à Python d'utiliser votre configuration système, utilisez une chaîne vide
pour définir le code de langue à utiliser.
```{.python .numberLines}
```python {.numberLines}
from datetime import datetime
import locale
locale.setlocale(locale.LC_ALL, "") # utilise les paramètres de langue de l'utilisateur
event_1 = datetime.now()
print(f"{event_1:%A %d %B %Y}")
```
[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 :
## Obtenir un objet `datetime` depuis une chaîne
```{.python .numberLines}
Ici, il s'agit de comprendre la structure d'une chaîne de caractères contenant
une date et en déduire un objet `datetime`.
```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")
text = "31/10/1991 15:17"
# La méthode `strptime` est utilisée (string parse time)
moment = datetime.strptime(text, "%d/%m/%Y %H:%M")
print(moment)
```
----
## Bonus : Fuseaux horaires
## Extra : 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())
```
Par défaut, les objets de date que vous confectionnez sont dits [naïfs]{.naming}, 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.
----
### 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)
Depuis la version 3.9, Python propose un module `zoneinfo`, qui offre un type `ZoneInfo`
applicable à des objets `datetime` :
```{.python .numberLines}
from datetime import datetime
```python {.numberLines}
from datetime import datetime, UTC
from zoneinfo import ZoneInfo
moment = datetime(1975, 3, 1, hour=13, tzinfo=ZoneInfo("Europe/Madrid"))
moment_madrid = datetime(1975, 3, 1, hour=13, tzinfo=ZoneInfo("Europe/Madrid"))
moment_utc = datetime(1966, 10, 5, tzinfo=UTC)
```
----
## 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 :
L'écosystème Python propose des bibliothèques simplifiant l'usage des dates :
- [Arrow](https://arrow.readthedocs.io/en/latest/) : Better dates and times for Python
- [Pendulum](https://pendulum.eustace.io/) : Python datetimes made easy

View File

@ -51,8 +51,8 @@ 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
```bash {.numberLines}
pip install lxml types-lxml # pour installer la bibliothèque externe
```
----
@ -62,16 +62,17 @@ pip install lxml # pour installer la bibliothèque externe
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
- Méthode [XPath]{.naming}, où l'on référence des éléments par rapport à leur "chemin" dans le document
- Méthode [ElementPath]{.naming}, proposée par LXML via les méthodes `find` et `findall`
La plus simple des méthodes disponibles consiste à se baser sur le XPATH pour trouver des éléments :
```{.python .numberLines}
```python {.numberLines}
from lxml import etree
tree = etree.parse(r"source.xml") # récupère l'élément racine
root = 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")
items = root.xpath("/CATALOG/CD")
```
- [Guide complet sur le XPath](https://www.ionos.com/digitalguide/websites/web-development/xpath-tutorial/)
@ -85,12 +86,12 @@ items = tree.xpath("/CATALOG/CD")
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}
```python {.numberLines}
from lxml import etree
tree = etree.parse(r"source.xml") # récupère l'élément racine
root = 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")
items = root.xpath("/CATALOG/CD")
for cd in items:
for attribute in cd:

View File

@ -46,7 +46,7 @@ Cette table ne possède pas de contrainte particulière et autorise à enregistr
```python {.numberLines}
import sqlite3
connection = sqlite3.connect("intro.sqlite3", isolation_level=None)
connection = sqlite3.connect("intro.sqlite3", autocommit=True)
cursor = connection.cursor() # cet objet est utilisé pour envoyer des requêtes
cursor.execute("""
CREATE TABLE IF NOT EXISTS person (
@ -126,7 +126,7 @@ Les objets de type `sqlite3.Row`{.python} sont utilisables comme des dictionnair
----
## Hors-programme : Requêtes universelles avec les ORMs
## Hors-programme : Les [ORM]{.naming}
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;
@ -138,7 +138,7 @@ 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 problème est, entre autres questionnements, à l'origine de l'existence des ORMs ([Object Relational Mapper]{.naming}).
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.
@ -167,43 +167,6 @@ en charge par l'ORM).
----
## 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/))

View File

@ -12,11 +12,11 @@ author: Steve Kossouho
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 ?
Mais saviez-vous que Python considère le concept de documentation comme étant à part ?
----
```{.python .numberLines}
```python {.numberLines}
print(__doc__) # Ne provoque pas d'erreur, mais affiche `None`
```
@ -30,7 +30,7 @@ Cet attribut « magique » laisse entendre que la notion de documentation fait p
### Documentation dans la bibliothèque standard
```{.python .numberLines}
```python {.numberLines}
import sqlite3
print(sqlite3.__doc__) # documentation du module SQLite3
@ -45,7 +45,7 @@ récupérer quelque chose dans son attribut `__doc__`{.python}.
Pour documenter un module, c'est finalement assez simple :
```{.python .numberLines}
```python {.numberLines}
"""Texte de documentation du module."""
import os, sys
print(os.linesep, sys.getwindowsversion())
@ -81,7 +81,7 @@ Pour documenter, par exemple, un module, il y a deux façons de faire :
Vous pouvez considérer qu'une seule petite phrase peut servir à décrire votre module :
```{.python .numberLines}
```python {.numberLines}
"""Traitement des données d'épuration des eaux."""
```
@ -89,7 +89,7 @@ Vous pouvez considérer qu'une seule petite phrase peut servir à décrire votre
### Documentation plus longue
```{.python .numberLines}
```python {.numberLines}
"""
Traitement des données d'épuration des eaux.
@ -117,7 +117,7 @@ Possibilité d'ajouter des sections, des exemples, soyez exhaustifs si vous le s
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}
```bash {.numberLines}
pip install pdoc # installer la bibliothèque
pdoc [nom du module ou nom du script python] # lance un serveur pour tester
```
@ -137,7 +137,7 @@ Depuis Python 3.5 (fin 2015), le langage propose un moyen d'indiquer aux dévelo
Pour annoter une variable et définir quel est son type attendu, on peut :
```{.python .numberLines}
```python {.numberLines}
from tartempion import MyClass
variable1: int = 15 # on indique qu'on attend un entier
@ -166,7 +166,7 @@ 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}
```python {.numberLines}
variable: int | float = 15 # soit un entier soit un flottant devrait y être associé
```
@ -176,7 +176,7 @@ variable: int | float = 15 # soit un entier soit un flottant devrait y être as
On peut faire la même chose avec des arguments de fonctions :
```{.python .numberLines}
```python {.numberLines}
# value et default devraient être des entiers
def do_some_important_things(value: int, default: int = 1):
print(value, default)
@ -187,7 +187,7 @@ def do_some_important_things(value: int, default: int = 1):
Et on peut également décrire le type attendu du retour des fonctions et méthodes :
```{.python .numberLines}
```python {.numberLines}
def do_trivial_things() -> float: # on indique que la fonction retourne un flottant
return 100 / 15
```