2015-08-18 13 views
18

A volte ci vuole molto tempo per eseguire una singola cella, mentre è in esecuzione, vorrei scrivere ed eseguire altre celle nello stesso notebook, accedendo alle variabili in lo stesso contesto.un nuovo thread per eseguire una cella in un notebook ipython/jupyter

C'è qualche magia ipython che può essere utilizzata in modo tale che quando viene aggiunta a una cella, l'esecuzione della cella creerà automaticamente un nuovo thread e verrà eseguito con dati globali condivisi nel notebook?

risposta

7

Potrebbe non essere una risposta, ma piuttosto la direzione. Non ho visto nulla del genere, comunque sono interessato anche a questo.

Le mie scoperte attuali suggeriscono che è necessario definirlo propria magia cella personalizzata. Buone referenze sarebbero la cella della sezione magia personalizzato nella documentazione e due esempi che vorrei prendere in considerazione:

Entrambi i collegamenti che avvolgono il codice in una discussione. Questo potrebbe essere un punto di partenza.

UPDATE: ngcm dimostrativi presso github ha descrizione della classe di processi in background

##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() 

UPDATE 2: Un'altra opzione:

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() 
2

Ecco un piccolo frammento che mi è venuta con

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 

Utilizza il modulo incorporato IPython IPython.lib.backgroundjobs. Quindi il codice è piccolo e semplice e non vengono introdotte nuove dipendenze.

lo uso così:

jobs = jobs_manager() 

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

Quindi è possibile monitorare lo stato con:

jobs.status() 

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

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

Se il lavoro non è possibile ispezionare analisi dello stack con

jobs.traceback(0) 

C'è nessun modo per uccidere un lavoro. Quindi io uso con attenzione questo hack sporco:

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') 

solleva SystemError in un dato thread.Quindi, per uccidere un lavoro che faccio

kill_thread(jobs.all[1]) 

Per uccidere tutti i processi in esecuzione che faccio

for thread in jobs.running: 
    kill_thread(thread) 

Mi piace usare %job con basata su widget barra di avanzamento https://github.com/alexanderkuk/log-progress come questo:

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

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

Si può persino usare %job invece di 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

Alcuni problemi evidenti con questo codice:

  1. Non è possibile utilizzare codice arbitrario nel %job. Non ci possono essere assegnazioni e non stampe per esempio. Quindi lo uso con le routine che memorizzano i risultati sul disco rigido

  2. A volte il trucco sporco in kill_thread non funziona. Penso che sia per questo che IPython.lib.backgroundjobs non ha questa funzionalità di progettazione. Se thread sta eseguendo alcune chiamate di sistema come sleep o read, l'eccezione viene ignorata.

  3. Utilizza thread. Python ha GIL, in modo da %job non può essere utilizzato per alcuni calcoli pesanti che prendono in bytecode Python

+0

@minrk La prego di guardare a questa risposta e vedere se ci eventuali altri problemi con questa soluzione che io non pensavo di? – alexanderkuk