flotpython / course

Contenu pédagogique du MOOC Python3 sur fun-mooc.fr
Other
130 stars 22 forks source link

asyncio: Change code 3.6 -> 3.10 #62

Open Karduin opened 2 years ago

Karduin commented 2 years ago

Juste pour voir si je suis dans la bonne direction et si je continue comme ça. Est-ce que quelque chose comme ça convient ?

parmentelat commented 2 years ago

bonsoir @Karduin

le fichier que tu as modifié est bien le source de ce qui se trouve ici

image

donc effectivement c'est là qu'on pourrait envisager de corriger le code que je tape dans la vidéo pour montrer celui qu'il aurait fallu taper avec une 3.10

(pour info, ça ne se met pas à jour tout seul depuis git, il faudra un opération manuelle pour ça)


je me souviens assez mal de ce contenu, mais il me semble qu'en effet le principal du code est dans les vidéos, et que les notebooks sont bcp plus minces que dans les autres semaines.

donc oui je pense que c'est la bonne voie pour mettre à jour le contenu de cette semaine-là, qui en a bien besoin par ailleurs :)

merci de ton aide !

Karduin commented 2 years ago

Bonjour Thierry, En ce qui concerne le commit 'Boucle d'événements' j'ai essayé de coller le plus possible à la vidéo parce que je pense que l'objectif c'est d'expliquer le fonctionnement de la boucle. Les tasks ne sont pas encore abordées et du coup pour la version 3.10 c'est du low level api. Je n'ai pas su utiliser loop.stop() parce-que pour que cela fonctionne j'ai ajouter un objet 'future'. Est-ce que c'est bon comme ça, je ne suis pas sur. Heureusement que tu vérifiera tout ça ;-)

parmentelat commented 2 years ago

Bonjour @Karduin

Je te remercie pour cette contribution; je viens de jeter un coup d'oeil et j'ai conclu qu'il allait me falloir de toutes façons passer pas mal de temps sur ce sujet ne serait-ce que pour me remettre tout ça en mémoire et bien tout vérifier avec les nouvelles versions

aussi je te suggère de ne pas passer trop de temps là-dessus; je ne sais pas trop quand j'aurai le temps de faire ce travail, et ne suis même pas sûr de ne pas complètement enlever la semaine 8 à terme, car de toute évidence elle suscite assez peu d'intérêt, et d'autre part en l'état elle est en effet assez décalée par rapport à l'état de l'art - en plus du fait que j'ai en réalité assez peu l'occasion de pratiquer...


un truc qui m'a interpelé toutefois: moi aussi à un moment j'ai été tenté de remplacer un appel à genre run_until_complete() par un appel à asyncio.run(), mais je me suis rendu compte à l'usage que cela ne correspond pas exactement, car si je me souviens bien asyncio.run() va faire le ménage à la fin, et fermer la boucle, alors que ce n'était pas le cas de run_until_complete(); mais il faut se méfier car dans le cadre d'un petit programme on ne se rend pas compte de la différence

bref, cette histoire est bien compliquée, à nouveau je ne voudrais pas que tu perdes trop de temps là-dessus :)

Karduin commented 2 years ago

Bon voila les derniers. ça fonctionne correctement dans un notebook, dans ipython et dans une console. Il faut bien sur dans une console remplacer await par asyncio.run.

Effectivement asyncio.run ferme la boucle mais en créant un objet future la boucle ne se termine pas tant que l'objet n'est pas done. Après est-ce la bonne façon de faire , Je ne suis pas assez qualifié pour en juger ;-) En tous cas ça fonctionne sans erreur dans un notebook. ça ne fonctionne pas sur windows certaines fonctions ne sont pas implémentées. J'ai testé sur linux et notebook du mooc. Je présume que sur mac ça fonctionnera. Du coup j'ai fini, c’était formateur donc ne t’inquiètes pas pour le temps.

d'autre part en l'état elle est en effet assez décalée par rapport à l'état de l'art - en plus du fait que j'ai en réalité assez peu l'occasion de pratiquer..

Oui difficile d'être raccord avec les vidéos. Si je me fis à la doc et aux erreurs pendant mes essais, asyncio.run() utilise run_until_complete(). Toujours d’après la doc c'est maintenant une fonction low level api comme d'autre utilisées dans game. Toujours par rapport à la doc :

