diff --git a/sessions/python-13.md b/sessions/python-13.md index c95558e..7c42912 100644 --- a/sessions/python-13.md +++ b/sessions/python-13.md @@ -1,11 +1,431 @@ % Programmation avec Python (chapitre 13) % Dimitri Merejkowsky +# \center \huge Rappels -* venv -* pytest, assert, TDD +# Virtualenvs + +* Création: + +``` +$ python3 -m venv /path/to/foo +``` + +* Utilisation: + +``` +$ /path/to/foo/bin/pip install requests +# ou +$ source /path/to/foo/bin/activate +(foo) $ pip install requests +``` + + +# Tests + +```python +# foo.py +def add_3(x): + return x + 3 + +# test_foo.py +import foo + +def test_add_3(): + result = foo.add_3(2) + assert result == 5 +``` + +# pytest + +``` +$ pytest test_foo.py +============== test session starts ================= +test_foo.py . [100%] +=========== 1 passed in 0.01 seconds =============== +``` + +# TDD + +* Une discipline +* 4 règles +* Un cycle + + +# Règles + + +* Règle 1 : Il est interdit d'écrire du code de production, *sauf* si c'est pour faire passer un test qui + a échoué. +* Règle 2 : Il est interdit d'écrire plus de code que celui qui est nécessaire pour provoquer une erreur + dans les tests (n'importe quelle erreur) +* Règle 3 : Il est interdit d'écrire plus de code que celui qui est nécessaire pour faire passer + un test qui a échoué +* Règle 4 : Une fois que tous les tests passent, il est interdit de modifier le code sans s'arrêter + pour considérer la possibilité d'un refactoring. + +# Cycle + + +* RED: on écrit un test qui échoue +* GREEN: on fait passer le test +* REFACTOR: on refactor le code de production et le code de test + +# + +\center \huge Générateurs + +\vfill + +\normalsize Ces objects qui sont "presque" des listes ... + +# Retour sur les boucles + +Itération sur une liste +```python +for i in [1, 2, 3]: + print(i) +``` + +Clés d'un dictionnaire +```python +for key in {"a": 1, "b": 2, "c": 3}.keys(): + print(key) +``` + +Valeurs d'un dictionnaire +```python +for value in {"a": 1, "b": 2, "c": 3}.values(): + print(key) +``` + +# Pas des listes + +```python +>>> my_dict = {"a": 1, "b": 2, "c": 3} +>>> my_dict.keys() +dict_keys(['a', 'b', 'c']) +>>> my_dict.keys()[0] +TypeError: 'dict_keys' object is not subscriptable +``` + +# Vues + +En fait, `.keys()`, `.values()`, `.items()` renvoient des *vues*. + +Elles changent quand le dictionnaire changent, mais on ne peut pas +s'en servir pour changer la taille du dictionnaire. + +```python +>>> my_dict = {"a": 1, "b": 2, "c": 3} +>>> for k in my_dict.keys(): +>>> if k == "a": +>>> del my_dict[k] +RuntimeError: dictionary changed size during iteration +``` + +# Vues (2) + +Si vraiment vous en avez besoin, vous pouver les convertir en liste +avec `list()`. + +```python +>>> my_dict = {"a": 1, "b": 2, "c": 3} +>>> for k in list(my_dict.keys()): +>>> if k == "a": +>>> del my_dict[k] +>>> my_dict +{"b": 2, "c": 3} +``` + +# Listes par compréhension + +```python +# 1 +sequence = ["a", "b", "c"] +new_sequence = [] +for element in sequence: + new_sequence.append(element.upper()) + + +# 2 +sequence = ["a", "b", "c"] +new_sequence = [element.upper() for element in sequence] +``` + +# Listes par compréhension (2) + +On peut transformer la séquence originale: + +```python +>>> sequence = [element.upper() for element in sequence] +>>> sequence +["A", "B", "C"] +``` + +\vfill + +On peut changer le type: + +```python +>>> as_strings = ["1", "2", "3"] +>>> numbers = [int(x) for x in as_strings] +>>> numbers +[1, 2, 3] +``` + +# Filtrer une liste + +```python +>>> numbers = [1, 2, 3, 4] +>>> odds = [x for x in numbers if x % 2 == 1] +>>> odds +[1, 3] +``` + +\vfill + +```python +>>> numbers = [1, 2, 3, 4] +>>> odds_squared = [x*x for x in numbers if x % 2 == 1] +>>> odds_squared +[1, 9] +``` + +# Dictionnaires et ensembles par compréhension + +Même principe: + +```python +>>> scores = [("bob", 2), ("alice", 3)] +>>> my_dict = {t[0]:t[1] for t in scores} +>>> my_dict +{"bob": 2, "alice": 3} +``` + +\vfill + +```python +>>> numbers = [-1, 2, -3, 3] +>>> numbers = {abs(x) for x in numbers} +>>> numbers +{1, 2, 3} +``` + +# Avantage des compréhensions + +* Code plus succint +* Code plus rapide :) +* Code "idiomatique" + +# Apparté: Python est gourmand + +Python évalue d'habitude de manière "gourmande": + +```python +def foo(): + .. + + +def bar(): + ... + + +x = foo(bar(y)) +``` + +On calcule d'abord `y`, puis `bar(y)` puis `foo(bar(y))` + + +# Générateurs + +Concept: fournir les valeurs une par une, à la demande. + +Le code *à l'intérieur* des compréhensions est un générateur. + +On peut le voir si on utilise des parenthèses: + +```python +>>> generator = (x for x in range(0, 3)) +>>> generator + at 0x7f654c272138> +>>> list(generator) +[1, 2, 3] +``` + +Les générateurs sont "feignants": ils ne calculent leur valeurs que quand +c'est demandé + +# Les générateurs s'épuisent + +```python +>>> generator = (x for x in range(0, 3)) +>>> list(generator) +[1, 2, 3] +>>> list(generator) +[] +``` + +# next() + +On peut aussi appeler `next()` sur un générateur, au lieu de `for ... in` + +```python +>>> generator = (x for x in range(0, 2)) +>>> next(generator) +0 +>>> next(generator) +1 +>>> next(generator) +StopIteration +``` + + + + +# Fabriquer un générateur à l'aide d'une classe + +Avec une méthode `__next__()` + +```python +class Squares: + def __init__(self): + self.value = 0 + + def __next__(self): + self.value += 1 + return self.value ** 2 +``` + +```python +>>> squares = Squares() +>>> next(squares) +1 +>>> next(squares) +4 +>>> next(squares) +9 +``` + + +# Les générateurs ne sont pas des itérateurs + +```python +>>> [x for x in s if x < 10] +TypeError: 'Squares' object is not iterable +``` + +Pour faire `for in`, il faut un itérateur, en plus d'un générateur + + +# Fabriquer un itérateur à l'aide d'une classe + +Avec `__iter__` *et* `__next__`: + +```python +class SquaresLessThan: + def __init__(self, stop_value): + self.value = 0 + self.stop_value = stop_value + + def __iter__(self): + return self + + def __next__(self): + self.value += 1 + res = self.value ** 2 + if res > self.stop_value: + raise StopIteration() + return res +``` + + + +# Fabriquer un itérateur à l'aide d'une classe + +```python +>>> squares = SquaresLessThan(100) +>>> [x for x in squares] +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +``` + +On retrouve les propriétés des expressions génératrices: +```python +>>> squares = SquaresLessThan(100) +>>> squares[2] +TypeError: 'SquaresLessThan' object is not subscriptable +>>> squares = SquaresLessThan(100) +>>> list(squares) +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +>>> list(squares) +[] +``` + +# yield + +On peut aussi faire des générateurs avec `yield` +pour mettre le générateur "en pause", et `return` pour +terminer: + + +```python +def squares(max_value): + x = 1 + res = 1 + while res <= max_value: + yield res + x += 1 + res = x * x + return + +>>> s = squares(100) +>>> [x for x in s] +[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] +``` + +# Combiner avec une classe génératrices + +```python +class Squares: + def __init__(self): + self.value = 0 + + def __next__(self): + self.value += 1 + return self.value ** 2 + +def squares_less_than(max_value): + squares = Squares() + while True: + x = next(squares) + if x > max_value: + return + yield x +``` + +# yield from + +On peut aussi chaîner les fonctions génératrices + +```python +def integers(max_value): + x = 0 + while x < max_value: + yield x + x += 1 + return + + +def zero_to_five(): + yield from integers(5) + + +>>> s = zero_to_five() +>>> [x for x in s] +[0, 1, 2, 3, 4] +``` #