23 KiB
Python
Par
Julien Palard julien@palard.fr
notes:
Introduce yourself!
Ça couvre les types de bases survol quelques strucutres de contrôle, et quelques fonctions natives.
Et juste pour le doctest :
import random
random.seed(42)
ingredients = ["sel", "farine", "sucre", "levure", "beurre", "œuf"]
def randint(a, b):
return 4 # https://xkcd.com/221/
il_reste_de_la_pâte = 0
recettes = [
{'titre': 'Broyés du Poitou', 'ingredients': "farine", "durée": 35},
{'titre': 'Œufs mimosa', 'ingredients': "", "durée": 20},
{'titre': 'Houmous', 'ingredients': "", "durée": 13},
]
Python : Introduction
Python est un langage de programmation permettant de s'exprimer de manière concise et lisible.
Qui utilise Python ?
YouTube, Dropbox, Reddit, Instagram, Spotify, NASA…
Combien ?
::: notes
Oui ça fait ~175TB/jour sur PyPI.org.
https://insights.stackoverflow.com/trends baisse en 2023, pourquoi ?
Quelle version ?
Installation
— ou —
::: notes
- On windows use the WSL, or gitforwindows.org if you can't
- https://docs.python.org/3/using/windows.html
- https://docs.python.org/3/using/mac.html
- On windows, don't install from the Microsoft Store.
Démarrer un interpréteur
Sur Windows :
py
Sur tous les autres OS :
python3
::: notes
- Définir « Interpréteur »
py
sur Windows trouve l'interpréteur le plus récent.
L'interpréteur
Parfois appelé le REPL ou la console interactive.
$ python3
>>> 5 + 2
7
>>>
notes:
Permet d'essayer un peu de Python sans pour autant ouvrir un fichier.
Et oui, même après 10 ans de Python, on l'utilise encore.
Expliquer les parties "R", "E", "P", "L".
L'interpréteur
Il en existe plusieurs : Celui natif à Python, IDLE, IPython, …
Il ressemble généralement soit à ça :
>>>
soit à ça :
In [1]:
Testons l'interpréteur
>>> 10
10
notes:
L'interpréteur à lu les caractères 1
0
, a compris que c'était un
nombre entier, l'a stocké dans sa représentation interne, un objet,
puis nous l'a représenté à son tour avec deux caractères 1
et 0
pour qu'on puisse le lire.
C'est votre nouvelle calculatrice
>>> 60 * 60 * 4
14400
Les exceptions
>>> 5 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
notes:
Lisez TOUJOURS la dernière ligne en premier !
Types natifs
Booléens
>>> True
True
>>> False
False
Nombres
>>> 42
42
Nombres
>>> 18446744073709551616
18446744073709551616
Nombres
>>> 3.1415
3.1415
Chaînes de caractères
>>> "Beurre ou huile d'olive ?"
"Beurre ou huile d'olive ?"
notes:
Expliquer ce qu'est une chaîne, sans parler de pointeurs, on est pas dans un cours de C89.
Chaînes de caractères
>>> 'Cuisson "au beurre" !!'
'Cuisson "au beurre" !!'
notes:
Les triples quotes apparaissent jour 2.
Listes
>>> ["1 kg de farine", "12 œufs", "130 g de beurre"]
['1 kg de farine', '12 œufs', '130 g de beurre']
notes:
La représentation est souvent du Python valide.
Listes
>>> ["farine", 1000]
['farine', 1000]
notes:
Attention à ne pas abuser du mélange autorisé des types.
n-uplets, tuple
>>> ("œufs", 12)
('œufs', 12)
>>> ("sucre", 130)
('sucre', 130)
notes:
C'est la virgule qui fait le n-uplet, pas les parenthèses.
Pensez au n-uplet comme une structure C, a record, pas comme une liste, par exemple des coordonnées : (x, y).
Listes et tuples
>>> [("farine", 1000), ("œufs", 12), ("sucre", 130)]
[('farine', 1000), ('œufs', 12), ('sucre', 130)]
notes:
Une liste c'est de la donnée, ce qu'elle contint c'est de la donnée.
Ensembles
{"farine", "sucre", "œuf", "levure", "sel", "beurre"}
notes:
Un ensemble n'est pas ordonné.
Dictionnaires
{
"un verre": "25 cl",
"un petit verre": "12 cl",
"qs": "quantité suffisante"
}
notes:
On associe une valeur à une clé. Utile seulement si on ne connaît pas les clefs à l'avance, sinon c'est une classe.
Les opérateurs
Les opérateurs mathématiques
>>> 10 + 10
20
>>> 10.5 + 2
12.5
Les opérateurs mathématiques
>>> (4 * 10**1) + (2 * 10**0)
42
Les opérateurs mathématiques
>>> 10 / 2
5.0
Les opérateurs
>>> "Bri" + "oche"
'Brioche'
notes:
It's called concatenation of strings.
Les opérateurs
>>> "mur" * 2
'murmur'
notes:
Tant qu'il n'y a pas d'ambiguité, c'est implémenté.
Les opérateurs
>>> ["farine", "œufs"] + ["sucre", "sel"]
['farine', 'œufs', 'sucre', 'sel']
Les comparaisons
>>> 10 < 1
False
>>> 10 == 10
True
>>> 10 >= 20
False
notes:
Déconseiller l'utilisation de is
, de toute facons PyLint leur dira
quand l'utiliser.
Logique
>>> True or False
True
>>> True and False
False
>>> not True
False
notes:
On utilisera ça plus tard, avec les structures de contrôle.
Test d'appartenance
>>> "chocolat" in "pain au chocolat"
True
Test d'appartenance
>>> "sel" in {"farine", "œuf", "sucre", "sel", "levure"}
True
Travailler avec les ensembles
>>> {"farine"} | {"œuf", "sucre"} == {"sucre","farine","œuf"}
True
notes:
C'est une union.
Travailler avec les ensembles
>>> {"farine", "levure", "sel"} & {"œuf", "sel", "sucre"}
{'sel'}
notes:
Une intersection.
Mais en cas d'ambiguité…
>>> "farine" * "sucre"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type 'str'
Les variables
Affectation
>>> preparation = 20
>>> cuisson = 30
>>> preparation + cuisson
50
notes:
JAMAIS dire : On met 20 dans « preparation ».
Affectation multiple
>>> preparation, cuisson = 20, 30
>>> preparation
20
>>> cuisson
30
Accès par indice
>>> etapes = ["preparation", "cuisson", "dégustation"]
>>> etapes[0]
'preparation'
>>> etapes[1]
'cuisson'
>>> etapes[2]
'dégustation'
notes:
On réutilise le nom pour accéder au contenu.
Bien prendre le temps d'expliquer la syntaxe ici.
Accès par clé
>>> definitions = {
... "un verre": "25 cl",
... "un petit verre": "12 cl",
... "qs": "quantité suffisante"
... }
...
>>> definitions["un petit verre"]
'12 cl'
Les fonctions
>>> print("Brioche")
Brioche
notes:
C'est leur première fonction, s'attarder sur la syntaxe !
>>> print("La moitié de 130 g :", 130 // 2, "g")
La moitié de 130 g : 65 g
notes:
En effet, le P de REPL étant print
, le print est implicite dans un REPL.
Mais le REPL sert a tester : on peut bien tester print dans le REPL.
Exercices:
- Print 42
- Number of seconds in a year
- Using operators
str, list, int, ...
>>> str(130)
'130'
str, list, int, ...
>>> int("130")
130
len
>>> len(["farine", "œufs", "sucre"])
3
>>> len("farine")
6
notes:
Exercise: Character counting
range
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(5, 10))
[5, 6, 7, 8, 9]
help
Affiche la documentation de n'importe quoi, essayez :
help(str)
help(list)
- ...
notes:
Accepte aussi une valeur (et donc une variable) mais attention : si la
variable est une chaîne, help
n'affichera pas la documentation des
chaînes.
sorted
>>> sorted({"sel", "farine", "sucre"})
['farine', 'sel', 'sucre']
importer des modules
>>> from random import choice
>>> print(choice(["Pain au chocolat", "Chocolatine"]))
Pain au chocolat
notes:
Exercice : Import.
Les instructions
if
>>> if "sucre" in ingredients and "œuf" in ingredients:
... print("Commencer par blanchir les œufs.")
Commencer par blanchir les œufs.
notes:
Parler de l'indentation !
Notez le ...
, on a du appyer un coup en « entrée » pour fermer ce bloc.
1 était premier, avant, mais ça casse le théorème « Every possible whole number can be written as a unique product of primes ».
Le else
Après un bloc if
, on peut ajouter un bloc else
:
>>> if "sucre" in ingredients and "œuf" in ingredients:
... print("Commencer par blanchir les œufs.")
... else:
... print("Commencer comme vous voulez.")
Commencer par blanchir les œufs.
Le elif
Après un if
, on peut ajouter un ou des bloc elif
:
>>> grams = 1250
>>> if grams == 0:
... print("Ne pas en mettre")
... elif grams < 1000:
... print("En mettre", grams, "grammes.")
... else:
... kg, g = divmod(grams, 1000)
... print("En mettre", kg, "kg", "et", g, "g.")
...
En mettre 1 kg et 250 g.
notes:
Parler de pass
et de ...
.
for
>>> for ingredient in ingredients:
... print(ingredient)
...
sel
farine
sucre
levure
beurre
œuf
>>> ingredient = ingredients[0]
>>> print(ingredient)
sel
>>> ingredient = ingredients[1]
>>> print(ingredient)
farine
…
for
>>> for lettre in "Œuf":
... print(lettre)
...
Œ
u
f
>>>
for
>>> for i in range(5):
... print(i)
0
1
2
3
4
notes:
Exercice : Square numbers, powers of two, comparisons.
L'instruction while
Très rarement utilisée car le for
est bien plus pratique, sert
cependant dans quelques cas:
while True:
while il_reste_du_travail_à_faire:
L'instruction while
>>> while il_reste_de_la_pâte:
... faire_une_crêpe()
...
Les méthodes
Sur les chaînes
>>> s = "Pâte à crêpe"
>>> s.count("e")
2
>>> s.startswith("Pâte")
True
>>> s.split()
['Pâte', 'à', 'crêpe']
notes:
Exercise : Counting Words.
Sur les chaînes
>>> s = "Durée : {} minutes."
>>> s.format(3600 // 60)
'Durée : 60 minutes.'
Sur les listes
>>> ingredients = ["1 kg de farine", "12 œufs"]
>>> ingredients.append("130 g de beurre")
>>> ingredients
['1 kg de farine', '12 œufs', '130 g de beurre']
Sur les dictionnaires
>>> definitions = {
... "un verre": "25 cl",
... "un petit verre": "12 cl",
... "qs": "quantité suffisante"
... }
>>> definitions.items()
dict_items([('un verre', '25 cl'), ('un petit verre', '12 cl'), ('qs', 'quantité suffisante')])
Les variables (suite)
Le type des variables
En Python, les variables ne sont que des noms.
Des « étiquettes » qu'on colle aux objets.
Comme sur les pots de confiture.
Le type des variables
Seules les valeurs sont typées.
L'étiquette n'a pas de goût, c'est la confiture qui a du goût.
notes:
Toutes les valeurs sont des objets.
Sans. Exceptions.
On peut « coller » plusieurs étiquettes à une même valeur.
C'est pour ça que pour n = 10
on dit "n est assigné à 10", et non "10 est mis dans n".
Immuables vs modifiables
Certains types sont modifiables, d'autres, non.
notes:
On dit qu'elles sont immuables (immutable en anglais).
Attention, les variables sont toujours ... variables, nous n'avons pas de constantes.
Les types modifiables
- On peut ajouter à une liste.
- On peut vider un ensemble.
- On peut supprimer une clef d'un dictionnaire.
- ...
notes:
- Listes
- Dictionnaires
- Ensembles
- ...
Les types immuables
- On ne peut pas dire que maintenant 10 vaut 12.
- Ni que faux devient vrai.
- Ni qu'une paire contient maintenant trois éléments.
- ...
notes:
- Les chaînes
- Les n-uplets
- Les entiers
- Les booléens
- ...
Pour les chaînes c'est discutable, mais avoir des chaînes immuables est confortable (clef de dictionnaires par exemple, ou la garantie qu'un appel à une fonction avec une chaîne en paramètre ne va pas la modifier).
La vérité
En Python, ce qui est vide est faux, 0
est faux, None
est faux,
False
est faux. Le reste est vrai :
>>> bool("Non vide")
True
>>> bool([]) # Une liste vide
False
>>> bool(0.0)
False
notes:
Attention à la sémantique : if foo
est différent de if foo is True
.
Leur rappeler que c'est pylint qui leur dira quand utiliser is
, leur
dire quand même : pour True
, False
, et None
.
Les fonctions
Syntaxe
def ma_fonction(ses_paramètres):
... # Le code de la fonction
notes:
Passer du temps sur la syntaxe et le vocabulaire
- fonction
- paramètre, argument
return
Exemple
>>> def temps_total(temps_de_preparation, temps_de_cuisson):
... return temps_de_preparation + temps_de_cuisson
...
>>> temps_total(30, 20)
50
Paramètres
Une fonction prend des paramètres et renvoie une valeur.
def fahrenheit_to_celsius(fahrenheit):
return (fahrenheit - 32) * 5/9
Arguments
On peut donc lui donner des arguments :
>>> fahrenheit_to_celsius(320)
160.0
>>> fahrenheit_to_celsius(390)
198.88888888888889
>>> fahrenheit_to_celsius(450)
232.22222222222223
notes:
Exercices: First function, Print even numbers, ...
Les chaînes
>>> """Elle préfère "l'huile d'olive"."""
'Elle préfère "l\'huile d\'olive".'
notes:
~ fin de jour 1 début de jour 2
Les docstrings
def une_fonction():
"""Une courte description."""
...
return ...
Les nombres
Les opérateurs mathématiques
>>> 0.1 + 0.1
0.2
Les opérateurs mathématiques
>>> 0.1 + 0.2
0.30000000000000004
notes:
https://0.30000000000000004.com
for
et while
break
et continue
break
sert à interrompre une boucle, continue
sert à passer à
l'élément suivant. Qu'on soit dans un for
ou dans un while
.
break
>>> while True:
... if not il_reste_de_la_pâte:
... break
... faire_une_crêpe()
...
continue
>>> for recette in recettes:
... if "sésame" in recette["ingredients"]:
... continue
... if "noix de Pécan" in recette["ingredients"]:
... continue
... print(recette["titre"])
Broyés du Poitou
Œufs mimosa
Houmous
Les exceptions : try
>>> try:
... int("2 kg")
... except ValueError:
... print("Raté")
Raté
La notation en compréhension
C'est transformer ça :
>>> durées = []
>>> for recette in recettes:
... durées.append(recette["durée"])
>>> durées
[35, 20, 13]
La notation en compréhension
en :
>>> [recette["durée"] for recette in recettes]
[35, 20, 13]
La notation en compréhension
Ou :
def compte_recettes(recettes, ingrédient):
trouvées = []
for recette in recettes:
if ingrédient in recette["ingredients"]:
trouvées.append(recette)
return len(trouvées)
La notation en compréhension
en :
def compte_recettes(recettes, ingrédient):
return len(
[
recette
for recette in recettes
if ingrédient in recette["ingredients"]
]
)
notes:
Elle devrait s'écrire sur une seule ligne, mais, vidéoprojecteur...
Les slices
slices en anglais
notes:
On pourrait traduire par « tranches » et filer la métaphore culinaire…
['incontestable', 'contestable', 'testable', 'stable']
seq = 'incontestable'
Les slices
>>> seq = 'incontestable'
>>> seq[0]
'i'
Les slices
11
0123456789 |
incontestable—12
|
10
>>> seq[5:9]
'test'
>>> seq[5:13]
'testable'
>>> seq[7:13]
'stable'
Les slices
>>> seq[:2]
'in'
>>> seq[2:]
'contestable'
Les slices
>>> seq[-1]
'e'
Les slices
>>> seq[-6:]
'stable'
Les slices
>>> "perso"[::2]
'pro'
notes:
dict = ['ados', 'assit', 'engager', 'éroder', 'épater', 'ivres', 'soda', 'tissa', 'regagne', 'erodé', 'retapé', 'servi']
Les slices
>>> for word in dict:
... if word[::-1] in dict:
... print(word, word[::-1])
ados soda
assit tissa
engager regagne
épater retapé
ivres servi
soda ados
tissa assit
regagne engager
retapé épater
servi ivres
Les slices
seq[<start>:<stop>:<step>]
Les classes
La syntaxe
class LeNomDeLaClasse:
"""Sa docstring, comme pour une
fonction.
"""
# Le corps de la classe.
Notes:
Dédiaboliser l'héritage, l'héritage multiple, les interfaces, les classes abstraites, les méthodes virtuelles, ...
À retenir
On pourrait trier les données en deux types :
- celles dont on connaît les attributs → classes
- celles dont on ne connaît pas les attributs → dictionnaires
Notes:
Le contexte, le métier, les bibliothèques utilisées peuvent générer des cas particuliers, cette règle n'est pas absolue.
préciser peut être : "dont on connaît à toutes les étapes du programme".
Exemple
class Recette:
def __init__(self, nom, ingrédients):
self.nom = nom
self.ingrédients = ingrédients
Notes: On connaît les attributs.
Exemple
>>> ingrédients = {
... 'farine': 1000,
... 'eau': 615,
... 'levure': 10,
... 'sel': 20
... }
Notes: On ne les connaît pas.
Syntaxe → les méthodes
class Dice:
def throw(self):
self.value = randint(1, 6)
Notes: Une classe ne sert pas à stocker des fonctions, mais des données. Pensez aux structs C.
La données d'abord, l'algorithme après.
Syntaxe → le constructeur
class DiceCup:
def __init__(self, dices):
self.dices = dices
def shake(self):
...
Notes: Leur faire faire implémenter le repr, min, max, et mean.
Leur faire faire 1000 tirages dans un Counter, avec des valeurs ENTIẼRES.
BiaisedDice(Dice) est un bon exemple d'héritage.
Utilisation
>>> dice = Dice()
>>> dice.throw()
>>> dice.value
4
Utilisation
>>> cup = DiceCup(
... [Dice() for _ in range(10)]
... )
>>> cup.shake()
Notes:
Faire quelques exemples d'héritage simples avant de passer a la suite.
super() considered super() !
pip, venvs, conda
pip
C'est l'outil standard pour installer un paquet.
$ python3 -m pip install <package-name>
Mais, ça installe où ?
venv
C'est l'outil standard pour indiquer où installer les paquets.
On peut ainsi avoir plusieurs environnements, un par projet par exemple.
Notes:
Pratique pour avoir des versions différentes.
venv
$ python3 -m venv --prompt test .venv/
$ source .venv/bin/activate
(test) $ python3 -m pip install pytest
Notes: Dépendant du shell, les envoyer sur library/venv.html. Insister sur le côté "trashable du venv" :
- Ne rien mettre dans .venv
- rm -fr .venv # au moindre souci
conda
$ conda create --name test
$ conda activate test
(test) $ conda install numpy
Tester
pytest
(test) $ mkdir tests/
(test) $ pip install pytest
(test) $ editor tests/test_dice.py
Notes: C'est l'occasion de parler de assert.
hypothesis
from hypothesis import given
from hypothesis.strategies import floats
@given(floats())
def test_temperature_conversions(temp):
assert fahrenheit_to_celsius(
celsius_to_fahrenheit(temp)) == temp
Les bonnes pratiques
Notes: Prérequis: pip et venv.
~fin jour 2 / début jour 3.
Bonnes habitudes
There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.
Bonnes habitudes
Pas plus de 7.
Les « linters »
Il existe plusieurs outils pour « relire » votre code :
- flake8,
- pylint,
- mypy,
- black,
- bandit,
- isort.
Et un pour les unifier tous : tox
.
Notes: Leur faire implémenter un is_prime(x)
pour jouer avec.
La règle des 7.
flake8
Dans un venv :
(test) $ pip install flake8
(test) $ pip install flake8-bugbear
(test) $ flake8 --max-complexity 9 *.py
Notes:
Flake8 est rapide, n'effectuant pas les imports
il ne peut repérer
qu'une catégorie de problèmes.
9 est trop bas, 15 est probablement un bon choix.
pylint
(test) $ pip install pylint
(test) $ pylint is_prime.py
mypy
mypy
fonctionne avec des annotations de types.
(test) $ pip install mypy
(test) $ mypy is_prime.py
Notes: --ignore-missing-imports
bandit
Bandit cherche les failles de sécurité...
(test) $ pip install bandit
(test) $ bandit is_prime.py
tox
Permet de lancer les tests:
- sur plusieurs interpréteurs (s'ils sont installés),
- de plusieurs outils,
- en parallèle.
Notes: c.f. gh/JulienPalard/oeis.
pdb
breakpoint()
PYTHONDEVMODE=y
Notes: Voir mon bashrc :] Surtout "viable" depuis la 3.8.
*
, **
*
Signifie « plusieurs », comme dans une liste ou un n-uplet.
>>> begin, *rest = range(5)
>>> begin
0
>>> rest
[1, 2, 3, 4]
Notes:
Attention, initiation : Le but est de savoir que ça existe, savoire le lire.
Équivaut à: begin, rest = seq[0], seq[1:]
*
>>> def sum(*args):
... print(args)
...
>>> sum(1, 2, 3, 4, 5)
(1, 2, 3, 4, 5)
*
>>> [0, 0, 0, *range(3)]
[0, 0, 0, 0, 1, 2]
*
>>> print(*range(5))
0 1 2 3 4
**
Signifie « plusieurs, nommés », comme dans un dictionnaire.
>>> def p(**kwargs):
... for key, value in kwargs.items():
... print(key, "→", value)
...
>>> p(x=10, y=12)
x → 10
y → 12
**
>>> defaults = {"path": "./",
... "pattern": "*.txt"}
>>> {**defaults, "pattern": "*.md"}
{'path': './', 'pattern': '*.md'}
**
>>> def p(x, y):
... print(f"x → {x}")
... print(f"y → {y}")
...
>>> point = {"x": 10, "y": 12}
>>> p(**point)
x → 10
y → 12
L'encodage
encoder
C'est transformer une chaîne en octets, pour son transport ou stockage :
>>> "Pâte à crêpe".encode()
b'P\xc3\xa2te \xc3\xa0 cr\xc3\xaape'
>>> list("Pâte à crêpe".encode())
[80, 195, 162, 116, 101, 32, 195, 160, 32, 99, 114, 195, 170, 112, 101]
décoder
C'est le contraire ...
>>> b'P\xc3\xa2te \xc3\xa0 cr\xc3\xaape'.decode()
'Pâte à crêpe'
Notes:
Parler d'unicode, d'UTF-8, de latin-1. Ne pas oubilier de mentionner que latin-1 et companie sont à taille fixe, et qu'UTF-8 est à taille variable.
Les modules utiles
- argparse
- re
- csv (quand on a pas Pandas)
- subprocess
La communauté
Come for the language, stay for the community
— Brett Cannon
Les PyCons
La PyConFR (tous les ans) ! Les meetups locaux !