diff --git a/python-avancé/1-for.md b/python-avancé/1-for.md deleted file mode 100644 index 9ae2dec..0000000 --- a/python-avancé/1-for.md +++ /dev/null @@ -1,444 +0,0 @@ -# Python - -Notes: - -En initiation, on utilise (le for par exemple), -en avancé on crée (des itérables par exemple). - -- Exemples concrets et définitions abstraites - - Pas de `class Foo`, le cerveau ne peut s'accrocher à rien. - - Le bonheur est dans le chemin et dans la finalité -- Contenu différenciant - - Pas de `class Foo`, tout le monde le fait déjà. - - Détaillez toutes les étapes, même les plus petites. - - Soyez drôle ! Donnez envie ! - -J'ai 5 jours, donc ~200 slides. - - -## « Tout est objet » - -Comme en Java, #oupas - -Notes: - -- Sortir un interpréteur. -- Leur faire essayer de deviner ce qui pourrait ne pas être une classe. -- Démo avec: - - un nombre entier, #obvious, c'est géré par Python - - ouvrir une parenthèse si nécessaire, avec 6 ** 6 ** 6 - - un float, en les faisant hésiter vu qu'ils sont « gérés par le CPU » - - une fonction - - une classe (et une instance) - - range ! - - module !! - -OK mais pas `for`, `def`, ... ce sont des mots clefs. - - -## Donc, tout a des attributs… - -Notes: - -Exercice : avec des `set`, et `dir()`, trouver la liste des attributs -communs à une fonction, disons `max` et à un int, disons `42`, -combien y'en-a-il ? Moi 23. Combien `object` en a-il ? - - -## Même un int ? - -```python ->>> (42).__bool__() is bool(42) -True -``` - -Ou `help(42 .to_bytes)`. - -Notes: - -Ouvrir une parenthèse sur la notion de vérité, ce qui est : - -- Vide -- Égal à zéro -- None ou False - -c'est faux, le reste, c'est vrai. - - -## Les noms - -Notes: - -Faire le schéma à deux colonnes: noms → mémoire. -https://dreampuf.github.io/GraphvizOnline/#%23%20import%20math%0A%0A%23%20def%20print_tau()%3A%0A%23%20%20%20...%0A%0A%0Adigraph%20G%20%7B%0A%0A%20%20subgraph%20cluster_0%20%7B%0A%20%20%20%20%20label%20%3D%20%22Noms%22%3B%0A%20%20%20%20%20math%3B%0A%20%20%20%20%20print_tau%3B%0A%20%20%7D%0A%0A%20%20subgraph%20cluster_1%20%7B%0A%20%20%20%20%20label%20%3D%20%22Objets%22%3B%0A%20%20%20%20%20%22%3Cmodule%20math%3E%22%0A%20%20%20%20%20%22%3Cfunction%20print_tau%3E%22%0A%20%20%7D%0A%20%20%0A%20%20math%20-%3E%20%22%3Cmodule%20math%3E%22%0A%20%20print_tau%20-%3E%20%22%3Cfunction%20print_tau%3E%22%0A%7D - -En Python avancé bien insister sur le fait qu'un objet en mémoire à -une adresse. - -Insister sur le fait qu'un paramètre de fonction n'est qu'un nom. On a -donc pas de « passage par valeur » chez nous. - -Bien préciser qu'on ne peut pas « délier » un nom pour le faire -pointer sur rien (en ce cas on le fait pointer sur `None`). - - -# J'ai 5mn pour vous parler de `for` - -Notes: - -Déjà, c'est pas un objet. - -Jusqu'où peut-on creuser ? - - -## `for` itère sur des itérables - -- Itérable : Objet dont on peut obtenir les éléments un à un. -- Itérateur : Représentation d'un flux d'éléments. -- Séquence : Un itérable dont les éléments sont accessible par indice et dont on connaît la taille. -- Collection : Itérable dont on connaît la longueur. - -Notes: - -On peut très bien imaginer un itérateur capable d'itérer un itérable, -mais aussi une séquence, une collections, ... - - -## Le protocole « séquence » - -Implémente `__getitem__` et `__len__`. - -(voir meme `__reversed__`, `__iter__` et `__contains__`). - -Notes: - -Exercice, implémenter un `range()`, mais sans `stop` ni `step`. - -Petite parenthèse : `range`, c'est une classe ou une fonction ? - - -## Le protocole « séquence » - -`__getitem__` suffit pour être itérable. - -Notes: - -C'est l'application du duck-typing : Si ça a tout ce dont `for` à -besoin, alors ça fonctionne. `for` n'a pas besoin de connaître la -taille, donc ça fonctionne. - - -## Le protocole d'itération - -Notes: - -Itérable : Objet capable de renvoyer ses éléments un à un. -Itérateur : Objet chargé de s'occuper de l'itération d'un itérable : -se souvenir où on en est. - - -## Le protocole d'itération - -- `iter()` : Crée un itérateur à partir d'un itérable. -- `next()` : Demande l'élément suivant à un itérateur. - -Notes: - -Première démo REPL sur une liste « on reste utilisateurs de Python ». - - -## Le protocole d'itération - -`__iter__` et `__next__` - -Notes: - -Démo REPL sur une liste « on perçoit comment on va pouvoir -l'implémenter ». - -La différence ? Petite parenthèse : `iter()` peut utiliser soit le -protocole séquence soit le protocole d'itération, et fait quelques -vérifications (que l'itérateur renvoyé soit bien un itérateur). - - -## Petite parenthèse - -```python ->>> class Counter: -... def __getitem__(self, i): -... return i -... ->>> i = iter(Counter()) ->>> i - ->>> next(i) -0 ->>> next(i) -1 ->>> next(i) -2 -``` - -Notes: - -Via le protocole séquence, `__len__` n'est pas utilisé donc ça se -passe bien. - - -## Petite parenthèse - -```python ->>> class B: ... -... ->>> iter(B()) -Traceback (most recent call last): - File "", line 1, in -TypeError: 'B' object is not iterable -``` - -Notes: - -`iter()` donne une belle exception. - - -## Petite parenthèse - -```python ->>> class C: -... def __iter__(self): return None -... ->>> iter(C()) -Traceback (most recent call last): - File "", line 1, in -TypeError: iter() returned non-iterator of type 'NoneType' -``` - -Notes: - -`iter()` valide que `__iter__` renvoie bien un itérateur. - - -## Digression - -`iter` a aussi une version qui prend deux paramètres. - -```python -from functools import partial - -with open('mydata.db', 'rb') as f: - for block in iter(partial(f.read, 64), b''): - process_block(block) -``` - - -## Retour sur `__iter__` - -```python - def __iter__(self): - return self -``` - -Notes: - -Mauvaise idée ! - -Réimplémentez la classe `Counter()` comme ça. - - -## Solution - -```python -class Counter: - def __init__(self): self.i = -1 - def __iter__(self): return self - def __next__(self): - self.i += 1 - return self.i -``` - -## Le problème - -```python ->>> c = Counter() ->>> for i, j in zip(c, c): -... print(i, j) -... if i > 5: break -... -0 1 -2 3 -4 5 -6 7 -``` - - -## On recommence - -Notes: - -Cette fois avec un itérateur dédié. - - -## Solution - -```python -class BetterCounter: - def __iter__(self): - return CounterIterator() -``` - - -## Solution - -```python -class CounterIterator: - def __init__(self): - self.i = -1 - - def __next__(self): - self.i += 1 - return self.i -``` - - -## Solution - -```python ->>> c = BetterCounter() ->>> for i, j in zip(c, c): -... if i > 5: break -... print(i, j) -0 0 -1 1 -2 2 -3 3 -4 4 -5 5 -``` - -Notes: - -C'est toujours faux ! Un itérateur doit AUSSI implémenter `__iter__`, -donc qui `return self`, ça permet d'utiliser aussi les itérateurs avec -for. - - -## Peut-on faire plus simple ? - -Notes: - -Oui ! Avec un générateur ! C'est le sucre syntaxique pour créer ses -itérables. - -Attention, une fonction générateur renvoie un itérateur, (qu'on -appelle un générateur), pas un itérable ! Et là on est bien contents -qu'un itérateur ai un `__iter__` qui se renvoie lui même, pour pouvoir -l'utiliser dans un for ! - - -## Mais alors - -Si une fonction générateur renvoie un itérateur, et que `__iter__` -doit renvoyer un itérateur, on peut implémenter `__iter__` avec yield ? - -Notes: - -Oui. - - -## Exemple - -```python -class GenCounter: - def __iter__(self): - i = 0 - while True: - yield i - i += 1 -``` - - -## Pendant qu'on parle de `yield` - -Connaissez-vous `yield from` ? - - -## Pendant qu'on parle de `for` - -Connaissez-vous le `else` du `for` ? - -Notes: - -Il ne s'exécute que si le `for` sort sans `break`. - - -## `else` - -```python ->>> n = 13 ->>> for i in range(2, n - 1): -... if n % i == 0: -... print(f"{n} is not prime") -... break -... else: -... print(f"{n} is prime") -13 is prime -``` - -Notes: - -Typiquement utile lors des recherches, la sémantique : - - Trouvé, plus besoin de chercher, break. - - else: pas trouvé. - -Fonctionne aussi sur le while. - -Ah j'ai utilisé une f-string. - - -## Literal String Interpolation - -```python ->>> f"{42:08b}" -'00101010' -``` - -Notes: - -Attention aux ':' et '!' dans l'expression, bien que ce soit accepté -si c'est entre guillemet, crochets, parenthèses, ... sinon toute -expression Python est autorisée (comme avec .format, mais avec .format -c'est plus évident). - - -## Literal String Interpolation - -```python ->>> f"{(lambda x: x.upper())('hello'):^11}" -' HELLO ' -``` - -Notes: - -Attention à rester lisible, mais ici le `:` de la lambda est entre -parenthèses, donc c'est bon. - -En parlant de parenthèse, fermons une parenthèse. - - -## On parlais d'itérables - -Si on parlais d'unpacking ? - -Notes: - -Pour se remémorer ces choses, cherchez les PEPs, typiquement la 448, la 3132, ... - -- Parler de `deep unpacking`. -- Parler de `head, *rest`, ... - - -## Ça peut rappeler `*args` et `**kwargs` - -Notes: - -Démo si nécessaire. diff --git a/python-avancé/2-object.md b/python-avancé/2-object.md deleted file mode 100644 index c3b4ef7..0000000 --- a/python-avancé/2-object.md +++ /dev/null @@ -1,82 +0,0 @@ -# Les objets - -## Rappels - -- Keep it simple. -- Flat is better than nested. - - -## `classmethod` vs `staticmethod` - -## La MRO - -Notes: - -Simple démo REPL : `bool.__mro__`. - -## `super()` ! - -Notes: - -Et la coopération, démo avec deux classes : `TCPConnection` qui prend -`host, port, timeout`, et `HTTPConnection` qui prend url, method, ...` - -Démo aussi : passer un argument de trop et voir que object() se plains. - -Antisèche : https://wyz.fr/3Z8 - - -## Le protocole « descripteur » - -- `object.__get__(self, instance, owner=None)` -- `object.__set__(self, instance, value)` - -Notes: - -Et `__delete__` et `__set_name__`. - -- instance... c'est l'instance. -- owner, c'est le type, il est toujours connu donc "devrait" toujours être donné -- Si instance n'est pas donnée, c'est qu'on accède à l'attribut sur le type. - -Exercice : https://www.hackinscience.org/exercises/temperature-class - - -## Métaclasses - -Puisqu'une classe est un objet, une métaclasse c'est le type du type. - -Notes: - -En initiation on dit "ça ne vous servira pas". En avancé on dit -`__init_subclass__` couvrira tous vos besoins. - - -## Métaclasse - -- `__new__` et `__init__` d'une classe servent à personaliser l'instance. -- `__new__` et `__init__` d'une metaclasse servent à personalier une classe. - -Notes: - -```python -class M(type): - def __new__(cls, *args, **kwargs): - print(f"meta.__new__(*{args}, **{kwargs})") - return super().__new__(cls, *args, **kwargs) - - def __init__(self, *args, **kwargs): - print(f"meta.__init__(*{args}, **{kwargs})") - super().__init__(*args, **kwargs) - -class MyCls(metaclass=M): - def __new__(cls, *args, **kwargs): - print(f"cls.__new__(*{args}, **{kwargs})") - return super().__new__(cls, *args, **kwargs) - - def __init__(self, *args, **kwargs): - print(f"cls.__init__(*{args}, **{kwargs})") - super().__init__(*args, **kwargs) -``` - -Vous pouvez aussi utiliser un décorateur pour personaliser une classe. diff --git a/python-avancé/3-language.md b/python-avancé/3-language.md deleted file mode 100644 index 273ae02..0000000 --- a/python-avancé/3-language.md +++ /dev/null @@ -1,307 +0,0 @@ -# Langage - -## `id` et `is` - -Notes: - -- `is` : pour les singletons `None`, `True`, `False`. -- `id` : identifiant unique, l'adresse mémoire en CPython. -- `is` : proche de `id(left) == id(right)` mais attention au GC. - - -## Parenthèse sur les singletons - -Notes: - -Un module est un singleton. - - -## String interning - -```python -a = "Bonjour !" -b = "Bonjour !" -a is b -``` - -? - -Notes: - -- C'est dépendant de l'implémentation, ça change d'une version à l'autre de Python. -- Les chaînes ne contenant que des [a-zA-Z0-9_] sont internées. - - -## IEEE 754 - -```python -f"http://{.1 + .2}.com" -``` - -Notes: - -Notez ! Et au besoin utilisez le module Decimal. - - -## Définir vos propres exceptions - -Il suffit d'hériter d'`Exception`, rien de plus. - -``` ->>> class DBError(Exception): pass -... ->>> raise DBError("No such entry") -Traceback (most recent call last): - File "", line 1, in -__main__.DBError: No such entry -``` - -Notes: - -library/exceptions.html → hierarchy - - -## try, finally, else, except - -Dans quel ordre ? - -Notes: Oui, il y a un else ici aussi. - - -## try, except, else, finally - -## Les gestionnaires de contexte - -```python -with open("/etc/hosts") as f: - f.read() -``` - -Notes: - -En initiation on apprend a les utiliser. -En avancé on apprend à en faire. - - -## Les gestionnaires de contexte - -- ``__enter__`` -- ``__exit__`` - - -Notes: - -Expliquer le protocole. - - -## Les gestionnaires de contexte - -```python -class transaction: - def __init__(self, db): - self.db = db - def __enter__(self): - self.db.begin() - def __exit__(self, type, value, tb): - if type is None: - self.db.commit() - else: - self.db.rollback() -``` - -Notes: - -C'est un exemple de gestionnaire de contexte de transaction de base de donnée. - -Astuce, `__enter__` peut renvoyer un tuple, qu'on peut décomposer à -droite du as, typiquement `ifile`, `ofile`. - - -## Les décorateurs - -`@` - -Notes: - -En initiation on apprend a les utiliser. -En avancé on apprend à en faire. - - -Just for doctest: -```python -def clock(f=None, *args, **kwargs): - return lambda *args: None -``` - -## Les décorateurs - -```python -@clock -def fib(n): - ... -``` - -équivaut à - -```python -fib = clock(fib) -``` - -Notes: - -Bien insister sur le fait que `@` est bien séparé de son -`dotted_name`, pas n'importe quelle expression. sur le fait qu'on -peut les empiler (clarifier l'ordre). - - -## Les décorateurs - -```python -@clock(deadline=10) -def fib(n): - ... -``` - -équivaut à - -```python -fib = clock(deadline=10)(fib) -``` - -Notes: - -Rappeler que `()` n'est qu'un opérateur. - - -## Les décorateurs - -Faire ses propres décorateurs. - -Notes: - -Leur faire implémenter un décorateur @clock. -```python -def clock(func): - def clocked(*args): - t0 = time.perf_counter() - result = func(*args) - elapsed = time.perf_counter() -t0 - name = func.__name__ - arg_str = ', '.join(repr(arg) for arg in args) - print(f"[{elapsed:.08f}s] {name}({arg_str}) -> {result!r}") - return result - return clocked -``` - - -## Les décorateurs - -Faire ses décorateurs paramétrés. - -Notes: - -Leur faire implémenter @memoize qui prend en paramètre une limite. - -En profiter pour parler de `global`, `nonlocal`, et des closures. - - -## Les décorateurs - -Les utiliser pour leurs effets de bord. - -Notes: - -`@route("/")` par exemple. - - -## Les décorateurs - -- `@staticmethod` -- `@classmethod` -- `@property` - - -## contextlib - -- `with suppress:` -- `@contextmanager` - - -## contextlib - -Un décorateur peut-il être aussi un gestionnaire de contexte ? - -Est-ce utile ? Pertinent ? - -Notes: - -Oui, par exemple Django `@atomic` et with `atomic:`, `contextlib.ContextDecorator`. - -Parler des gestionnaires de contextes réutilisables, puis réentrants. - - -## The Walrus Operator - -`:=` - -Notes: - -Démo REPL avec re.match, rappeler que les parenthèses sont souvent -obligatoires. - - -## Les listes en compréhension - -```python -l = [] -for i in range(5): - if i % 2 == 0: - for j in range(5): - if j % 2 == 0: - for k in range(5): - if k % 2 == 0: - if i + j + k == 4: - l.append((i,j,k)) -``` - -## Les listes en compréhension - -```python ->>> [(i, j, k) -... for i in range(5) -... if i % 2 == 0 -... for j in range(5) -... if j % 2 == 0 -... for k in range(5) -... if k % 2 == 0 -... if i + j + k == 4] -[(0, 0, 4), (0, 2, 2), (0, 4, 0), (2, 0, 2), (2, 2, 0), (4, 0, 0)] -``` - -Notes: - -Juste pour doctest: - -```python -factors = lambda i: [i] -``` - -## Les listes en compréhension - -```python -{x: factors(x) - for x in range(1000) - if len(factors(x)) == 3} -``` - -Notes: si factors est lent (spoiler: il l'est), c'est du gâchis, -utiliser un walrus ! - - -## Les listes en compréhension - -```python -{x: prime_factors - for x in range(1000) - if len(prime_factors := factors(x)) == 3} -``` diff --git a/python-avancé/4-encodage.md b/python-avancé/4-encodage.md deleted file mode 100644 index 4290b1b..0000000 --- a/python-avancé/4-encodage.md +++ /dev/null @@ -1,57 +0,0 @@ -# L'encodage - -## Les octets d'abord - -```python ->>> bytes([0x01, 0x02]) == b"\x01\x02" -True -``` - -Notes: - -Notez qu'en hexadecimal, deux symboles permet de représenter -exactement 8 bits, donc exactement un octet. - - -## ASCII - -Notes: - -1960, 7 bits ("a word", qu'on a traduit "un octet"), [0; 127] - -Seul la moitié des octets sont donc de l'ASCII valide. - -Exercice: Utiliser `range()` et `bytes([i])` pour afficher la table ascii. - - -## Latin-1 - -Notes: - -1985, 8 bits, [0; 255] - -Couvre environ 32 langues. - -Quasi complet pour le francais, il manque juste le Œ, le œ (le -francais qui s'en est occupé n'était pas linguiste.) - - -Exercice: Utiliser `range()` et `bytes([i])` pour afficher la table latin-1. - - -## Unicode - -Notes: - -~1990, d'abord sur 16 bits, aujourd'hui c'est juste une base de donnée. - -Couvre environ 150 langues (environ toutes). - -Calque latin1 de 0 à 255, même C0 (controles bien définis) et C1 (controles -ignorés, de 0x80 à 0x9F). - - -## encoder, décoder - -- `str.encode` → `bytes` -- `bytes.decode` → `str` diff --git a/python-avancé/5-packaging.md b/python-avancé/5-packaging.md deleted file mode 100644 index 4a8ac21..0000000 --- a/python-avancé/5-packaging.md +++ /dev/null @@ -1,98 +0,0 @@ -# Le packaging - -## Petite parenthèse - -La différence entre un paquet et un module ? - -Notes: - -Pour Python il n'y en a pas, tout est module, pour nous, un paquet est -un dossier. Aborder rapidement les paquets-espace-de-noms. - - -## Digression - -`__main__` et `__main__.py`. - - -## venv - -Notes: - -Et ses alternatives : virtualenv / conda. - - -## pip - -Notes: - -Jamais `sudo`, toujours dans un `venv`. - - -## pyproject.toml - -- https://setuptools.readthedocs.io/ -- https://github.com/JulienPalard/oeis - - -## pip install -e . - -## Packager - -```bash -pip install build -python -m build -``` - -### Publier - -```bash -pip install twine -twine upload dist/* -``` - -## Bonnes habitudes - -> There are 2 hard problems in computer science: cache invalidation, -> naming things, and off-by-1 errors. - - -## Bonnes habitudes - -Pas plus de 7. - - -## Garder son API évolutive - -Utilisez correctement `/` et `*` dans les prototypes de fonction. - -Notes: - -help(sum) - - -## Les « linters » - -Il existe plusieurs outils pour « relire » votre code : -- flake8, -- pylint, -- mypy, -- black, -- bandit, -- isort, -- ruff, -- tox. - -Notes: Leur faire implémenter un `is_prime(x)` pour jouer avec. - - -## pdb - -``` -breakpoint() -``` - - -## PYTHONDEVMODE=y - -Et `./configure --with-pydebug`. diff --git a/python-avancé/6-coro.md b/python-avancé/6-coro.md deleted file mode 100644 index 8f1e338..0000000 --- a/python-avancé/6-coro.md +++ /dev/null @@ -1,230 +0,0 @@ -# async / await - -Une coroutine est une fonction dont l'exécution peut être suspendue. - - -## Callback Hell - -``` -function pong_handler(client) -{ - client.on('data', function (data) - { - client.on('data_written', function () - { - client.close() - }); - client.write(data) - client.flush() - }); -} -``` - - -## Avec des coroutines - -```python -async def pong_handler(): - client.write(await client.read()) - await client.flush() - client.close() -``` - -## Les coroutines - - - generator-based coroutines - - native coroutines - - -## Generator-based coroutines - -```pytho -import types - - -@types.coroutine -def get_then_print(url): - ... -``` - - -## Native coroutines - -```python -async def get_then_print(url): - ... -``` - - -## Coroutines - -Une `coroutine`, renvoie un objet `coroutine` : - -``` ->>> async def tum(): -... print("tum") -... ->>> tum() - -``` - - -## Coroutines - -``` ->>> async def tum(): -... print("tum") -... ->>> a_coroutine_object = tum() ->>> a_coroutine_object.send(None) -tum -Traceback (most recent call last): - File "", line 1, in - StopIteration -``` - -Notes: - -qu'on peut manipuler. - -As you can see, calling `tum()` did not execute the `print("tum")`, -but calling `.send(None)` did (see PEP 342). - -L'appel de .send est fait par la main loop (asyncio.run). - - -## Récupérer un résultat - -Le résultat d'une coroutine est stocké dans l'exception `StopIteration`. - -Notes: - -Dans l'attribut `value`. - - -## await - - -``` -async def two(): - return 2 - -async def four(): - return await two() + await two() - -coro = four() -coro.send(None) -``` - -Notes: - -Ça donne `StopIteration: 4`, de manière complètement synchrone. - - -## Suspendre une coroutine. - -Ce n'est pas possible dans une coroutine. - -Notes: - -Bon, à part `await asyncio.sleep(0)`, ou toute attente vers un -awaitable qui se suspend sans rien faire. - - -## Future-like object - -Un `future-like object` est un object implémentant `__await__`, qui a -le droit de `yield`. L'expression du yield traversera toute la stack -d'`await` jusqu'au `send(None)`. - - -## Awaitables - -Les [awaitables](https://www.python.org/dev/peps/pep-0492/#await-expression) -sont des objets pouvant être « attendus » via un `await`. - -Notes: - -Typiquement `coroutine` ou un objet implémentant `__await__`. - - -## Gérer ses coroutines - -```python -async def two(): - return 2 - -async def four(): - return await two() + await two() - -def coro_manager(coro): - try: - coro.send(None) - except StopIteration as stop: - return stop.value - -print(coro_manager(four())) -``` - - -## Gérer ses coroutines - - -```python -class Awaitable: - def __await__(self): - yield - -async def wont_terminate_here(): - await Awaitable() - print("Terminated") - return 42 - -print(coro_manager(wont_terminate_here())) -``` - -## Gérer ses coroutines - - -```python -def frenetic_coro_manager(coro): - try: - while True: - coro.send(None) - except StopIteration as stop: - return stop.value -``` - - -## Gérer ses coroutines - -```python -import random - - -def frenetic_coros_manager(*coros): - coros = list(coros) - while coros: - coro = random.choice(coros) - try: - coro.send(None) - except StopIteration as stop: - coros.remove(coro) -``` - - -## Gérer ses coroutines - -```python -async def tum(): - for _ in range(10): # ou : while True: - await Awaitable() - print("Tum") - -async def pak(): - for _ in range(10): # ou : while True: - await Awaitable() - print("Pak") - -frenetic_coros_manager(tum(), pak()) -``` diff --git a/python-avancé/7-perf.md b/python-avancé/7-perf.md deleted file mode 100644 index 3ffe45d..0000000 --- a/python-avancé/7-perf.md +++ /dev/null @@ -1,411 +0,0 @@ -# Performance - -## Le code - -```python -def main(): - already_checked = [] - while True: - c = "".join(choice(ascii_letters) for _ in range(10)) - if c in already_checked: continue - already_checked.append(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - print("Searching") -``` - - -## Premiers tests - -```bash -$ time python perf.py AFPy 00 -Searching -[...] -Searching -Found: sha512(5NX3dB0BrO + AFPy) = 00… - -real 0m0.048s -user 0m0.040s -sys 0m0.008s -``` - -## Premiers tests - -```bash -$ time python perf.py AFPy 000 -Searching -[...] -Searching -Found: sha512(UYb0z6nac1 + AFPy) = 000… - -real 0m2.797s -user 0m2.773s -sys 0m0.024s -``` - -## Premiers tests - -```bash -$ time python perf.py AFPy 0000 -Searching -[...] -Searching -Found: sha512(dX0oAzvOmm + AFPy) = 0000… - -real 0m16.381s -user 0m16.375s -sys 0m0.004s -``` - -C'est long mais ça passe … - - -## Premiers tests - -```bash -$ time python perf.py AFPy 00000 -Searching -[...] -Searching -Searching -Searching -Searching -``` - -Bon, on a un sushi. - -## cProfile - -```bash -$ python -m cProfile -o prof perf.py AFPy 0000 -``` - -## pstats - -```bash -$ python -m pstats prof -Welcome to the profile statistics browser. -prof% sort cumulative -prof% stats 10 -``` - -## pstats - -```txt -ncalls cumtime percall filename:lineno(function) - 12/1 17.007 17.007 {built-in method builtins.exec} - 1 17.007 17.007 /tmp/perf.py:1() - 1 16.996 16.996 /tmp/perf.py:20(main) - 36429 0.869 0.000 {method 'join' of 'str' objects} -``` - -## snakeviz - -```bash -$ pip install snakeviz -Collecting snakeviz - Using cached snakeviz-2.1.0-py2.py3-none-any.whl (282 kB) -Collecting tornado>=2.0 - Using cached tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl (427 kB) -Installing collected packages: tornado, snakeviz -Successfully installed snakeviz-2.1.0 tornado-6.1 -``` - - -## snakeviz - -```bash -$ snakeviz prof -``` - -## snakeviz -![](static/snakeviz-v1.png) - - -## vprof - -``` -$ pip install vprof -Collecting vprof - Using cached vprof-0.38-py3-none-any.whl (319 kB) -Collecting psutil>=3 - Using cached psutil-5.7.3-cp39-cp39d-linux_x86_64.whl -Installing collected packages: psutil, vprof -Successfully installed psutil-5.7.3 vprof-0.38 -``` - -## vprof - -``` -$ vprof -c h "perf.py AFPy 0000" -``` - -## vprof - -![](static/vprof.png) - - -## Le code, v1 - -```python [2,5,6] -def main(): - already_checked = [] - while True: - c = "".join(choice(ascii_letters) for _ in range(10)) - if c in already_checked: continue - already_checked.append(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - print("Searching") -``` - -## Le code, v2 - -```python [2,5,6] -def main(): - already_checked = set() - while True: - c = "".join(choice(ascii_letters) for _ in range(10)) - if c in already_checked: continue - already_checked.add(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - print("Searching") -``` - -## Les perfs - -```bash -$ hyperfine 'python perf.py AFPy 00000' -``` -- v1 : ∞ -- v2 (`set`) : 23 s ± 23 s - -::: notes - -Il existe aussi pyperf: https://github.com/psf/pyperf - - -## cProfile + pstats - -```bash -$ python -m cProfile -o prof perf.py AFPy 0000 -$ python -m pstats prof -``` - -## cProfile + pstats -``` - ncalls cumtime percall filename:lineno(function) - 12/1 1.156 1.156 {built-in method builtins.exec} - 1 1.156 1.156 perf.py:1() - 1 1.143 1.143 perf.py:35(main) - 34215 0.771 0.000 {method 'join' of 'str' objects} - 371647 0.681 0.000 perf.py:39() - 337860 0.526 0.000 /python3.9/random.py(choice) - 337860 0.283 0.000 /python3.9/random.py(randbelow) - 33786 0.134 0.000 built-in method print - 372745 0.037 0.000 method 'getrandbits' of Random' - 33786 0.037 0.000 method 'hexdigest' of hashlib -``` - -## snakeviz - -```bash -$ snakeviz prof -``` - -## snakeviz - -![](static/snakeviz-v2.png) - - -## Le code, v2 - -```python [4] -def main(): - already_checked = set() - while True: - c = "".join(choice(ascii_letters) for _ in range(10)) - if c in already_checked: continue - already_checked.add(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - print("Searching") -``` - - -## Le code, v3 - -```python [4] -def main(): - already_checked = set() - while True: - c = "".join(choices(ascii_letters, k=10)) - if c in already_checked: continue - already_checked.add(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - print("Searching") -``` - -## Les perfs - -```bash -$ hyperfine 'python perf.py AFPy 00000' -``` -- v1 : ∞ -- v2 (`set`) : 23 s ± 23 s -- v3 (`choices`): 8.591 s ± 6.525 s - - -## snakeviz - -![](static/snakeviz-v3.png) - - -## Le code, v4 - -```python [3] -def main(): - already_checked = set() - for c in product(ascii_letters, repeat=10): - c = "".join(c) - if c in already_checked: continue - already_checked.add(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - print("Searching") -``` - -## Les perfs - -```bash -$ hyperfine 'python perf.py AFPy 00000' -``` -- v1 : ∞ -- v2 (`set`) : 23 s ± 23 s -- v3 (`choices`): 8.591 s ± 6.525 s -- v4 (`deterministic`) : 3.900 s ± 0.121 s - - -## snakeviz - -![](static/snakeviz-v4.png) - - -## Le code, v5 - -```python [12] -def main(): - already_checked = set() - for c in product(ascii_letters, repeat=10): - c = "".join(c) - if c in already_checked: continue - already_checked.add(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) - # print("Searching") -``` - -## Les perfs - -```bash -$ hyperfine 'python perf.py AFPy 00000' -``` -- v1 : ∞ -- v2 (`set`) : 23 s ± 23 s -- v3 (`choices`): 8.591 s ± 6.525 s -- v4 (`deterministic`) : 3.900 s ± 0.121 s -- v5 (`print`) : 3.120 s ± 0.062 s - - -## Snakeviz - -![](static/snakeviz-v5.png) - -Il reste du `hexdigest`, du `encode`, et du `join`. - -## vprof - -![](static/vprof2.png) - -Ligne 26 et 28 !? - -## Le code, v6 - -```python -def main(): - for c in product(ascii_letters, repeat=10): - c = "".join(c) - digest = sha512( - (c + args.string).encode("UTF-8")).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({c} + {args.string}) = {digest}") - sys.exit(0) -``` - - -## Snakeviz - -![](static/snakeviz-v6.png) - -Il reste du `hexdigest`, du `encode`, et du `join`. - - -## Le code, v7 - -```python -def main(): - string = args.string.encode("UTF-8") - pool = ascii_letters.encode("UTF-8") - for c in product(pool, repeat=10): - digest = sha512(bytes(c) + string).hexdigest() - if digest.startswith(args.sha_prefix): - print(f"sha512({bytes(c)} + {args.string}) = " - f"{digest}") - sys.exit(0) -``` - - -## Les perfs - -```bash -$ hyperfine 'python perf.py AFPy 00000' -``` -- v1 : ∞ -- v2 (`set`) : 23 s ± 23 s -- v3 (`choices`): 8.591 s ± 6.525 s -- v4 (`deterministic`) : 3.900 s ± 0.121 s -- v5 (`print`) : 3.120 s ± 0.062 s -- v6 (`dead code`): 2.844 s ± 0.059 s -- v7 (`bytes`) : 1.837 s ± 0.067 s - - -## Encore plus d'expériences - -- pypy: 3.8s -- python: 1.8s -- cython (hashlib) 1.3s -- cython (crypto) 0.8s -- c: 0.3s diff --git a/python-avancé/python-avancé.md b/python-avancé/python-avancé.md new file mode 100644 index 0000000..49de929 --- /dev/null +++ b/python-avancé/python-avancé.md @@ -0,0 +1,1641 @@ +# Python + +Notes: + +En initiation, on utilise (le for par exemple), +en avancé on crée (des itérables par exemple). + +- Exemples concrets et définitions abstraites + - Pas de `class Foo`, le cerveau ne peut s'accrocher à rien. + - Le bonheur est dans le chemin et dans la finalité +- Contenu différenciant + - Pas de `class Foo`, tout le monde le fait déjà. + - Détaillez toutes les étapes, même les plus petites. + - Soyez drôle ! Donnez envie ! + +J'ai 5 jours, donc ~200 slides. + + +## « Tout est objet » + +Comme en Java, #oupas + +Notes: + +- Sortir un interpréteur. +- Leur faire essayer de deviner ce qui pourrait ne pas être une classe. +- Démo avec: + - un nombre entier, #obvious, c'est géré par Python + - ouvrir une parenthèse si nécessaire, avec 6 ** 6 ** 6 + - un float, en les faisant hésiter vu qu'ils sont « gérés par le CPU » + - une fonction + - une classe (et une instance) + - range ! + - module !! + +OK mais pas `for`, `def`, ... ce sont des mots clefs. + + +## Donc, tout a des attributs… + +Notes: + +Exercice : avec des `set`, et `dir()`, trouver la liste des attributs +communs à une fonction, disons `max` et à un int, disons `42`, +combien y'en-a-il ? Moi 23. Combien `object` en a-il ? + + +## Même un int ? + +```python +>>> (42).__bool__() is bool(42) +True +``` + +Ou `help(42 .to_bytes)`. + +Notes: + +Ouvrir une parenthèse sur la notion de vérité, ce qui est : + +- Vide +- Égal à zéro +- None ou False + +c'est faux, le reste, c'est vrai. + + +## Les noms + +Notes: + +Faire le schéma à deux colonnes: noms → mémoire. +https://dreampuf.github.io/GraphvizOnline/#%23%20import%20math%0A%0A%23%20def%20print_tau()%3A%0A%23%20%20%20...%0A%0A%0Adigraph%20G%20%7B%0A%0A%20%20subgraph%20cluster_0%20%7B%0A%20%20%20%20%20label%20%3D%20%22Noms%22%3B%0A%20%20%20%20%20math%3B%0A%20%20%20%20%20print_tau%3B%0A%20%20%7D%0A%0A%20%20subgraph%20cluster_1%20%7B%0A%20%20%20%20%20label%20%3D%20%22Objets%22%3B%0A%20%20%20%20%20%22%3Cmodule%20math%3E%22%0A%20%20%20%20%20%22%3Cfunction%20print_tau%3E%22%0A%20%20%7D%0A%20%20%0A%20%20math%20-%3E%20%22%3Cmodule%20math%3E%22%0A%20%20print_tau%20-%3E%20%22%3Cfunction%20print_tau%3E%22%0A%7D + +En Python avancé bien insister sur le fait qu'un objet en mémoire à +une adresse. + +Insister sur le fait qu'un paramètre de fonction n'est qu'un nom. On a +donc pas de « passage par valeur » chez nous. + +Bien préciser qu'on ne peut pas « délier » un nom pour le faire +pointer sur rien (en ce cas on le fait pointer sur `None`). + + +# J'ai 5mn pour vous parler de `for` + +Notes: + +Déjà, c'est pas un objet. + +Jusqu'où peut-on creuser ? + + +## `for` itère sur des itérables + +- Itérable : Objet dont on peut obtenir les éléments un à un. +- Itérateur : Représentation d'un flux d'éléments. +- Séquence : Un itérable dont les éléments sont accessible par indice et dont on connaît la taille. +- Collection : Itérable dont on connaît la longueur. + +Notes: + +On peut très bien imaginer un itérateur capable d'itérer un itérable, +mais aussi une séquence, une collections, ... + + +## Le protocole « séquence » + +Implémente `__getitem__` et `__len__`. + +(voir meme `__reversed__`, `__iter__` et `__contains__`). + +Notes: + +Exercice, implémenter un `range()`, mais sans `stop` ni `step`. + +Petite parenthèse : `range`, c'est une classe ou une fonction ? + + +## Le protocole « séquence » + +`__getitem__` suffit pour être itérable. + +Notes: + +C'est l'application du duck-typing : Si ça a tout ce dont `for` à +besoin, alors ça fonctionne. `for` n'a pas besoin de connaître la +taille, donc ça fonctionne. + + +## Le protocole d'itération + +Notes: + +Itérable : Objet capable de renvoyer ses éléments un à un. +Itérateur : Objet chargé de s'occuper de l'itération d'un itérable : +se souvenir où on en est. + + +## Le protocole d'itération + +- `iter()` : Crée un itérateur à partir d'un itérable. +- `next()` : Demande l'élément suivant à un itérateur. + +Notes: + +Première démo REPL sur une liste « on reste utilisateurs de Python ». + + +## Le protocole d'itération + +`__iter__` et `__next__` + +Notes: + +Démo REPL sur une liste « on perçoit comment on va pouvoir +l'implémenter ». + +La différence ? Petite parenthèse : `iter()` peut utiliser soit le +protocole séquence soit le protocole d'itération, et fait quelques +vérifications (que l'itérateur renvoyé soit bien un itérateur). + + +## Petite parenthèse + +```python +>>> class Counter: +... def __getitem__(self, i): +... return i +... +>>> i = iter(Counter()) +>>> i + +>>> next(i) +0 +>>> next(i) +1 +>>> next(i) +2 +``` + +Notes: + +Via le protocole séquence, `__len__` n'est pas utilisé donc ça se +passe bien. + + +## Petite parenthèse + +```python +>>> class B: ... +... +>>> iter(B()) +Traceback (most recent call last): + File "", line 1, in +TypeError: 'B' object is not iterable +``` + +Notes: + +`iter()` donne une belle exception. + + +## Petite parenthèse + +```python +>>> class C: +... def __iter__(self): return None +... +>>> iter(C()) +Traceback (most recent call last): + File "", line 1, in +TypeError: iter() returned non-iterator of type 'NoneType' +``` + +Notes: + +`iter()` valide que `__iter__` renvoie bien un itérateur. + + +## Digression + +`iter` a aussi une version qui prend deux paramètres. + +```python +from functools import partial + +with open('mydata.db', 'rb') as f: + for block in iter(partial(f.read, 64), b''): + process_block(block) +``` + + +## Retour sur `__iter__` + +```python + def __iter__(self): + return self +``` + +Notes: + +Mauvaise idée ! + +Réimplémentez la classe `Counter()` comme ça. + + +## Solution + +```python +class Counter: + def __init__(self): self.i = -1 + def __iter__(self): return self + def __next__(self): + self.i += 1 + return self.i +``` + +## Le problème + +```python +>>> c = Counter() +>>> for i, j in zip(c, c): +... print(i, j) +... if i > 5: break +... +0 1 +2 3 +4 5 +6 7 +``` + + +## On recommence + +Notes: + +Cette fois avec un itérateur dédié. + + +## Solution + +```python +class BetterCounter: + def __iter__(self): + return CounterIterator() +``` + + +## Solution + +```python +class CounterIterator: + def __init__(self): + self.i = -1 + + def __next__(self): + self.i += 1 + return self.i +``` + + +## Solution + +```python +>>> c = BetterCounter() +>>> for i, j in zip(c, c): +... if i > 5: break +... print(i, j) +0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +``` + +Notes: + +C'est toujours faux ! Un itérateur doit AUSSI implémenter `__iter__`, +donc qui `return self`, ça permet d'utiliser aussi les itérateurs avec +for. + + +## Peut-on faire plus simple ? + +Notes: + +Oui ! Avec un générateur ! C'est le sucre syntaxique pour créer ses +itérables. + +Attention, une fonction générateur renvoie un itérateur, (qu'on +appelle un générateur), pas un itérable ! Et là on est bien contents +qu'un itérateur ai un `__iter__` qui se renvoie lui même, pour pouvoir +l'utiliser dans un for ! + + +## Mais alors + +Si une fonction générateur renvoie un itérateur, et que `__iter__` +doit renvoyer un itérateur, on peut implémenter `__iter__` avec yield ? + +Notes: + +Oui. + + +## Exemple + +```python +class GenCounter: + def __iter__(self): + i = 0 + while True: + yield i + i += 1 +``` + + +## Pendant qu'on parle de `yield` + +Connaissez-vous `yield from` ? + + +## Pendant qu'on parle de `for` + +Connaissez-vous le `else` du `for` ? + +Notes: + +Il ne s'exécute que si le `for` sort sans `break`. + + +## `else` + +```python +>>> n = 13 +>>> for i in range(2, n - 1): +... if n % i == 0: +... print(f"{n} is not prime") +... break +... else: +... print(f"{n} is prime") +13 is prime +``` + +Notes: + +Typiquement utile lors des recherches, la sémantique : + - Trouvé, plus besoin de chercher, break. + - else: pas trouvé. + +Fonctionne aussi sur le while. + +Ah j'ai utilisé une f-string. + + +## Literal String Interpolation + +```python +>>> f"{42:08b}" +'00101010' +``` + +Notes: + +Attention aux ':' et '!' dans l'expression, bien que ce soit accepté +si c'est entre guillemet, crochets, parenthèses, ... sinon toute +expression Python est autorisée (comme avec .format, mais avec .format +c'est plus évident). + + +## Literal String Interpolation + +```python +>>> f"{(lambda x: x.upper())('hello'):^11}" +' HELLO ' +``` + +Notes: + +Attention à rester lisible, mais ici le `:` de la lambda est entre +parenthèses, donc c'est bon. + +En parlant de parenthèse, fermons une parenthèse. + + +## On parlais d'itérables + +Si on parlais d'unpacking ? + +Notes: + +Pour se remémorer ces choses, cherchez les PEPs, typiquement la 448, la 3132, ... + +- Parler de `deep unpacking`. +- Parler de `head, *rest`, ... + + +## Ça peut rappeler `*args` et `**kwargs` + +Notes: + +Démo si nécessaire. + + +# Les objets + +## Rappels + +- Keep it simple. +- Flat is better than nested. + + +## `classmethod` vs `staticmethod` + +## La MRO + +Notes: + +Simple démo REPL : `bool.__mro__`. + +## `super()` ! + +Notes: + +Et la coopération, démo avec deux classes : `TCPConnection` qui prend +`host, port, timeout`, et `HTTPConnection` qui prend url, method, ...` + +Démo aussi : passer un argument de trop et voir que object() se plains. + +Antisèche : https://wyz.fr/3Z8 + + +## Le protocole « descripteur » + +- `object.__get__(self, instance, owner=None)` +- `object.__set__(self, instance, value)` + +Notes: + +Et `__delete__` et `__set_name__`. + +- instance... c'est l'instance. +- owner, c'est le type, il est toujours connu donc "devrait" toujours être donné +- Si instance n'est pas donnée, c'est qu'on accède à l'attribut sur le type. + +Exercice : https://www.hackinscience.org/exercises/temperature-class + + +## Métaclasses + +Puisqu'une classe est un objet, une métaclasse c'est le type du type. + +Notes: + +En initiation on dit "ça ne vous servira pas". En avancé on dit +`__init_subclass__` couvrira tous vos besoins. + + +## Métaclasse + +- `__new__` et `__init__` d'une classe servent à personaliser l'instance. +- `__new__` et `__init__` d'une metaclasse servent à personalier une classe. + +Notes: + +```python +class M(type): + def __new__(cls, *args, **kwargs): + print(f"meta.__new__(*{args}, **{kwargs})") + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + print(f"meta.__init__(*{args}, **{kwargs})") + super().__init__(*args, **kwargs) + +class MyCls(metaclass=M): + def __new__(cls, *args, **kwargs): + print(f"cls.__new__(*{args}, **{kwargs})") + return super().__new__(cls, *args, **kwargs) + + def __init__(self, *args, **kwargs): + print(f"cls.__init__(*{args}, **{kwargs})") + super().__init__(*args, **kwargs) +``` + +Vous pouvez aussi utiliser un décorateur pour personaliser une classe. + + +# Langage + +## `id` et `is` + +Notes: + +- `is` : pour les singletons `None`, `True`, `False`. +- `id` : identifiant unique, l'adresse mémoire en CPython. +- `is` : proche de `id(left) == id(right)` mais attention au GC. + + +## Parenthèse sur les singletons + +Notes: + +Un module est un singleton. + + +## String interning + +```python +a = "Bonjour !" +b = "Bonjour !" +a is b +``` + +? + +Notes: + +- C'est dépendant de l'implémentation, ça change d'une version à l'autre de Python. +- Les chaînes ne contenant que des [a-zA-Z0-9_] sont internées. + + +## IEEE 754 + +```python +f"http://{.1 + .2}.com" +``` + +Notes: + +Notez ! Et au besoin utilisez le module Decimal. + + +## Définir vos propres exceptions + +Il suffit d'hériter d'`Exception`, rien de plus. + +``` +>>> class DBError(Exception): pass +... +>>> raise DBError("No such entry") +Traceback (most recent call last): + File "", line 1, in +__main__.DBError: No such entry +``` + +Notes: + +library/exceptions.html → hierarchy + + +## try, finally, else, except + +Dans quel ordre ? + +Notes: Oui, il y a un else ici aussi. + + +## try, except, else, finally + +## Les gestionnaires de contexte + +```python +with open("/etc/hosts") as f: + f.read() +``` + +Notes: + +En initiation on apprend a les utiliser. +En avancé on apprend à en faire. + + +## Les gestionnaires de contexte + +- ``__enter__`` +- ``__exit__`` + + +Notes: + +Expliquer le protocole. + + +## Les gestionnaires de contexte + +```python +class transaction: + def __init__(self, db): + self.db = db + def __enter__(self): + self.db.begin() + def __exit__(self, type, value, tb): + if type is None: + self.db.commit() + else: + self.db.rollback() +``` + +Notes: + +C'est un exemple de gestionnaire de contexte de transaction de base de donnée. + +Astuce, `__enter__` peut renvoyer un tuple, qu'on peut décomposer à +droite du as, typiquement `ifile`, `ofile`. + + +## Les décorateurs + +`@` + +Notes: + +En initiation on apprend a les utiliser. +En avancé on apprend à en faire. + + +Just for doctest: +```python +def clock(f=None, *args, **kwargs): + return lambda *args: None +``` + +## Les décorateurs + +```python +@clock +def fib(n): + ... +``` + +équivaut à + +```python +fib = clock(fib) +``` + +Notes: + +Bien insister sur le fait que `@` est bien séparé de son +`dotted_name`, pas n'importe quelle expression. sur le fait qu'on +peut les empiler (clarifier l'ordre). + + +## Les décorateurs + +```python +@clock(deadline=10) +def fib(n): + ... +``` + +équivaut à + +```python +fib = clock(deadline=10)(fib) +``` + +Notes: + +Rappeler que `()` n'est qu'un opérateur. + + +## Les décorateurs + +Faire ses propres décorateurs. + +Notes: + +Leur faire implémenter un décorateur @clock. +```python +def clock(func): + def clocked(*args): + t0 = time.perf_counter() + result = func(*args) + elapsed = time.perf_counter() -t0 + name = func.__name__ + arg_str = ', '.join(repr(arg) for arg in args) + print(f"[{elapsed:.08f}s] {name}({arg_str}) -> {result!r}") + return result + return clocked +``` + + +## Les décorateurs + +Faire ses décorateurs paramétrés. + +Notes: + +Leur faire implémenter @memoize qui prend en paramètre une limite. + +En profiter pour parler de `global`, `nonlocal`, et des closures. + + +## Les décorateurs + +Les utiliser pour leurs effets de bord. + +Notes: + +`@route("/")` par exemple. + + +## Les décorateurs + +- `@staticmethod` +- `@classmethod` +- `@property` + + +## contextlib + +- `with suppress:` +- `@contextmanager` + + +## contextlib + +Un décorateur peut-il être aussi un gestionnaire de contexte ? + +Est-ce utile ? Pertinent ? + +Notes: + +Oui, par exemple Django `@atomic` et with `atomic:`, `contextlib.ContextDecorator`. + +Parler des gestionnaires de contextes réutilisables, puis réentrants. + + +## The Walrus Operator + +`:=` + +Notes: + +Démo REPL avec re.match, rappeler que les parenthèses sont souvent +obligatoires. + + +## Les listes en compréhension + +```python +l = [] +for i in range(5): + if i % 2 == 0: + for j in range(5): + if j % 2 == 0: + for k in range(5): + if k % 2 == 0: + if i + j + k == 4: + l.append((i,j,k)) +``` + +## Les listes en compréhension + +```python +>>> [(i, j, k) +... for i in range(5) +... if i % 2 == 0 +... for j in range(5) +... if j % 2 == 0 +... for k in range(5) +... if k % 2 == 0 +... if i + j + k == 4] +[(0, 0, 4), (0, 2, 2), (0, 4, 0), (2, 0, 2), (2, 2, 0), (4, 0, 0)] +``` + +Notes: + +Juste pour doctest: + +```python +factors = lambda i: [i] +``` + +## Les listes en compréhension + +```python +{x: factors(x) + for x in range(1000) + if len(factors(x)) == 3} +``` + +Notes: si factors est lent (spoiler: il l'est), c'est du gâchis, +utiliser un walrus ! + + +## Les listes en compréhension + +```python +{x: prime_factors + for x in range(1000) + if len(prime_factors := factors(x)) == 3} +``` + + +# L'encodage + +## Les octets d'abord + +```python +>>> bytes([0x01, 0x02]) == b"\x01\x02" +True +``` + +Notes: + +Notez qu'en hexadecimal, deux symboles permet de représenter +exactement 8 bits, donc exactement un octet. + + +## ASCII + +Notes: + +1960, 7 bits ("a word", qu'on a traduit "un octet"), [0; 127] + +Seul la moitié des octets sont donc de l'ASCII valide. + +Exercice: Utiliser `range()` et `bytes([i])` pour afficher la table ascii. + + +## Latin-1 + +Notes: + +1985, 8 bits, [0; 255] + +Couvre environ 32 langues. + +Quasi complet pour le francais, il manque juste le Œ, le œ (le +francais qui s'en est occupé n'était pas linguiste.) + + +Exercice: Utiliser `range()` et `bytes([i])` pour afficher la table latin-1. + + +## Unicode + +Notes: + +~1990, d'abord sur 16 bits, aujourd'hui c'est juste une base de donnée. + +Couvre environ 150 langues (environ toutes). + +Calque latin1 de 0 à 255, même C0 (controles bien définis) et C1 (controles +ignorés, de 0x80 à 0x9F). + + +## encoder, décoder + +- `str.encode` → `bytes` +- `bytes.decode` → `str` + + +# Le packaging + +## Petite parenthèse + +La différence entre un paquet et un module ? + +Notes: + +Pour Python il n'y en a pas, tout est module, pour nous, un paquet est +un dossier. Aborder rapidement les paquets-espace-de-noms. + + +## Digression + +`__main__` et `__main__.py`. + + +## venv + +Notes: + +Et ses alternatives : virtualenv / conda. + + +## pip + +Notes: + +Jamais `sudo`, toujours dans un `venv`. + + +## pyproject.toml + +- https://setuptools.readthedocs.io/ +- https://github.com/JulienPalard/oeis + + +## pip install -e . + +## Packager + +```bash +pip install build +python -m build +``` + +### Publier + +```bash +pip install twine +twine upload dist/* +``` + +## Bonnes habitudes + +> There are 2 hard problems in computer science: cache invalidation, +> naming things, and off-by-1 errors. + + +## Bonnes habitudes + +Pas plus de 7. + + +## Garder son API évolutive + +Utilisez correctement `/` et `*` dans les prototypes de fonction. + +Notes: + +help(sum) + + +## Les « linters » + +Il existe plusieurs outils pour « relire » votre code : +- flake8, +- pylint, +- mypy, +- black, +- bandit, +- isort, +- ruff, +- tox. + +Notes: Leur faire implémenter un `is_prime(x)` pour jouer avec. + + +## pdb + +``` +breakpoint() +``` + + +## PYTHONDEVMODE=y + +Et `./configure --with-pydebug`. + + +# async / await + +Une coroutine est une fonction dont l'exécution peut être suspendue. + + +## Callback Hell + +``` +function pong_handler(client) +{ + client.on('data', function (data) + { + client.on('data_written', function () + { + client.close() + }); + client.write(data) + client.flush() + }); +} +``` + + +## Avec des coroutines + +```python +async def pong_handler(): + client.write(await client.read()) + await client.flush() + client.close() +``` + +## Les coroutines + + - generator-based coroutines + - native coroutines + + +## Generator-based coroutines + +```pytho +import types + + +@types.coroutine +def get_then_print(url): + ... +``` + + +## Native coroutines + +```python +async def get_then_print(url): + ... +``` + + +## Coroutines + +Une `coroutine`, renvoie un objet `coroutine` : + +``` +>>> async def tum(): +... print("tum") +... +>>> tum() + +``` + + +## Coroutines + +``` +>>> async def tum(): +... print("tum") +... +>>> a_coroutine_object = tum() +>>> a_coroutine_object.send(None) +tum +Traceback (most recent call last): + File "", line 1, in + StopIteration +``` + +Notes: + +qu'on peut manipuler. + +As you can see, calling `tum()` did not execute the `print("tum")`, +but calling `.send(None)` did (see PEP 342). + +L'appel de .send est fait par la main loop (asyncio.run). + + +## Récupérer un résultat + +Le résultat d'une coroutine est stocké dans l'exception `StopIteration`. + +Notes: + +Dans l'attribut `value`. + + +## await + + +``` +async def two(): + return 2 + +async def four(): + return await two() + await two() + +coro = four() +coro.send(None) +``` + +Notes: + +Ça donne `StopIteration: 4`, de manière complètement synchrone. + + +## Suspendre une coroutine. + +Ce n'est pas possible dans une coroutine. + +Notes: + +Bon, à part `await asyncio.sleep(0)`, ou toute attente vers un +awaitable qui se suspend sans rien faire. + + +## Future-like object + +Un `future-like object` est un object implémentant `__await__`, qui a +le droit de `yield`. L'expression du yield traversera toute la stack +d'`await` jusqu'au `send(None)`. + + +## Awaitables + +Les [awaitables](https://www.python.org/dev/peps/pep-0492/#await-expression) +sont des objets pouvant être « attendus » via un `await`. + +Notes: + +Typiquement `coroutine` ou un objet implémentant `__await__`. + + +## Gérer ses coroutines + +```python +async def two(): + return 2 + +async def four(): + return await two() + await two() + +def coro_manager(coro): + try: + coro.send(None) + except StopIteration as stop: + return stop.value + +print(coro_manager(four())) +``` + + +## Gérer ses coroutines + + +```python +class Awaitable: + def __await__(self): + yield + +async def wont_terminate_here(): + await Awaitable() + print("Terminated") + return 42 + +print(coro_manager(wont_terminate_here())) +``` + +## Gérer ses coroutines + + +```python +def frenetic_coro_manager(coro): + try: + while True: + coro.send(None) + except StopIteration as stop: + return stop.value +``` + + +## Gérer ses coroutines + +```python +import random + + +def frenetic_coros_manager(*coros): + coros = list(coros) + while coros: + coro = random.choice(coros) + try: + coro.send(None) + except StopIteration as stop: + coros.remove(coro) +``` + + +## Gérer ses coroutines + +```python +async def tum(): + for _ in range(10): # ou : while True: + await Awaitable() + print("Tum") + +async def pak(): + for _ in range(10): # ou : while True: + await Awaitable() + print("Pak") + +frenetic_coros_manager(tum(), pak()) +``` + + +# Performance + +## Le code + +```python +def main(): + already_checked = [] + while True: + c = "".join(choice(ascii_letters) for _ in range(10)) + if c in already_checked: continue + already_checked.append(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + print("Searching") +``` + + +## Premiers tests + +```bash +$ time python perf.py AFPy 00 +Searching +[...] +Searching +Found: sha512(5NX3dB0BrO + AFPy) = 00… + +real 0m0.048s +user 0m0.040s +sys 0m0.008s +``` + +## Premiers tests + +```bash +$ time python perf.py AFPy 000 +Searching +[...] +Searching +Found: sha512(UYb0z6nac1 + AFPy) = 000… + +real 0m2.797s +user 0m2.773s +sys 0m0.024s +``` + +## Premiers tests + +```bash +$ time python perf.py AFPy 0000 +Searching +[...] +Searching +Found: sha512(dX0oAzvOmm + AFPy) = 0000… + +real 0m16.381s +user 0m16.375s +sys 0m0.004s +``` + +C'est long mais ça passe … + + +## Premiers tests + +```bash +$ time python perf.py AFPy 00000 +Searching +[...] +Searching +Searching +Searching +Searching +``` + +Bon, on a un sushi. + +## cProfile + +```bash +$ python -m cProfile -o prof perf.py AFPy 0000 +``` + +## pstats + +```bash +$ python -m pstats prof +Welcome to the profile statistics browser. +prof% sort cumulative +prof% stats 10 +``` + +## pstats + +```txt +ncalls cumtime percall filename:lineno(function) + 12/1 17.007 17.007 {built-in method builtins.exec} + 1 17.007 17.007 /tmp/perf.py:1() + 1 16.996 16.996 /tmp/perf.py:20(main) + 36429 0.869 0.000 {method 'join' of 'str' objects} +``` + +## snakeviz + +```bash +$ pip install snakeviz +Collecting snakeviz + Using cached snakeviz-2.1.0-py2.py3-none-any.whl (282 kB) +Collecting tornado>=2.0 + Using cached tornado-6.1-cp39-cp39-manylinux2010_x86_64.whl (427 kB) +Installing collected packages: tornado, snakeviz +Successfully installed snakeviz-2.1.0 tornado-6.1 +``` + + +## snakeviz + +```bash +$ snakeviz prof +``` + +## snakeviz +![](static/snakeviz-v1.png) + + +## vprof + +``` +$ pip install vprof +Collecting vprof + Using cached vprof-0.38-py3-none-any.whl (319 kB) +Collecting psutil>=3 + Using cached psutil-5.7.3-cp39-cp39d-linux_x86_64.whl +Installing collected packages: psutil, vprof +Successfully installed psutil-5.7.3 vprof-0.38 +``` + +## vprof + +``` +$ vprof -c h "perf.py AFPy 0000" +``` + +## vprof + +![](static/vprof.png) + + +## Le code, v1 + +```python [2,5,6] +def main(): + already_checked = [] + while True: + c = "".join(choice(ascii_letters) for _ in range(10)) + if c in already_checked: continue + already_checked.append(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + print("Searching") +``` + +## Le code, v2 + +```python [2,5,6] +def main(): + already_checked = set() + while True: + c = "".join(choice(ascii_letters) for _ in range(10)) + if c in already_checked: continue + already_checked.add(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + print("Searching") +``` + +## Les perfs + +```bash +$ hyperfine 'python perf.py AFPy 00000' +``` +- v1 : ∞ +- v2 (`set`) : 23 s ± 23 s + +::: notes + +Il existe aussi pyperf: https://github.com/psf/pyperf + + +## cProfile + pstats + +```bash +$ python -m cProfile -o prof perf.py AFPy 0000 +$ python -m pstats prof +``` + +## cProfile + pstats +``` + ncalls cumtime percall filename:lineno(function) + 12/1 1.156 1.156 {built-in method builtins.exec} + 1 1.156 1.156 perf.py:1() + 1 1.143 1.143 perf.py:35(main) + 34215 0.771 0.000 {method 'join' of 'str' objects} + 371647 0.681 0.000 perf.py:39() + 337860 0.526 0.000 /python3.9/random.py(choice) + 337860 0.283 0.000 /python3.9/random.py(randbelow) + 33786 0.134 0.000 built-in method print + 372745 0.037 0.000 method 'getrandbits' of Random' + 33786 0.037 0.000 method 'hexdigest' of hashlib +``` + +## snakeviz + +```bash +$ snakeviz prof +``` + +## snakeviz + +![](static/snakeviz-v2.png) + + +## Le code, v2 + +```python [4] +def main(): + already_checked = set() + while True: + c = "".join(choice(ascii_letters) for _ in range(10)) + if c in already_checked: continue + already_checked.add(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + print("Searching") +``` + + +## Le code, v3 + +```python [4] +def main(): + already_checked = set() + while True: + c = "".join(choices(ascii_letters, k=10)) + if c in already_checked: continue + already_checked.add(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + print("Searching") +``` + +## Les perfs + +```bash +$ hyperfine 'python perf.py AFPy 00000' +``` +- v1 : ∞ +- v2 (`set`) : 23 s ± 23 s +- v3 (`choices`): 8.591 s ± 6.525 s + + +## snakeviz + +![](static/snakeviz-v3.png) + + +## Le code, v4 + +```python [3] +def main(): + already_checked = set() + for c in product(ascii_letters, repeat=10): + c = "".join(c) + if c in already_checked: continue + already_checked.add(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + print("Searching") +``` + +## Les perfs + +```bash +$ hyperfine 'python perf.py AFPy 00000' +``` +- v1 : ∞ +- v2 (`set`) : 23 s ± 23 s +- v3 (`choices`): 8.591 s ± 6.525 s +- v4 (`deterministic`) : 3.900 s ± 0.121 s + + +## snakeviz + +![](static/snakeviz-v4.png) + + +## Le code, v5 + +```python [12] +def main(): + already_checked = set() + for c in product(ascii_letters, repeat=10): + c = "".join(c) + if c in already_checked: continue + already_checked.add(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) + # print("Searching") +``` + +## Les perfs + +```bash +$ hyperfine 'python perf.py AFPy 00000' +``` +- v1 : ∞ +- v2 (`set`) : 23 s ± 23 s +- v3 (`choices`): 8.591 s ± 6.525 s +- v4 (`deterministic`) : 3.900 s ± 0.121 s +- v5 (`print`) : 3.120 s ± 0.062 s + + +## Snakeviz + +![](static/snakeviz-v5.png) + +Il reste du `hexdigest`, du `encode`, et du `join`. + +## vprof + +![](static/vprof2.png) + +Ligne 26 et 28 !? + +## Le code, v6 + +```python +def main(): + for c in product(ascii_letters, repeat=10): + c = "".join(c) + digest = sha512( + (c + args.string).encode("UTF-8")).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({c} + {args.string}) = {digest}") + sys.exit(0) +``` + + +## Snakeviz + +![](static/snakeviz-v6.png) + +Il reste du `hexdigest`, du `encode`, et du `join`. + + +## Le code, v7 + +```python +def main(): + string = args.string.encode("UTF-8") + pool = ascii_letters.encode("UTF-8") + for c in product(pool, repeat=10): + digest = sha512(bytes(c) + string).hexdigest() + if digest.startswith(args.sha_prefix): + print(f"sha512({bytes(c)} + {args.string}) = " + f"{digest}") + sys.exit(0) +``` + + +## Les perfs + +```bash +$ hyperfine 'python perf.py AFPy 00000' +``` +- v1 : ∞ +- v2 (`set`) : 23 s ± 23 s +- v3 (`choices`): 8.591 s ± 6.525 s +- v4 (`deterministic`) : 3.900 s ± 0.121 s +- v5 (`print`) : 3.120 s ± 0.062 s +- v6 (`dead code`): 2.844 s ± 0.059 s +- v7 (`bytes`) : 1.837 s ± 0.067 s + + +## Encore plus d'expériences + +- pypy: 3.8s +- python: 1.8s +- cython (hashlib) 1.3s +- cython (crypto) 0.8s +- c: 0.3s