Browse Source

chapitre 13 prêt

master
Dimitri Merejkowsky 5 years ago
parent
commit
4da2812da7
1 changed files with 422 additions and 2 deletions
  1. +422
    -2
      sessions/python-13.md

+ 422
- 2
sessions/python-13.md View File

@@ -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
<generator object <genexpr> 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]
```

#