Il est préférable d'utiliser les fonctions asyncio de haut niveau, telles qu'asyncio.run(). Le référencement de l'objet boucle ou l'appel de ses méthodes s'adresse principalement aux auteurs de code, de bibliothèques et de frameworks de niveau inférieur, qui ont besoin d'un contrôle plus fin du comportement de la boucle d'événement.

game rentre dans quelle catégorie ?

C'est pour cela que j'ai fait comme ça.

async def mainloop(self):
        loop = asyncio.get_running_loop() #remplace get_event_loop
        fut = loop.create_future() # l'objet future s'il n'est pas done la boucle tourne indéfiniment
        # on met ensemble une clock et un scheduler
        clock = Clock(fut) # je passe l'objet à clock quand fut et done on arrête clock
        scheduler = Scheduler(self.script, fut) #je pass fut au scheduler pour le set à done quand tout est fini
        # et on fait tourner le tout
        asyncio.ensure_future(clock.run())
        asyncio.ensure_future(scheduler.run())
        await fut
parmentelat commented 2 years ago

je viens de passer un peu de temps à revoir un peu tout ça je me demandais si tu avais utilisé la 3.10 pour tes essais ? en effet avec la 3.10 j'ai l'impression qu'il y a encore un autre petit souci, avecget_event_loop() qui ne se comporte pas exactement comme avant apparemment..


ce qui ne me plait pas du tout dans toute cette affaire, c'est qu'il ne semble pas y avoir moyen d'écrire du code qui fonctionne à l'identique dans un notebook et dans un .py 'normal'

je vais tacher de mieux documenter tout ceci avant de décider de la meilleure approche...

Karduin commented 2 years ago

Oui, j'ai travaillé avec la 3.10. Un souci de quel nature ?

ce qui ne me plait pas du tout dans toute cette affaire, c'est qu'il ne semble pas y avoir moyen d'écrire du code qui fonctionne à l'identique dans un notebook et dans un .py 'normal'

Je crois effectivement que l'on a pas le choix. La boucle existant déjà dans ipython et du coup dans les noteboooks.

Cela dit, je n'ai ni ton expérience ni ta maitrise du sujet.

Karduin commented 2 years ago

J'ai pensé à quelque chose concernant un code commun, notebook /terminal.

import asyncio

async def morceaux(message):
    print(message, "début")
    # avec await on rend la main
    await asyncio.sleep(0.5)

    print(message, "milieu")
    await asyncio.sleep(1)

    print(message, "fin")
    return f'{message} par morceaux'

async def main():
    L = await asyncio.gather(
        morceaux("run 1"),
        morceaux("run 2"),
    )
    print(L)

try:
    get_ipython()
    asyncio.create_task(main()) #run from ipython and notebook
except:
    asyncio.run(main()) #run from console

ça fonctionne bien dans dans un terminal et dans un notebook. Dans ipython, ça run correctement, mais il passe aussi par l'except.

Je crois que l'on peux différencier ipython / notebook, en récupérant l'objet avec get_ipython mais je bloque sur l'utilisation.

ip_or_nb = get_ipython()

dans ipython terminal j'obtiens : <IPython.terminal.interactiveshell.TerminalInteractiveShell object at 0x000002468BD6F7C0> dans un notebook sur funmooc : <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f02ad1e1660>

On devrait pouvoir faire quelque chose TerminalInteractiveShell vs ZMQInteractiveShell, mais je vois pas comment.

parmentelat commented 2 years ago

c'est une approche intéressante, mais ça me semble vraiment très compliqué je veux dire, sachant qu'on parle de la toute première vidéo où il y a du code qui tourne, je crains que ça ne déroute/dégoûte les gens qui débarquent

par ailleurs cette semaine ne fait guère recette; les statistiques indiquent que de l'ordre de 1000 personnes seulement ont ouvert les notebooks sur la semaine 8, c'est-à-dire 25x moins que le premier notebook du coup je me demande si je ne vais pas laisser tomber, c'est-à-dire enlever complètement la semaine 8, ou alors laisser le contenu tel quel, et mettre un gros avertissement sur le fait que ce n'est plus à jour...

Karduin commented 2 years ago

Effectivement, ça ne vaut vraisemblablement pas le coup d'investir du temps pour cette section.

