% 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 Atelier