|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- % Programmation avec Python (chapitre 13)
- % Dimitri Merejkowsky
-
- #
-
- \center \huge Rappels
-
- # 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
- <generator object <genexpr> at 0x7f654c272138>
- >>> list(generator)
- [0, 1, 2]
- ```
-
- 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)
- [0, 1, 2]
- >>> 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]
- ```
-
- #
-
- \center \huge Consignes
-
-
- #
-
- * Repartir de git.e2li.org/dmerejkowsky/cours-python/sources/lister
-
- * Ajouter le code pour gérer correctement l'attribut <is_directory> de l
- class Entry
-
- * Utiliser TDD pour la suite!
-
- * Faire en sorte d'afficher les répertoires avec un `/` à la fin
- * Afficher la date de modification de manière plus lisible
- (voir `time` dans la bibliothèque standard)
- * Grouper les éléments sur plusieurs colonnes
- * Implémenter d'autres options (`--sort=time`, etc.)
- * Réécrire parse_args() en utilisant `argparse`.
-
- # Solutions et questions
-
- Envoyez-moi un email à dimitri@e2li.org !
|