web-dev-qa-db-fra.com

un nouveau thread pour exécuter une cellule dans le cahier ipython / jupyter

Parfois, il faut beaucoup de temps pour exécuter une seule cellule, alors qu'il est en cours d'exécution, je voudrais écrire et exécuter d'autres cellules dans le même bloc-notes, en accédant aux variables dans le même contexte.

Existe-t-il une magie ipython pouvant être utilisée de telle sorte que lorsqu'elle est ajoutée à une cellule, l'exécution de la cellule crée automatiquement un nouveau thread et s'exécute avec des données globales partagées dans le bloc-notes?

28
chentingpc

Ce n'est peut-être pas une réponse, mais plutôt la direction à suivre. Je n'ai rien vu de tel, ça m'intéresse toujours aussi.

Mes découvertes actuelles suggèrent qu'il faut définir sa propre magie cellulaire personnalisée . De bonnes références seraient la section magique de cellule personnalisée dans la documentation et deux exemples que je considérerais:

Ces deux liens enveloppant le code dans un thread. Cela pourrait être un point de départ.

MISE À JOUR: ngcm-tutorial at github a la description de la classe des jobs d'arrière-plan

##github.com/jupyter/ngcm-tutorial/blob/master/Day-1/IPython%20Kernel/Background%20Jobs.ipynb
from IPython.lib import backgroundjobs as bg
jobs = bg.BackgroundJobManager()

def printfunc(interval=1, reps=5):
    for n in range(reps):
        time.sleep(interval)
        print('In the background... %i' % n)
        sys.stdout.flush()
    print('All done!')
    sys.stdout.flush()

jobs.new('printfunc(1,3)')
jobs.status()

MISE À JOUR 2: Une autre option:

from IPython.display import display
from ipywidgets import IntProgress

import threading

class App(object):
    def __init__(self, nloops=2000):
        self.nloops = nloops
        self.pb = IntProgress(description='Thread loops', min=0, max=self.nloops)

    def start(self):
        display(self.pb)
        while self.pb.value < self.nloops:
            self.pb.value += 1 
        self.pb.color = 'red'

app = App(nloops=20000)

t = threading.Thread(target=app.start)

t.start()
#t.join()
11
kpykc

Voici un petit extrait que j'ai trouvé

def jobs_manager():
    from IPython.lib.backgroundjobs import BackgroundJobManager
    from IPython.core.magic import register_line_magic
    from IPython import get_ipython

    jobs = BackgroundJobManager()

    @register_line_magic
    def job(line):
        ip = get_ipython()
        jobs.new(line, ip.user_global_ns)

    return jobs

Il utilise le module intégré IPython IPython.lib.backgroundjobs. Le code est donc petit et simple et aucune nouvelle dépendance n'est introduite.

Je l'utilise comme ceci:

jobs = jobs_manager()

%job [fetch_url(_) for _ in urls]  # saves html file to disk
Starting job # 0 in a separate thread.

Ensuite, vous pouvez surveiller l'état avec:

jobs.status()

Running jobs:
1 : [fetch_url(_) for _ in urls]

Dead jobs:
0 : [fetch_url(_) for _ in urls]

Si le travail échoue, vous pouvez inspecter la trace de la pile avec

jobs.traceback(0)

Il n'y a aucun moyen de tuer un emploi. J'utilise donc soigneusement ce hack sale:

def kill_thread(thread):
    import ctypes

    id = thread.ident
    code = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(id),
        ctypes.py_object(SystemError)
    )
    if code == 0:
        raise ValueError('invalid thread id')
    Elif code != 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(
            ctypes.c_long(id),
            ctypes.c_long(0)
        )
        raise SystemError('PyThreadState_SetAsyncExc failed')

Il lève SystemError dans un thread donné. Donc, pour tuer un travail que je fais

kill_thread(jobs.all[1])

Pour tuer tous les travaux en cours que je fais

for thread in jobs.running:
    kill_thread(thread)

J'aime utiliser %job avec une barre de progression basée sur un widget https://github.com/alexanderkuk/log-progress comme ceci:

%job [fetch_url(_) for _ in log_progress(urls, every=1)]

http://g.recordit.co/iZJsJm8BOL.gif

On peut même utiliser %job au lieu de multiprocessing.TreadPool:

for chunk in get_chunks(urls, 3):
    %job [fetch_url(_) for _ in log_progress(chunk, every=1)]

http://g.recordit.co/oTVCwugZYk.gif

Quelques problèmes évidents avec ce code:

  1. Vous ne pouvez pas utiliser de code arbitraire dans %job. Il ne peut pas y avoir d'affectations et pas d'impressions par exemple. Je l'utilise donc avec des routines qui stockent les résultats sur le disque dur

  2. Parfois hack sale dans kill_thread ne marche pas. Je pense que c'est pourquoi IPython.lib.backgroundjobs n'a pas cette fonctionnalité de par sa conception. Si le thread effectue un appel système comme sleep ou read, l'exception est ignorée.

  3. Il utilise des threads. Python a GIL, donc %job ne peut pas être utilisé pour certains calculs lourds qui prennent en python octet code

5
alexanderkuk