├── .gitignore
├── LICENCE.md
├── Makefile
├── README.md
├── add_title_level.py
├── gen_archive.py
├── gen_titles.py
├── logo_cours.png
├── src
├── 0-presentation.md
├── 1-starters
│ ├── 0-startings.md
│ ├── 1-containers
│ │ ├── 0-containers.md
│ │ ├── 1-contains.md
│ │ ├── 2-len.md
│ │ ├── 3-indexables.md
│ │ ├── 4-slices.md
│ │ ├── 5-collections.md
│ │ ├── 6-tp.md
│ │ └── x-conclusion.md
│ ├── 2-iterables
│ │ ├── 0-iterables.md
│ │ ├── 1-for.md
│ │ ├── 2-iterables.md
│ │ ├── 3-itertools.md
│ │ ├── 4-unpacking.md
│ │ ├── 5-tp.md
│ │ └── x-conclusion.md
│ └── 3-mutables-hashables
│ │ ├── 0-mutables-hashables.md
│ │ ├── 1-mutables.md
│ │ ├── 2-egality-identity.md
│ │ ├── 3-hashables.md
│ │ ├── 4-tp.md
│ │ └── x-conclusion.md
├── 2-functions
│ ├── 0-functions.md
│ ├── 1-callables
│ │ ├── 0-callables.md
│ │ ├── 1-functions-classes-lambdas.md
│ │ ├── 2-parameters.md
│ │ ├── 3-call.md
│ │ ├── 4-callables.md
│ │ ├── 5-operator-functools.md
│ │ ├── 6-tp.md
│ │ └── x-conclusion.md
│ ├── 2-annotations-signatures
│ │ ├── 0-annotations-signatures.md
│ │ ├── 1-annotations.md
│ │ ├── 2-inspect.md
│ │ ├── 3-signatures.md
│ │ ├── 4-tp.md
│ │ └── x-conclusion.md
│ └── 3-decorators
│ │ ├── 0-decorators.md
│ │ ├── 1-deco.md
│ │ ├── 2-parameterize.md
│ │ ├── 3-wraps.md
│ │ ├── 4-tp.md
│ │ └── x-conclusion.md
├── 3-further
│ ├── 0-further.md
│ ├── 1-generators
│ │ ├── 0-generators.md
│ │ ├── 1-generator.md
│ │ ├── 2-send.md
│ │ ├── 3-methods.md
│ │ ├── 4-yield-from.md
│ │ ├── 5-comprehension.md
│ │ ├── 6-list-generators.md
│ │ ├── 7-tp.md
│ │ └── x-conclusion.md
│ ├── 2-context-managers
│ │ ├── 0-context-managers.md
│ │ ├── 1-with.md
│ │ ├── 2-open.md
│ │ ├── 3-internal.md
│ │ ├── 4-contextlib.md
│ │ ├── 5-reusability.md
│ │ ├── 6-tp.md
│ │ └── x-conclusion.md
│ └── 3-accessors-descriptors
│ │ ├── 0-accessors-descriptors.md
│ │ ├── 1-accessors.md
│ │ ├── 2-descriptors.md
│ │ ├── 3-properties.md
│ │ ├── 4-methods.md
│ │ ├── 5-tp.md
│ │ └── x-conclusion.md
├── 4-classes
│ ├── 0-classes.md
│ ├── 1-types
│ │ ├── 0-types.md
│ │ ├── 1-instance-class-metaclass.md
│ │ ├── 2-new.md
│ │ ├── 3-inheritance-parameters.md
│ │ ├── 4-tp.md
│ │ └── x-conclusion.md
│ ├── 2-metaclasses
│ │ ├── 0-metaclasses.md
│ │ ├── 1-type.md
│ │ ├── 2-metaclass.md
│ │ ├── 3-function-metaclass.md
│ │ ├── 4-tp.md
│ │ └── x-conclusion.md
│ └── 3-abstract-classes
│ │ ├── 0-abstract-classes.md
│ │ ├── 1-abc.md
│ │ ├── 2-isinstance.md
│ │ ├── 3-issubclass.md
│ │ ├── 4-collections.md
│ │ ├── 5-tp.md
│ │ └── x-conclusion.md
├── 5-exercises
│ ├── 0-exercises.md
│ ├── 2-3-decorators
│ │ ├── 0-decorators.md
│ │ ├── 1-check-type.md
│ │ ├── 2-memoize.md
│ │ ├── 3-singledispatch.md
│ │ └── 4-tail-rec.md
│ ├── 3-2-context-managers
│ │ ├── 0-context-managers.md
│ │ ├── 1-changedir.md
│ │ ├── 2-contextmanager.md
│ │ └── 3-suppress.md
│ ├── 3-3-accessors-descriptors
│ │ ├── 0-accesors-descriptors.md
│ │ └── 1-property.md
│ └── 4-2-metaclasses
│ │ ├── 0-metaclasses.md
│ │ ├── 1-lazy-evaluation.md
│ │ └── 2-immutable.md
└── x-conclusion.md
└── title.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | *.pdf
3 | *.zip
4 | manifest.json
5 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/
2 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PDF = cours_python_avance.pdf
2 | ZIP = cours_python_avance.zip
3 | SRC = $(shell find src -name "*.md" | sort -n)
4 |
5 | FLAGS = --top-level-division=part --toc
6 |
7 | GEN = $(PDF) $(ZIP)
8 |
9 | $(PDF): title.md $(SRC)
10 | pandoc -V lang=fr -V geometry:margin=1in -V colorlinks=true $^ -o $@ $(FLAGS)
11 |
12 | $(ZIP): $(SRC)
13 | ./gen_archive.py $@ $^
14 |
15 | clean:
16 | rm -f $(GEN)
17 |
18 | re: clean $(GEN)
19 |
20 | .PHONY: clean re
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notions de Python avancées
2 |
3 | Découvrez Python plus en profondeur.
4 |
5 | ## Lecture
6 |
7 | Le tutoriel est publié sur *Zeste de savoir* à l'adresse suivante : .
8 |
9 | L'ensemble du cours est rédigé en *Markdown* est peut donc être consulté directement depuis *Github* en parcourant le dossier [`src/`](src/).
10 |
11 | ## Compilation
12 |
13 | Le *Makefile* présente des règles de compilation vers deux formats :
14 |
15 | * *PDF* : `make cours_python_avance.pdf` (nécessite *pandoc* et *latex*) ;
16 | * Archive *ZIP* *Zeste de savoir* : `make cours_python_avance.zip` (nécessite *python*).
17 |
18 | 
19 |
20 | ## Licence
21 |
22 | Cours sous licence [CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/deed.fr).
23 |
--------------------------------------------------------------------------------
/add_title_level.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import re
4 | import os
5 |
6 | files = []
7 |
8 | for directory, _, filenames in os.walk('src'):
9 | for filename in filenames:
10 | files.append(os.path.join(directory, filename))
11 |
12 | code = False
13 | def handle_line(line):
14 | global code
15 | if re.match(r'```', line):
16 | code = not code
17 | return line
18 | if code:
19 | return line
20 |
21 | m = re.match(r'#+ .+$', line)
22 | if m:
23 | line = '#' + line
24 | return line
25 |
26 | for filename in files:
27 | with open(filename, 'r') as f:
28 | lines = f.readlines()
29 | lines = [handle_line(l) for l in lines]
30 | with open(filename, 'w') as f:
31 | f.write(''.join(lines))
32 |
--------------------------------------------------------------------------------
/gen_archive.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import json
4 | import sys
5 | import os.path
6 | import re
7 | from collections import OrderedDict
8 | from zipfile import ZipFile
9 |
10 | archive_name = sys.argv[1]
11 | sections = sys.argv[2:]
12 |
13 | manifest = {
14 | 'slug': 'notions-de-python-avancees',
15 | 'title': 'Notions de Python avancées',
16 | 'version': 2,
17 | 'description': 'Découvrez Python plus en profondeur',
18 | 'type': 'TUTORIAL',
19 | 'licence': 'CC BY-SA'
20 | }
21 |
22 | from collections import OrderedDict
23 | container = OrderedDict()
24 | # Number of nested levels
25 | document_depth = 0
26 | trans = str.maketrans('','','#*_`\n')
27 |
28 | # Split filenames into a dict that represent file hierarchy
29 | for section in sections:
30 | *path, filename = section.split('/')
31 | document_depth = max(document_depth, len(path))
32 | parent = container
33 | for p in path:
34 | parent = parent.setdefault(p, OrderedDict())
35 | parent[filename] = section
36 |
37 | # Prefix for 1st-level titles
38 | title_prefix = '#' * (document_depth + 1)
39 |
40 | # Rewrite titles of files
41 | def rewrite_titles(f):
42 | code = False
43 | for i, line in enumerate(f):
44 | if line.startswith('```'):
45 | code = not code
46 | elif not code and line.startswith('#'):
47 | if line.startswith(title_prefix):
48 | line = line[document_depth:]
49 | else:
50 | print('Warning with title {!r} in file {}:{}'.format(line, f.name, i))
51 | yield line
52 |
53 | # Write a file in the archive
54 | def write_file(archive, filename):
55 | with open(filename, 'r') as f:
56 | title = next(f).translate(trans).strip()
57 | content = ''.join(rewrite_titles(f))
58 | archive.writestr(filename, content)
59 | return title
60 |
61 | # Recursively construct a document
62 | def make_document(archive, obj, name=None):
63 | if isinstance(obj, str):
64 | extract = {'object': 'extract', 'text': obj}
65 | extract['slug'], _ = os.path.splitext(name)
66 | extract['title'] = write_file(archive, obj)
67 | return extract
68 | container = {'object': 'container'}
69 | if name:
70 | container['slug'] = name
71 | keys = list(obj.keys())
72 | if keys[0].startswith('0-'):
73 | container['introduction'] = obj.pop(keys[0])
74 | container['title'] = write_file(archive, container['introduction'])
75 | if keys[-1].startswith('x-'):
76 | container['conclusion'] = obj.pop(keys[-1])
77 | write_file(archive, container['conclusion'])
78 | if obj:
79 | container['children'] = [make_document(archive, child, name) for name, child in obj.items()]
80 | return container
81 |
82 | with ZipFile(archive_name, 'w') as archive:
83 | document = make_document(archive, container['src'])
84 | document.update(manifest)
85 | archive.writestr('manifest.json', json.dumps(document, indent=4))
86 |
--------------------------------------------------------------------------------
/gen_titles.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import re
4 | import os
5 |
6 | files = []
7 |
8 | for directory, _, filenames in os.walk('src'):
9 | for filename in filenames:
10 | files.append(os.path.join(directory, filename))
11 |
12 | def sort_key(path):
13 | return tuple(int(re.sub(r'[^0-9]', '', x) or 0) for x in path.split('/'))
14 |
15 | files.sort(key=sort_key)
16 |
17 |
18 | print('% Notions de Python avancées')
19 |
20 | for filename in files:
21 | with open(filename, 'r') as f:
22 | code = False
23 | for line in f:
24 | if re.match(r'```', line):
25 | code = not code
26 | continue
27 | if code:
28 | continue
29 |
30 | m = re.match(r'(#+) (.+)$', line)
31 | if m:
32 | tabs = len(m.group(1)) - 1
33 | if not tabs:
34 | print()
35 | print('{}- {}'.format(' ' * tabs, m.group(2)))
36 |
--------------------------------------------------------------------------------
/logo_cours.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/entwanne/cours_python_avance/6f1771773032b696c9ed99e7508cb08a273c9c47/logo_cours.png
--------------------------------------------------------------------------------
/src/0-presentation.md:
--------------------------------------------------------------------------------
1 | ## Présentation
2 |
3 | Python est simple.
4 |
5 | C'est probablement ce que l'on vous a dit de nombreuses fois, et ce que vous avez constaté en apprenant et pratiquant ce langage.
6 | Mais derrière cette simplicité apparente existent un certain nombre de concepts plus complexes qui forment la puissance de ce langage.
7 |
8 | En Python, on s'intéresse plus au comportement des objets qu'à leur nature.
9 | Ainsi, l'interface des objets (c'est-à-dire l'ensemble de leurs attributs et méthodes) est quelque chose de très important,
10 | c'est entre autres ce qui les définit.
11 |
12 | En effet, une grande partie des outils du langage sont génériques — tels les appels de fonctions ou les boucles `for` — c'est-à-dire qu'ils peuvent s'appliquer à des types différents.
13 | Python demande simplement à ces types de respecter une interface en implémentant un certain nombre de méthodes spéciales.
14 | Ces interfaces et méthodes seront décrites dans le cours.
15 |
16 | Le pré-requis pour suivre ce tutoriel est de connaître Python, même à un niveau intermédiaire.
17 | Il est simplement nécessaire de savoir manipuler les structures du langage (conditions, boucles, fonctions),
18 | les types de base (nombres, chaînes de caractères, listes, dictionnaires),
19 | et d'avoir des notions de [programmation objet en Python](https://zestedesavoir.com/tutoriels/1253/la-programmation-orientee-objet-en-python/).
20 | Connaître le mécanisme des exceptions est un plus.
21 |
22 | Ce cours se divise en chapitres consacrés chacun à une spécificité du langage.
23 | Ces dernières ne devraient plus avoir de secret pour vous une fois la lecture terminée.
24 |
25 | Je tiens aussi à préciser que ce cours s'adresse principalement aux utilisateurs de Python 3,
26 | et n'est pas valable pour les versions de Python inférieures.
27 |
--------------------------------------------------------------------------------
/src/1-starters/0-startings.md:
--------------------------------------------------------------------------------
1 | # Mise en bouche
2 |
3 | ##### À table !
4 |
5 | Quoi de mieux pour commencer qu'une petite mise en bouche ?
6 |
7 | Nous allons nous intéresser dans cette partie aux plus simples interfaces du langage.
8 | Celles qui nous permettent de confectionner des objets tels que les listes ou les dictionnaires.
9 |
--------------------------------------------------------------------------------
/src/1-starters/1-containers/0-containers.md:
--------------------------------------------------------------------------------
1 | ## Conteneurs
2 |
3 | En Python, on appelle conteneur (*container*) un objet ayant vocation à en contenir d'autres, comme les chaînes de caractères, les listes, les ensembles ou les dictionnaires.
4 |
5 | Il existe plusieurs catégories de conteneurs, notamment celle des *subscriptables*. Ce nom barbare regroupe tous les objets sur lesquels l'opérateur `[]` peut être utilisé.
6 | L'ensemble des types cités dans le premier paragraphe sont *subscriptables*, à l'exception de l'ensemble (*set*), qui n'implémente pas l'opération `[]`.
7 |
8 | Les *subscriptables* se divisent en deux nouvelles catégories non exclusives : les *indexables* et les *sliceables*. Les premiers sont ceux pouvant être indexés avec des nombres entiers, les seconds pouvant l'être avec des `slice` (voir plus loin).
9 |
10 | On parle plus généralement de séquence quand un conteneur est *indexable* et *sliceable*.
11 |
12 | Une autre catégorie importante de conteneurs est formée par les *mappings* : il s'agit des objets qui associent des valeurs à des clefs, comme le font les dictionnaires.
13 |
14 | Une séquence et un *mapping* se caractérisent aussi par le fait qu'ils possèdent une taille, comme nous le verrons par la suite dans ce chapitre.
15 |
--------------------------------------------------------------------------------
/src/1-starters/1-containers/1-contains.md:
--------------------------------------------------------------------------------
1 | ### Les conteneurs, c'est `in`
2 |
3 | Comme indiqué, les conteneurs sont donc des objets qui contiennent d'autres objets.
4 | Ils se caractérisent par l'opérateur `in` : `(0, 1, 2, 3)` étant un conteneur, il est possible de tester s'il contient telle ou telle valeur à l'aide de cet opérateur.
5 |
6 | ```python
7 | >>> 3 in (0, 1, 2, 3)
8 | True
9 | >>> 4 in (0, 1, 2, 3)
10 | False
11 | ```
12 |
13 | Comment cela fonctionne ? C'est très simple. Comme pour de nombreux comportements, Python se base sur des méthodes spéciales des objets.
14 | Vous en connaissez déjà probablement, ce sont les méthodes dont les noms débutent et s'achèvent par `__`.
15 |
16 | Ici, l'opérateur `in` fait simplement appel à la méthode `__contains__` de l'objet, qui prend en paramètre l'opérande gauche, et retourne un booléen.
17 |
18 | ```python
19 | >>> 'o' in 'toto'
20 | True
21 | >>> 'toto'.__contains__('o')
22 | True
23 | ```
24 |
25 | Il nous suffit ainsi d'implémenter cette méthode pour faire de notre objet un conteneur.
26 |
27 | ```python
28 | >>> class MyContainer:
29 | ... def __contains__(self, value):
30 | ... return value is not None # contient tout sauf None
31 | ...
32 | >>> 'salut' in MyContainer()
33 | True
34 | >>> 1.5 in MyContainer()
35 | True
36 | >>> None in MyContainer()
37 | False
38 | ```
39 |
--------------------------------------------------------------------------------
/src/1-starters/1-containers/2-len.md:
--------------------------------------------------------------------------------
1 | ### C'est pas la taille qui compte
2 |
3 | Un autre point commun partagé par de nombreux conteneurs est qu'ils possèdent une taille.
4 | C'est-à-dire qu'ils contiennent un nombre fini et connu d'éléments, et peuvent être passés en paramètre à la fonction `len` par exemple.
5 |
6 | ```python
7 | >>> len([1, 2, 3])
8 | 3
9 | >>> len(MyContainer())
10 | Traceback (most recent call last):
11 | File "", line 1, in
12 | TypeError: object of type 'MyContainer' has no len()
13 | ```
14 |
15 | Comme pour l'opérateur `in`, la fonction `len` fait appel à une méthode spéciale de l'objet, `__len__`, qui ne prend ici aucun paramètre et doit retourner un nombre entier positif.
16 |
17 | ```python
18 | >>> len('toto')
19 | 4
20 | >>> 'toto'.__len__()
21 | 4
22 | ```
23 |
24 | Nous pouvons donc aisément donner une taille à nos objets :
25 |
26 | ```python
27 | >>> class MySizeable:
28 | ... def __len__(self):
29 | ... return 18
30 | ...
31 | >>> len(MySizeable())
32 | 18
33 | ```
34 |
35 | Je vous invite à faire des essais en retournant d'autres valeurs (nombres négatifs, flottants, chaînes de caractères) pour observer le comportement.
36 |
--------------------------------------------------------------------------------
/src/1-starters/1-containers/3-indexables.md:
--------------------------------------------------------------------------------
1 | ### Objets *indexables*
2 |
3 | Nous voilà bien avancés : nous savons mesurer la taille d'un objet, mais pas voir les éléments qu'il contient.
4 |
5 | L'accès aux éléments se fait via l'opérateur `[]`. De même que la modification et la suppression, quand celles-ci sont possibles (c'est-à-dire que l'objet est mutable).
6 |
7 | ```python
8 | >>> numbers = [4, 7, 6]
9 | >>> numbers[2]
10 | 6
11 | >>> numbers[1] = 5
12 | >>> numbers
13 | [4, 5, 6]
14 | >>> del numbers[0]
15 | >>> numbers
16 | [5, 6]
17 | ```
18 |
19 | Le comportement interne est ici régi par 3 méthodes : `__getitem__`, `__setitem__`, et `__delitem__`.
20 |
21 | ```python
22 | >>> numbers = [4, 7, 6]
23 | >>> numbers.__getitem__(2)
24 | 6
25 | >>> numbers.__setitem__(1, 5)
26 | >>> numbers
27 | [4, 5, 6]
28 | >>> numbers.__delitem__(0)
29 | >>> numbers
30 | [5, 6]
31 | ```
32 |
33 | Comme précédemment, nous pouvons donc implémenter ces méthodes dans un nouveau type. Nous allons ici nous contenter de faire un proxy autour d'une liste existante.
34 |
35 | Un proxy[^proxy] est un objet prévu pour se substituer à un autre, il doit donc répondre aux mêmes méthodes, de façon transparente.
36 |
37 | [^proxy]:
38 |
39 | ```python
40 | class MyList:
41 | def __init__(self, value=()): # Émulation du constructeur de list
42 | self.internal = list(value)
43 |
44 | def __len__(self): # Sera utile pour les tests
45 | return len(self.internal)
46 |
47 | def __getitem__(self, key):
48 | return self.internal[key] # Équivalent à return self.internal.__getitem__(key)
49 |
50 | def __setitem__(self, key, value):
51 | self.internal[key] = value
52 |
53 | def __delitem__(self, key):
54 | del self.internal[key]
55 | ```
56 |
57 | Nous pouvons tester notre objet, celui-ci a bien le comportement voulu :
58 |
59 | ```python
60 | >>> numbers = MyList('123456')
61 | >>> len(numbers)
62 | 6
63 | >>> numbers[1]
64 | '2'
65 | >>> numbers[1] = '0'
66 | >>> numbers[1]
67 | '0'
68 | >>> del numbers[1]
69 | >>> len(numbers)
70 | 5
71 | >>> numbers[1]
72 | '3'
73 | ```
74 |
--------------------------------------------------------------------------------
/src/1-starters/1-containers/4-slices.md:
--------------------------------------------------------------------------------
1 | ### Les *slices*
2 |
3 | Les « slices » se traduisent en français par « tranches ». Cela signifie que l'on va prendre notre objet et le découper en morceaux.
4 | Par exemple, récupérer la première moitié d'une liste, ou cette même liste en ne conservant qu'un élément sur deux.
5 |
6 | Les *slices* sont une syntaxe particulière pour l'indexation, à l'aide du caractère `:` lors des appels à `[]`.
7 |
8 | ```python
9 | >>> letters = ('a', 'b', 'c', 'd', 'e', 'f')
10 | >>> letters[0:4]
11 | ('a', 'b', 'c', 'd')
12 | >>> letters[1:-2]
13 | ('b', 'c', 'd')
14 | >>> letters[::2]
15 | ('a', 'c', 'e')
16 | ```
17 |
18 | Je pense que vous êtes déjà familier avec cette syntaxe. Le *slice* peut prendre jusqu'à 3 nombres :
19 |
20 | - Le premier est l'indice de départ (0 si omis) ;
21 | - Le second est l'indice de fin (fin de la liste si omis), l'élément correspondant à cet indice étant exclu ;
22 | - Le dernier est le pas, le nombre d'éléments passés à chaque itération (1 par défaut) ;
23 |
24 | Notons que les *slices* peuvent aussi servir pour la modification et la suppression :
25 |
26 | ```python
27 | >>> letters = ['a', 'b', 'c', 'd', 'e', 'f']
28 | >>> letters[::2] = 'x', 'y', 'z'
29 | >>> letters
30 | ['x', 'b', 'y', 'd', 'z', 'f']
31 | >>> del letters[0:3]
32 | >>> letters
33 | ['d', 'z', 'f']
34 | ```
35 |
36 | Une bonne chose pour nous est que, même avec les *slices*, ce sont les 3 mêmes méthodes `__getitem__`, `__setitem__` et `__delitem__` qui sont appelées lors des accès.
37 | Cela signifie que la classe `MyList` que nous venons d'implémenter est déjà compatible avec les *slices*.
38 |
39 | En fait, c'est simplement que le paramètre `key` passé ne représente pas un nombre, mais est un objet de type `slice` :
40 |
41 | ```python
42 | >>> s = slice(1, -1)
43 | >>> 'abcdef'[s]
44 | 'bcde'
45 | >>> 'abcdef'[slice(None, None, 2)]
46 | 'ace'
47 | ```
48 |
49 | Comme vous le voyez, le slice se construit toujours de la même manière, avec 3 nombres pouvant être omis, ou précisés à `None` pour prendre leur valeur par défaut.
50 |
51 | L'objet ainsi construit contient 3 attributs : `start`, `stop`, et `step`.
52 |
53 | ```python
54 | >>> s = slice(1, 2, 3)
55 | >>> s.start
56 | 1
57 | >>> s.stop
58 | 2
59 | >>> s.step
60 | 3
61 | ```
62 |
63 | Je vous conseille ce tutoriel de [**pascal.ortiz**](https://zestedesavoir.com/membres/voir/pascal.ortiz/) pour en savoir plus sur les slices :
64 |
--------------------------------------------------------------------------------
/src/1-starters/1-containers/x-conclusion.md:
--------------------------------------------------------------------------------
1 | ### Liens utiles
2 |
3 | Nous sommes maintenant à la fin du premier chapitre. En guise de conclusion, je vais vous fournir une liste de sources tirées de la documentation officielle permettant d'aller plus loin sur ces sujets.
4 |
5 | * La définition du terme séquence :
6 | * Celle du *mapping* :
7 | * Types de séquences :
8 | * Types standards :
9 | * Émuler les conteneurs :
10 | * Module `collections` :
11 |
--------------------------------------------------------------------------------
/src/1-starters/2-iterables/0-iterables.md:
--------------------------------------------------------------------------------
1 | ## Itérables
2 |
3 | Un itérable est un objet dont on peut parcourir les valeurs, à l'aide d'un `for` par exemple.
4 | La liste que nous venons d'implémenter est un exemple d'itérable.
5 |
6 | Les types `str`, `tuple`, `list`, `dict` et `set` sont d'autres itérables bien connus.
7 | Un grand nombre d'outils Python que nous verrons par la suite travaillent avec des itérables, il est donc intéressant d'en tirer profit.
8 |
9 | L'objectif de ce chapitre va être de comprendre ce qu'est un itérable, et comment en implémenter un.
10 |
--------------------------------------------------------------------------------
/src/1-starters/2-iterables/1-for.md:
--------------------------------------------------------------------------------
1 | ### `for` `for` lointain
2 |
3 | Les itérables et le mot-clef `for` sont intimement liés. C'est à partir de ce dernier que nous itérons sur les objets.
4 |
5 | Mais comment cela fonctionne en interne ? Je vous propose de regarder ça pas à pas, en nous aidant d'un objet de type `list`.
6 |
7 | ```python
8 | >>> numbers = [1, 2, 3, 4, 5]
9 | ```
10 |
11 | La première opération réalisée par le `for` est d'appeler la fonction `iter` avec notre objet.
12 | `iter` retourne un itérateur. L'itérateur est l'objet qui va se déplacer le long de l'itérable.
13 |
14 | ```python
15 | >>> iter(numbers)
16 |
17 | ```
18 |
19 | Puis, pas à pas, le `for` appelle `next` en lui précisant l'itérateur.
20 | `next` fait avancer l'itérateur et retourne la nouvelle valeur découverte à chaque pas.
21 |
22 | ```python
23 | >>> iterator = iter(numbers)
24 | >>> next(iterator)
25 | 1
26 | >>> next(iterator)
27 | 2
28 | >>> next(iterator)
29 | 3
30 | >>> next(iterator)
31 | 4
32 | >>> next(iterator)
33 | 5
34 | >>> next(iterator)
35 | Traceback (most recent call last):
36 | File "", line 1, in
37 | StopIteration
38 | ```
39 |
40 | Qu'est-ce que ce `StopIteration` ? Il s'agit d'une exception, levée par l'itérateur quand il arrive à sa fin, qui signifie que nous en sommes arrivés au bout, et donc que la boucle doit cesser. `for` attrape cette exception pour nous, ce qui explique que nous ne la voyons pas survenir dans une boucle habituelle.
41 |
42 | Ainsi, le code suivant :
43 |
44 | ```python
45 | for number in numbers:
46 | print(number)
47 | ```
48 |
49 | Peut se remplacer par celui-ci :
50 |
51 | ```python
52 | iterator = iter(numbers)
53 | while True:
54 | try:
55 | number = next(iterator)
56 | except StopIteration:
57 | break
58 | print(number)
59 | ```
60 |
61 | En interne, `iter` fait habituellement appel à la méthode `__iter__` de l'itérable, et `next` à la méthode `__next__` de l'itérateur.
62 | Ces deux méthodes ne prennent aucun paramètre. Ainsi :
63 |
64 | - Un itérable est un objet possédant une méthode `__iter__`[^approximation_iter] retournant un itérateur ;
65 | - Un itérateur est un objet possédant une méthode `__next__` retournant la valeur suivante à chaque appel, et levant une exception de type `StopIteration` en fin de course.
66 |
67 | La [documentation Python](https://docs.python.org/3/glossary.html#term-iterator) indique aussi qu'un itérateur doit avoir une méthode `__iter__` où il se retourne lui-même, les itérateurs étant ainsi des itérables à part entièe.
68 |
69 | [^approximation_iter]: À une approximation près, comme détaillé dans « Le cas des indexables ».
70 |
71 |
72 | #### Le cas des indexables
73 |
74 | En début du chapitre, j'ai indiqué que notre liste `Deque` était aussi un itérable. Pourtant, nous ne lui avons pas implémenté de méthode `__iter__` permettant de la parcourir.
75 |
76 | Il s'agit en fait d'une particularité des indexables, et de la fonction `iter` qui est capable de créer un itérateur à partir de ces derniers.
77 | Cet itérateur se contentera d'appeler `__getitem__` sur notre objet avec des indices successifs, partant de 0 et continuant jusqu'à ce que la méthode lève une `IndexError`.
78 |
79 | Dans notre cas, ça nous évite donc d'implémenter nous-même `__iter__`, mais ça complexifie aussi les traitements.
80 | Souvenez-vous de notre méthode `__getitem__` : elle parcourt la liste jusqu'à l'élément voulu.
81 |
82 | Ainsi, pour accéder au premier maillon, on parcourt un élément, on en parcourt deux pour accéder au second, etc.
83 | Donc pour itérer sur une liste de 5 éléments, on va devoir parcourir `1 + 2 + 3 + 4 + 5` soit 15 maillons, là où 5 seraient suffisants.
84 | C'est pourquoi nous reviendrons sur `Deque` en fin de chapitre pour lui intégrer sa propre méthode `__iter__`.
85 |
--------------------------------------------------------------------------------
/src/1-starters/2-iterables/2-iterables.md:
--------------------------------------------------------------------------------
1 | ### Utilisation des iterables
2 |
3 | #### Python et les itérables
4 |
5 | Ce concept d'itérateurs est utilisé par Python dans une grande partie des ses [builtins](https://docs.python.org/3/library/functions.html). Plutôt que de vous forcer à utiliser une liste, Python vous permet de fournir un objet itérable, pour `sum`, `max` ou `map` par exemple.
6 |
7 | Je vous propose de tester cela avec un itérable basique, qui nous permettra de réaliser un `range` simplifié.
8 |
9 | ```python
10 | class MyRange:
11 | def __init__(self, size):
12 | self.size = size
13 |
14 | def __iter__(self):
15 | return MyRangeIterator(self)
16 |
17 | class MyRangeIterator:
18 | def __init__(self, my_range):
19 | self.current = 0
20 | self.max = my_range.size
21 |
22 | def __iter__(self):
23 | return self
24 |
25 | def __next__(self):
26 | if self.current >= self.max:
27 | raise StopIteration
28 | ret = self.current
29 | self.current += 1
30 | return ret
31 | ```
32 |
33 | Maintenant, testons notre objet, en essayant d'itérer dessus à l'aide d'un `for`.
34 |
35 | ```python
36 | >>> MyRange(5)
37 | <__main__.MyRange object at 0x7fcf3b0e8f28>
38 | >>> for i in MyRange(5):
39 | ... print(i)
40 | ...
41 | 0
42 | 1
43 | 2
44 | 3
45 | 4
46 | ```
47 |
48 | Voilà pour l'itération, mais testons ensuite quelques autres *builtins* dont je parlais plus haut.
49 |
50 | ```python
51 | >>> sum(MyRange(5)) # sum réalise la somme de tous les éléments, soit 0 + 1 + 2 + 3 + 4
52 | 10
53 | >>> max(MyRange(5)) # max retourne la plus grande valeur
54 | 4
55 | >>> map(str, MyRange(5)) # Ici, map retournera chaque valeur convertie en str
56 |