python callback subprocess exit

Subproceso Python: devolución de llamada cuando se sale de cmd



callback subprocess (6)

AFAIK no existe tal API, al menos no en el módulo de subprocess . Necesitas rodar algo por tu cuenta, posiblemente usando hilos.

Actualmente estoy iniciando un programa usando subprocess.Popen(cmd, shell=TRUE)

Soy bastante nuevo en Python, pero parece que debería haber alguna API que me permita hacer algo similar a:

subprocess.Popen(cmd, shell=TRUE, postexec_fn=function_to_call_on_exit)

Estoy haciendo esto para que function_to_call_on_exit pueda hacer algo basándose en saber que el cmd ha salido (por ejemplo, manteniendo la cuenta del número de procesos externos que se están ejecutando actualmente)

Supongo que podría trivialmente envolver el subproceso en una clase que combina subprocesos con el método Popen.wait() , pero como aún no he hecho subprocesos en Python y parece que esto podría ser lo suficientemente común como para que exista una API, Pensé que iba a tratar de encontrar uno primero.

Gracias por adelantado :)


Hay un módulo concurrent.futures en Python 3.2 (disponible a través de los pip install futures para Python <3.2 más antiguo):

pool = Pool(max_workers=1) f = pool.submit(subprocess.call, "sleep 2; echo done", shell=True) f.add_done_callback(callback)

La devolución de llamada se llamará en el mismo proceso que se llamó f.add_done_callback() .

Programa completo

import logging import subprocess # to install run `pip install futures` on Python <3.2 from concurrent.futures import ThreadPoolExecutor as Pool info = logging.getLogger(__name__).info def callback(future): if future.exception() is not None: info("got exception: %s" % future.exception()) else: info("process returned %d" % future.result()) def main(): logging.basicConfig( level=logging.INFO, format=("%(relativeCreated)04d %(process)05d %(threadName)-10s " "%(levelname)-5s %(msg)s")) # wait for the process completion asynchronously info("begin waiting") pool = Pool(max_workers=1) f = pool.submit(subprocess.call, "sleep 2; echo done", shell=True) f.add_done_callback(callback) pool.shutdown(wait=False) # no .submit() calls after that point info("continue waiting asynchronously") if __name__=="__main__": main()

Salida

$ python . && python3 . 0013 05382 MainThread INFO begin waiting 0021 05382 MainThread INFO continue waiting asynchronously done 2025 05382 Thread-1 INFO process returned 0 0007 05402 MainThread INFO begin waiting 0014 05402 MainThread INFO continue waiting asynchronously done 2018 05402 Thread-1 INFO process returned 0


Me inspiré en la respuesta de Daniel G. e implementé un caso de uso muy simple. En mi trabajo a menudo necesito hacer llamadas repetidas al mismo proceso (externo) con diferentes argumentos. Había pirateado una forma de determinar cuándo se hacía cada llamada específica, pero ahora tengo una forma mucho más clara de emitir devoluciones de llamada.

Me gusta esta implementación porque es muy simple, pero me permite emitir llamadas asíncronas a múltiples procesadores (aviso que uso multiprocessing lugar de threading ) y recibir una notificación cuando se complete.

He probado el programa de muestra y funciona muy bien. Por favor, edita a voluntad y proporciona comentarios.

import multiprocessing import subprocess class Process(object): """This class spawns a subprocess asynchronously and calls a `callback` upon completion; it is not meant to be instantiated directly (derived classes are called instead)""" def __call__(self, *args): # store the arguments for later retrieval self.args = args # define the target function to be called by # `multiprocessing.Process` def target(): cmd = [self.command] + [str(arg) for arg in self.args] process = subprocess.Popen(cmd) # the `multiprocessing.Process` process will wait until # the call to the `subprocess.Popen` object is completed process.wait() # upon completion, call `callback` return self.callback() mp_process = multiprocessing.Process(target=target) # this call issues the call to `target`, but returns immediately mp_process.start() return mp_process if __name__ == "__main__": def squeal(who): """this serves as the callback function; its argument is the instance of a subclass of Process making the call""" print "finished %s calling %s with arguments %s" % ( who.__class__.__name__, who.command, who.args) class Sleeper(Process): """Sample implementation of an asynchronous process - define the command name (available in the system path) and a callback function (previously defined)""" command = "./sleeper" callback = squeal # create an instance to Sleeper - this is the Process object that # can be called repeatedly in an asynchronous manner sleeper_run = Sleeper() # spawn three sleeper runs with different arguments sleeper_run(5) sleeper_run(2) sleeper_run(1) # the user should see the following message immediately (even # though the Sleeper calls are not done yet) print "program continued"