parmentelat commented 2 years ago

pour résumer la liste des soucis que je vois à ce stade; je parle uniquement de la toute première vidéo, celle où on fait tourner deux instances de morceaux

interpréteur

essai #1

avec le code initial

loop = asyncio.get_event_loop()

loop.run_until_complete(morceaux("run"))

loop.run_until_complete(
    asyncio.gather(morceaux("run1"),
                   morceaux("run2")))

fonctionne, mais affiche

/Users/tparment/git/flotpython-course/w8/w8-s2-code-in-video.py:23: DeprecationWarning: There is no current event loop
  loop = asyncio.get_event_loop()
run début
run milieu
run fin
/Users/tparment/git/flotpython-course/w8/w8-s2-code-in-video.py:28: DeprecationWarning: There is no current event loop
  asyncio.gather(morceaux("run1"),
run1 début
run2 début
run1 milieu
run2 milieu
run1 fin
run2 fin

essai #2

idem avec new_event_loop() plutôt que get_event_loop()

loop = asyncio.new_event_loop()

loop.run_until_complete(morceaux("run"))

loop.run_until_complete(
    asyncio.gather(morceaux("run1"),
                   morceaux("run2")))

cette fois ça plante avec

python w8-s2-code-in-video.py
run début
run milieu
run fin
/Users/tparment/git/flotpython-course/w8/w8-s2-code-in-video.py:28: DeprecationWarning: There is no current event loop
  asyncio.gather(morceaux("run1"),
Traceback (most recent call last):
  File "/Users/tparment/git/flotpython-course/w8/w8-s2-code-in-video.py", line 27, in <module>
    loop.run_until_complete(
  File "/Users/tparment/miniconda3/envs/flotpython-course/lib/python3.10/asyncio/base_events.py", line 625, in run_until_complete
    future = tasks.ensure_future(future, loop=self)
  File "/Users/tparment/miniconda3/envs/flotpython-course/lib/python3.10/asyncio/tasks.py", line 615, in ensure_future
    return _ensure_future(coro_or_future, loop=loop)
  File "/Users/tparment/miniconda3/envs/flotpython-course/lib/python3.10/asyncio/tasks.py", line 621, in _ensure_future
    raise ValueError('The future belongs to a different loop than '
ValueError: The future belongs to a different loop than the one specified as the loop argument

essai #3

pareil mais on passe par une coroutine main() qui appelle gather()

async def main():
    L = await asyncio.gather(
        morceaux("run 1"),
        morceaux("run 2"),
    )
    print(L)

loop = asyncio.new_event_loop()

loop.run_until_complete(morceaux("run"))

loop.run_until_complete(main())

cette fois ça fonctionne pas mal

run début
run milieu
run fin
run 1 début
run 2 début
run 1 milieu
run 2 milieu
run 1 fin
run 2 fin
['run 1 par morceaux', 'run 2 par morceaux']
parmentelat commented 2 years ago

reste à documenter un peu la même démarche avec les notebooks

Karduin commented 2 years ago

Mais le fait que run_until_complete() soit du low level api ne va pas poser de problème ? Il ne faudrait pas théoriquement passer par les fonctions high level pour des petits codes comme ça ? ( enfin d’après la doc ).

Je ne sais pas qu'en penser.

parmentelat commented 2 years ago

honnêtement moi non plus à vrai dire à l'époque je n'avais pas trop le choix, et ça m'allait bien parce que je voulais introduire la boucle; même si c'est du low-level, c'est important de comprendre qu'il y a là derriere que boucle qui fait avancer le schmilblick

donc quelque part j'avais envie de rester le plus possible fidèle au code original en plus parmi les essais que j'ai faits, il y avait asyncio.run() mais si je me souviens bien je pouvais l'utiliser une seule fois, ou quelque chose dans ce goût-là, donc j'ai écarté ça, parce que dans les notebooks on a lance souvent plusieurs trucs d'affilée dans la même boucle, et ça n'est pas compatible avec l'usage de asyncio.run() si je comprends bien

enfin j'ai eu par ailleurs les chiffres de fréquentation de cette partie sur les vidéos, et ça confirme le peu d'intérêt que tout ceci a suscité donc je pense qu'il est urgent d'attendre et de réfléchir :-)