You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 
 
 
 
 

7.6 KiB

% 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

# 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

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)

Pas des listes

>>> 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.

>>> 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().

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

# 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:

>>> 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]

Filtrer une liste

>>> 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]

Dictionnaires et ensembles par compréhension

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}

Avantage des compréhensions

  • Code plus succint
  • Code plus rapide :)
  • Code “idiomatique”

Apparté: Python est gourmand

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))

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:

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

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

>>> 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__()

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

Les générateurs ne sont pas des itérateurs

>>> [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__:

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

>>> 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)
[]

yield

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]

Combiner avec une classe génératrices

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

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 !