Salida de muestra:

program continued finished Sleeper calling ./sleeper with arguments (1,) finished Sleeper calling ./sleeper with arguments (2,) finished Sleeper calling ./sleeper with arguments (5,)

A continuación se muestra el código fuente de sleeper.c : mi proceso externo "que consume mucho tiempo" de muestra

#include<stdlib.h> #include<unistd.h> int main(int argc, char *argv[]){ unsigned int t = atoi(argv[1]); sleep(t); return EXIT_SUCCESS; }

compilar como

gcc -o sleeper sleeper.c


Modifiqué la respuesta de Daniel G para simplemente pasar el subproceso. Abrir args y kwargs como ellos mismos en lugar de como un tupple / list separado, ya que quería usar argumentos de palabras clave con subprocess.Popen.

En mi caso tuve un método postExec() que quería ejecutar después de subprocess.Popen(''exe'', cwd=WORKING_DIR)

Con el código a continuación, simplemente se convierte en popenAndCall(postExec, ''exe'', cwd=WORKING_DIR)

import threading import subprocess def popenAndCall(onExit, *popenArgs, **popenKWArgs): """ Runs a subprocess.Popen, and then calls the function onExit when the subprocess completes. Use it exactly the way you''d normally use subprocess.Popen, except include a callable to execute as the first argument. onExit is a callable object, and *popenArgs and **popenKWArgs are simply passed up to subprocess.Popen. """ def runInThread(onExit, popenArgs, popenKWArgs): proc = subprocess.Popen(*popenArgs, **popenKWArgs) proc.wait() onExit() return thread = threading.Thread(target=runInThread, args=(onExit, popenArgs, popenKWArgs)) thread.start() return thread # returns immediately after the thread starts


Tienes razón, no hay una API agradable para esto. También tiene razón en su segundo punto: es trivialmente fácil diseñar una función que haga esto por usted mediante el uso de hilos.

import threading import subprocess def popenAndCall(onExit, popenArgs): """ Runs the given args in a subprocess.Popen, and then calls the function onExit when the subprocess completes. onExit is a callable object, and popenArgs is a list/tuple of args that would give to subprocess.Popen. """ def runInThread(onExit, popenArgs): proc = subprocess.Popen(*popenArgs) proc.wait() onExit() return thread = threading.Thread(target=runInThread, args=(onExit, popenArgs)) thread.start() # returns immediately after the thread starts return thread

Incluso el subprocesamiento es bastante fácil en Python, pero tenga en cuenta que si onExit () es computacionalmente costoso, querrá poner esto en un proceso separado en lugar de usar multiprocesamiento (para que GIL no ralentice su programa). En realidad es muy simple: básicamente puede reemplazar todas las llamadas a threading.Thread con multiprocessing.Process ya que siguen (casi) la misma API.


Tuve el mismo problema, y ​​lo resolví utilizando multiprocessing.Pool . Hay dos trucos hacky involucrados:

  1. hacer el tamaño de la piscina 1
  2. pasar argumentos iterables dentro de un iterable de longitud 1

el resultado es una función ejecutada con devolución de llamada al finalizar

def sub(arg): print arg #prints [1,2,3,4,5] return "hello" def cb(arg): print arg # prints "hello" pool = multiprocessing.Pool(1) rval = pool.map_async(sub,([[1,2,3,4,5]]),callback =cb) (do stuff) pool.close()

En mi caso, también quería que la invocación no fuera bloqueada. Funciona muy bien