formations/python-avancé/python-avancé.md
2023-05-27 09:28:28 +02:00

1428 lines
24 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Python avancé
### Présenté par
<!-- .slide: data-background="static/background.jpg" -->
Julien Palard <julien@palard.fr>
https://mdk.fr
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 des itérables
- Itérable,
- itérateur.
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__`.
Notes:
Exercice, implémenter un `range()`, mais sans `stop` ni `step`.
Petite parenthèse : `range`, c'est une classe ou une fonction ?
## 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.
- `raise StopIteration` : C'est terminé.
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).
## Duck typing
```python
>>> class Counter:
... def __getitem__(self, i):
... return i
...
>>> i = iter(Counter())
>>> i
<iterator object at ...>
>>> 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.
## Ne pas confondre
Itérateur :
```python
class CounterIterator:
def __init__(self):
self.i = -1
def __iter__(self):
return self
def __next__(self):
self.i += 1
return self.i
```
## Ne pas confondre
```pycon
>>> c = CounterIterator()
>>> for i in c:
... print(i)
... if i >= 2: break
...
0
1
2
>>> for i in c:
... print(i)
... if i >= 2: break
...
3
```
## Ne pas confondre
Et itérable :
```python
class CounterIterable:
def __iter__(self):
return CounterIterator()
```
## Ne pas confondre
```python
>>> c = CounterIterable()
>>> for i in c:
... print(i)
... if i >= 2: break
...
0
1
2
>>> for i in c:
... print(i)
... if i >= 2: break
...
0
1
2
```
## Peut-on faire plus simple ?
```python
class GenCounter:
def __iter__(self):
i = 0
while True:
yield i
i += 1
```
## 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.
## 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`, ...
# Les objets
## Rappels
- Keep it simple.
- Flat is better than nested.
## `classmethod`, `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 »
```python
def __get__(self, instance, owner=None): ...
def __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:
Vous pouvez aussi utiliser un décorateur pour personaliser une classe.
# Langage
## 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 "<stdin>", line 1, in <module>
__main__.DBError: No such entry
```
Notes:
library/exceptions.html → hierarchy
## 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
```python
def __enter__(self): ...
def __exit__(self, exc_type, exc_value, tb): ...
```
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`
## 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()
<coroutine object tum at 0x7fa294538468>
```
## Coroutines
```
>>> async def tum():
... print("tum")
...
>>> a_coroutine_object = tum()
>>> a_coroutine_object.send(None)
tum
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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
```text
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
```
```pycon
>>> print(coro_manager(four()))
4
```
## Gérer ses coroutines
```python
class Awaitable:
def __await__(self):
yield
async def wont_terminate_here():
await Awaitable()
print("Terminated")
return 42
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
```text
async def tum():
while True:
await Awaitable()
print("Tum")
async def pak():
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(<module>)
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(<module>)
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(<genexpr>)
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