% Programmation avec Python (chapitre 13) % Dimitri Merejkowsky
\center \huge Rappels
$ python3 -m venv /path/to/foo
$ /path/to/foo/bin/pip install requests
# ou
$ source /path/to/foo/bin/activate
(foo) $ pip install requests
# 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 test_foo.py
============== test session starts =================
test_foo.py . [100%]
=========== 1 passed in 0.01 seconds ===============
\center \huge Générateurs
\vfill
\normalsize Ces objects qui sont “presque” des listes ...
Itération sur une liste
for i in [1, 2, 3]:
print(i)
Clés d’un dictionnaire
for key in {"a": 1, "b": 2, "c": 3}.keys():
print(key)
Valeurs d’un dictionnaire
for value in {"a": 1, "b": 2, "c": 3}.values():
print(key)
>>> 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
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.
>>> 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
Si vraiment vous en avez besoin, vous pouver les convertir en liste
avec list().
>>> 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}
# 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]
On peut transformer la séquence originale:
>>> sequence = [element.upper() for element in sequence]
>>> sequence
["A", "B", "C"]
\vfill
On peut changer le type:
>>> as_strings = ["1", "2", "3"]
>>> numbers = [int(x) for x in as_strings]
>>> numbers
[1, 2, 3]
>>> numbers = [1, 2, 3, 4]
>>> odds = [x for x in numbers if x % 2 == 1]
>>> odds
[1, 3]
\vfill
>>> numbers = [1, 2, 3, 4]
>>> odds_squared = [x*x for x in numbers if x % 2 == 1]
>>> odds_squared
[1, 9]
Même principe:
>>> scores = [("bob", 2), ("alice", 3)]
>>> my_dict = {t[0]:t[1] for t in scores}
>>> my_dict
{"bob": 2, "alice": 3}
\vfill
>>> numbers = [-1, 2, -3, 3]
>>> numbers = {abs(x) for x in numbers}
>>> numbers
{1, 2, 3}
Python évalue d’habitude de manière “gourmande”:
def foo():
..
def bar():
...
x = foo(bar(y))
On calcule d’abord y, puis bar(y) puis foo(bar(y))
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:
>>> 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é
>>> generator = (x for x in range(0, 3))
>>> list(generator)
[0, 1, 2]
>>> list(generator)
[]
On peut aussi appeler next() sur un générateur, au lieu de for ... in
>>> generator = (x for x in range(0, 2))
>>> next(generator)
0
>>> next(generator)
1
>>> next(generator)
StopIteration
Avec une méthode __next__()
class Squares:
def __init__(self):
self.value = 0
def __next__(self):
self.value += 1
return self.value ** 2
>>> squares = Squares()
>>> next(squares)
1
>>> next(squares)
4
>>> next(squares)
9
>>> [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
Avec __iter__ et __next__:
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
>>> 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:
>>> 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)
[]
On peut aussi faire des générateurs avec yield
pour mettre le générateur “en pause”, et return pour
terminer:
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]
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
On peut aussi chaîner les fonctions génératrices
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.
Envoyez-moi un email à dimitri@e2li.org !