formations/python-initiation/initiation.md

23 KiB

Python

notes:

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

par

Julien Palard julien@palard.fr

https://mdk.fr

notes:

Introduce yourself!

Ça couvre les types de bases survol quelques strucutres de contrôle, et quelques fonctions natives.

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 utilisent Python ?

::: notes

https://insights.stackoverflow.com/trends

Installation

https://python.org

— ou —

::: notes

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 Comparisons

>>> 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", "sel"} | {"œuf", "sel"} == {"sel", "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'

Mais en cas d'ambiguité…

>>> {"farine", "levure"} + {"sel", "œuf"}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'set' and 'set'

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

>>> print("Brioche")
Brioche

notes:

C'est leur première fonction, s'attarder sur la syntaxe !

print

>>> 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']

Les 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
>>> ingredient = ingredients[2]
>>> print(ingredient)
sucre

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 Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Notes: On connaît les attributs.

Exemple

>>> ISIN = {
...   "GLE": "FR0000130809",
...   "PARRO": "FR0004038263",
...   "AM": "FR0000121725",
... }

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 integers

@given(integers(min_value=2,
                max_value=1000))
def test_fib(i):
    assert fib(i) == fib(i-1) + fib(i-2)

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.

Garder son code lisible

Attention au code vieillissant, au « je ne rajoute qu'une ligne ou deux à cette fonction et c'est réglé ».

Notes: Deux ans après la fonction fait 800 lignes, et personne ne l'a vu venir. flake8 peut aider.

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

Et ./configure --with-pydebug.

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 :

>>> "L'été".encode()
b"L'\xc3\xa9t\xc3\xa9"
>>> list("L'été".encode())
[76, 39, 195, 169, 116, 195, 169]

décoder

C'est le contraire ...

>>> bytes([76, 39, 195, 169, 116, 195, 169]).decode()
"L'été"

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, sauf cette année) ! Les meetups locaux hors période de pandémie !