class_docstr: Class to manage a pool of backgrounded threaded jobs.
  
  Below, we assume that 'jobs' is a BackgroundJobManager instance.
  
  Usage summary (see the method docstrings for details):
  
    jobs.new(...) -> start a new job
    
    jobs() or jobs.status() -> print status summary of all jobs
  
    jobs[N] -> returns job number N.
  
    foo = jobs[N].result -> assign to variable foo the result of job N
  
    jobs[N].traceback() -> print the traceback of dead job N
  
    jobs.remove(N) -> remove (finished) job N
  
    jobs.flush() -> remove all finished jobs
    
  As a convenience feature, BackgroundJobManager instances provide the
  utility result and traceback methods which retrieve the corresponding
  information from the jobs list:
  
    jobs.result(N) <--> jobs[N].result
    jobs.traceback(N) <--> jobs[N].traceback()
  
  While this appears minor, it allows you to use tab completion
  interactively on the job manager instance.
docstr: Add a new background job and start it in a separate thread.
  
  There are two types of jobs which can be created:
  
  1. Jobs based on expressions which can be passed to an eval() call.
  The expression must be given as a string.  For example:
  
    job_manager.new('myfunc(x,y,z=1)'[,glob[,loc]])
  
  The given expression is passed to eval(), along with the optional
  global/local dicts provided.  If no dicts are given, they are
  extracted automatically from the caller's frame.
  
  A Python statement is NOT a valid eval() expression.  Basically, you
  can only use as an eval() argument something which can go on the right
  of an '=' sign and be assigned to a variable.
  
  For example,"print 'hello'" is not valid, but '2+3' is.
  
  2. Jobs given a function object, optionally passing additional
  positional arguments:
  
    job_manager.new(myfunc, x, y)
  
  The function is called with the given arguments.
  
  If you need to pass keyword arguments to your function, you must
  supply them as a dict named kw:
  
    job_manager.new(myfunc, x, y, kw=dict(z=1))
  
  The reason for this assymmetry is that the new() method needs to
  maintain access to its own keywords, and this prevents name collisions
  between arguments to new() and arguments to your own functions.
  
  In both cases, the result is stored in the job.result field of the
  background job object.
  
  You can set `daemon` attribute of the thread by giving the keyword
  argument `daemon`.
  
  Notes and caveats:
  
  1. All threads running share the same standard output.  Thus, if your
  background jobs generate output, it will come out on top of whatever
  you are currently writing.  For this reason, background jobs are best
  used with silent functions which simply return their output.
  
  2. Threads also all work within the same global namespace, and this
  system does not lock interactive variables.  So if you send job to the
  background which operates on a mutable object for a long time, and
  start modifying that same mutable object interactively (or in another
  backgrounded job), all sorts of bizarre behaviour will occur.
  
  3. If a background job is spending a lot of time inside a C extension
  module which does not release the Python Global Interpreter Lock
  (GIL), this will block the IPython prompt.  This is simply because the
  Python interpreter can only switch between threads at Python
  bytecodes.  While the execution is inside C code, the interpreter must
  simply wait unless the extension module releases the GIL.
  
  4. There is no way, due to limitations in the Python threads library,
  to kill a thread once it has started.
