diff --git a/documentation/02-language-basics-data.md b/documentation/02-language-basics-data.md index 75c8c71..bea3957 100644 --- a/documentation/02-language-basics-data.md +++ b/documentation/02-language-basics-data.md @@ -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 diff --git a/documentation/06-extra-types.md b/documentation/06-extra-types.md index dcd8b72..daf623c 100644 --- a/documentation/06-extra-types.md +++ b/documentation/06-extra-types.md @@ -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 diff --git a/documentation/08-text-files-xml.md b/documentation/08-text-files-xml.md index 39a40ef..0f64247 100644 --- a/documentation/08-text-files-xml.md +++ b/documentation/08-text-files-xml.md @@ -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: diff --git a/documentation/09-sqlite.md b/documentation/09-sqlite.md index 9fd3d9c..c552b91 100644 --- a/documentation/09-sqlite.md +++ b/documentation/09-sqlite.md @@ -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/)) diff --git a/documentation/11-code-documentation.md b/documentation/11-code-documentation.md index 41d3f8f..ba06d20 100644 --- a/documentation/11-code-documentation.md +++ b/documentation/11-code-documentation.md @@ -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 ```