Podproces Pythona: wywołanie zwrotne po wyjściu cmd
Uruchamiam obecnie program używając subprocess.Popen(cmd, shell=TRUE)
Jestem całkiem nowy w Pythonie, ale wydaje mi się, że powinno być jakieś api, które pozwala mi zrobić coś podobnego do:
subprocess.Popen(cmd, shell=TRUE, postexec_fn=function_to_call_on_exit)
Robię to po to, aby function_to_call_on_exit
móc coś zrobić, wiedząc, że cmd się zakończył (na przykład zliczając liczbę uruchomionych zewnętrznych procesów)
Zakładam, że mógłbym dość trywialnie zawinąć podproces w klasę, która łączyła wątki z metodą Popen.wait()
, ale jako Nie robiłem jeszcze wątków w Pythonie i wydaje się, że może to być wystarczająco powszechne, aby API istniało, pomyślałem, że najpierw spróbuję je znaleźć.
Z góry dzięki:)
6 answers
Masz rację - nie ma do tego ładnego API. Masz również rację co do drugiego punktu - trywialnie łatwo jest zaprojektować funkcję, która robi to za Ciebie za pomocą wątku.
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
Nawet threading jest dość łatwy w Pythonie, ale zauważ, że jeśli onexit () jest kosztowne obliczeniowo, będziesz chciał umieścić to w osobnym procesie zamiast za pomocą przetwarzania wieloprocesorowego(aby GIL nie spowalniał Twojego programu). W rzeczywistości jest to bardzo proste - można po prostu zastąpić wszystkie wywołania threading.Thread
multiprocessing.Process
ponieważ podążają za (prawie) tym samym API.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-06 04:10:59
Jest concurrent.futures
moduł w Pythonie 3.2 (dostępny przez pip install futures
dla starszego Pythona
pool = Pool(max_workers=1)
f = pool.submit(subprocess.call, "sleep 2; echo done", shell=True)
f.add_done_callback(callback)
Wywołanie zwrotne zostanie wywołane w tym samym procesie, który wywołał f.add_done_callback()
.
Pełny program
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()
Wyjście
$ 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-06 09:43:22
Zmodyfikowałem odpowiedź Daniela G, aby po prostu przejść podproces.Popen args i kwargs jako siebie, a nie jako osobny tupple / list, ponieważ chciałem użyć argumentów słów kluczowych z podprocesem.Popen.
W moim przypadku miałem metodę postExec()
którą chciałem uruchomić po subprocess.Popen('exe', cwd=WORKING_DIR)
Z poniższym kodem, po prostu staje się 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-30 20:39:33
Miałem ten sam problem i rozwiązałem go za pomocą multiprocessing.Pool
. W grę wchodzą dwie hakerskie sztuczki:
- make size of pool 1
- przekazuje argumenty iterowalne w ramach iterowalnej długości 1
Wynikiem jest jedna funkcja wykonywana z wywołaniem zwrotnym po zakończeniu
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()
W moim przypadku chciałem, aby inwokacja była również nieblokująca. Działa pięknie
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-08 23:08:46
Zainspirował mnie Daniel G. answer i zaimplementowałem bardzo prosty przypadek użycia - w mojej pracy często muszę wykonywać powtarzające się wywołania do tego samego (zewnętrznego) procesu z różnymi argumentami. Hacked sposób, aby określić, kiedy każdy konkretny połączenie zostało zrobione, ale teraz mam znacznie czystszy sposób, aby wystawić wywołań zwrotnych.
Podoba mi się Ta implementacja, ponieważ jest bardzo prosta, ale pozwala mi na wykonywanie asynchronicznych wywołań do wielu procesorów (zauważ, że używam multiprocessing
zamiast threading
) i otrzymywanie powiadomienie po zakończeniu.
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"
Przykładowe wyjście:
program continued
finished Sleeper calling ./sleeper with arguments (1,)
finished Sleeper calling ./sleeper with arguments (2,)
finished Sleeper calling ./sleeper with arguments (5,)
Poniżej znajduje się kod źródłowy sleeper.c
- mój przykładowy" czasochłonny " proces zewnętrzny
#include<stdlib.h>
#include<unistd.h>
int main(int argc, char *argv[]){
unsigned int t = atoi(argv[1]);
sleep(t);
return EXIT_SUCCESS;
}
Skompiluj jako:
gcc -o sleeper sleeper.c
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-06 07:14:22
AFAIK nie ma takiego API, przynajmniej nie w module subprocess
. Musisz toczyć coś na własną rękę, ewentualnie za pomocą nici.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-05 23:52:26