docstr_tok: ["Add","a","new","background","job","and","start","it","in","a","separate","thread",".","There","are","two","types","of","jobs","which","can","be","created",":","1",".","Jobs","based","on","expressions","which","can","be","passed","to","an","eval","(",")","call",".","The","expression","must","be","given","as","a","string",".","For","example",":","job_manager.new","(","'myfunc","(","x",",","y",",","z=1",")","'","[",",","glob","[",",","loc","]","]",")","The","given","expression","is","passed","to","eval","(",")",",","along","with","the","optional","global\/local","dicts","provided",".","If","no","dicts","are","given",",","they","are","extracted","automatically","from","the","caller","'s","frame",".","A","Python","statement","is","NOT","a","valid","eval","(",")","expression",".","Basically",",","you","can","only","use","as","an","eval","(",")","argument","something","which","can","go","on","the","right","of","an","'=","'","sign","and","be","assigned","to","a","variable",".","For","example",",","''","print","'hello","'","''","is","not","valid",",","but","'","2+3","'","is",".","2",".","Jobs","given","a","function","object",",","optionally","passing","additional","positional","arguments",":","job_manager.new","(","myfunc",",","x",",","y",")","The","function","is","called","with","the","given","arguments",".","If","you","need","to","pass","keyword","arguments","to","your","function",",","you","must","supply","them","as","a","dict","named","kw",":","job_manager.new","(","myfunc",",","x",",","y",",","kw=dict","(","z=1",")",")","The","reason","for","this","assymmetry","is","that","the","new","(",")","method","needs","to","maintain","access","to","its","own","keywords",",","and","this","prevents","name","collisions","between","arguments","to","new","(",")","and","arguments","to","your","own","functions",".","In","both","cases",",","the","result","is","stored","in","the","job.result","field","of","the","background","job","object",".","You","can","set","`","daemon","`","attribute","of","the","thread","by","giving","the","keyword","argument","`","daemon","`",".","Notes","and","caveats",":","1",".","All","threads","running","share","the","same","standard","output",".","Thus",",","if","your","background","jobs","generate","output",",","it","will","come","out","on","top","of","whatever","you","are","currently","writing",".","For","this","reason",",","background","jobs","are","best","used","with","silent","functions","which","simply","return","their","output",".","2",".","Threads","also","all","work","within","the","same","global","namespace",",","and","this","system","does","not","lock","interactive","variables",".","So","if","you","send","job","to","the","background","which","operates","on","a","mutable","object","for","a","long","time",",","and","start","modifying","that","same","mutable","object","interactively","(","or","in","another","backgrounded","job",")",",","all","sorts","of","bizarre","behaviour","will","occur",".","3",".","If","a","background","job","is","spending","a","lot","of","time","inside","a","C","extension","module","which","does","not","release","the","Python","Global","Interpreter","Lock","(","GIL",")",",","this","will","block","the","IPython","prompt",".","This","is","simply","because","the","Python","interpreter","can","only","switch","between","threads","at","Python","bytecodes",".","While","the","execution","is","inside","C","code",",","the","interpreter","must","simply","wait","unless","the","extension","module","releases","the","GIL",".","4",".","There","is","no","way",",","due","to","limitations","in","the","Python","threads","library",",","to","kill","a","thread","once","it","has","started","."]
code: def new(self, func_or_exp, *args, **kwargs):
      """Add a new background job and start it in a separate thread.
  
      There are two types of jobs which can be created:
  
      1. Jobs based on expressions which can be passed to an eval() call.
      The expression must be given as a string.  For example:
  
        job_manager.new('myfunc(x,y,z=1)'[,glob[,loc]])
  
      The given expression is passed to eval(), along with the optional
      global/local dicts provided.  If no dicts are given, they are
      extracted automatically from the caller's frame.
  
      A Python statement is NOT a valid eval() expression.  Basically, you
      can only use as an eval() argument something which can go on the right
      of an '=' sign and be assigned to a variable.
  
      For example,"print 'hello'" is not valid, but '2+3' is.
  
      2. Jobs given a function object, optionally passing additional
      positional arguments:
  
        job_manager.new(myfunc, x, y)
  
      The function is called with the given arguments.
  
      If you need to pass keyword arguments to your function, you must
      supply them as a dict named kw:
  
        job_manager.new(myfunc, x, y, kw=dict(z=1))
  
      The reason for this assymmetry is that the new() method needs to
      maintain access to its own keywords, and this prevents name collisions
      between arguments to new() and arguments to your own functions.
  
      In both cases, the result is stored in the job.result field of the
      background job object.
  
      You can set `daemon` attribute of the thread by giving the keyword
      argument `daemon`.
  
      Notes and caveats:
  
      1. All threads running share the same standard output.  Thus, if your
      background jobs generate output, it will come out on top of whatever
      you are currently writing.  For this reason, background jobs are best
      used with silent functions which simply return their output.
  
      2. Threads also all work within the same global namespace, and this
      system does not lock interactive variables.  So if you send job to the
      background which operates on a mutable object for a long time, and
      start modifying that same mutable object interactively (or in another
      backgrounded job), all sorts of bizarre behaviour will occur.
  
      3. If a background job is spending a lot of time inside a C extension
      module which does not release the Python Global Interpreter Lock
      (GIL), this will block the IPython prompt.  This is simply because the
      Python interpreter can only switch between threads at Python
      bytecodes.  While the execution is inside C code, the interpreter must
      simply wait unless the extension module releases the GIL.
  
      4. There is no way, due to limitations in the Python threads library,
      to kill a thread once it has started."""
  
      if callable(func_or_exp):
          kw = kwargs.get("kw", {})
          job = BackgroundJobFunc(func_or_exp, *args, **kw)
      elif isinstance(func_or_exp, string_types):
          if not args:
              frame = sys._getframe(1)
              glob, loc = frame.f_globals, frame.f_locals
          elif len(args) == 1:
              glob = loc = args[0]
          elif len(args) == 2:
              glob, loc = args
          else:
              raise ValueError(
                  "Expression jobs take at most 2 args (globals,locals)"
              )
          job = BackgroundJobExpr(func_or_exp, glob, loc)
      else:
          raise TypeError("invalid args for new job")
  
      if kwargs.get("daemon", False):
          job.daemon = True
      job.num = len(self.all) + 1 if self.all else 0
      self.running.append(job)
      self.all[job.num] = job
      print("Starting job # %s in a separate thread." % job.num)
      job.start()
      return job
  
imports: from __future__ import print_function
  import sys
  import threading
  from IPython import get_ipython
  from IPython.core.ultratb import AutoFormattedTB
  from logging import error
  from IPython.utils.py3compat import string_types
label_desc: Use image node_id 5 for calling the BackgroundJobManager obj's underlying member method code with example usage: obj.new(func_or_exp) and returns: job