Подтвердить что ты не робот

Подпроцесс Python: обратный вызов при выходе cmd

В настоящее время я запускаю программу, используя subprocess.Popen(cmd, shell=TRUE)

Я новичок в Python, но он "чувствует", как будто должен быть какой-то api, который позволяет мне делать что-то похожее на:

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

Я делаю это так, чтобы function_to_call_on_exit мог что-то сделать, основываясь на знании того, что cmd завершил (например, подсчет количества текущих внешних процессов)

Я предполагаю, что я мог бы довольно тривиально обернуть подпроцесс в классе, который комбинировал потоки с помощью метода Popen.wait(), но поскольку я еще не выполнял потоки в Python, и кажется, что это может быть достаточно распространено для существования API, Я подумал, что сначала попытаюсь найти его.

Заранее спасибо:)

4b9b3361

Ответ 1

Вы правы - для этого нет хорошего API. Вы также правы в своей второй точке - тривиально легко создать функцию, которая делает это для вас, используя потоки.

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

В Python четная потоковая обработка довольно проста, но обратите внимание, что если onExit() является дорогостоящим по вычислительной технологии, вы захотите поместить это в отдельный процесс вместо использования многопроцессорности (чтобы GIL не замедлял вашу программу). Это на самом деле очень просто - вы можете просто заменить все вызовы на threading.Thread на multiprocessing.Process, поскольку они следуют (почти) одному и тому же API.

Ответ 2

Существует модуль concurrent.futures в Python 3.2 (доступен через pip install futures для более старого Python < 3.2):

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

Обратный вызов будет вызван в том же процессе, который называется f.add_done_callback().

Полная программа

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

Выход

$ 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

Ответ 3

Я изменил ответ Daniel G, чтобы просто передать subprocess.Popen args и kwargs как сами, а не как отдельный список tupple/, так как я хотел использовать аргументы ключевого слова с subprocess.Popen.

В моем случае у меня был метод postExec(), который я хотел запустить после subprocess.Popen('exe', cwd=WORKING_DIR)

С помощью приведенного ниже кода он просто становится 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

Ответ 4

У меня была такая же проблема, и я решил ее использовать с помощью multiprocessing.Pool. Есть два взломанных трюка:

  • сделать размер пула 1
  • передать повторяющиеся аргументы внутри итерации длины 1

результатом является одна функция, выполняемая с обратным вызовом при завершении

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

В моем случае я хотел, чтобы вызов был неблокирующим. Прекрасно работает

Ответ 5

Я был вдохновлен Даниэлем Г. ответить и реализовал очень простой случай использования - в моей работе мне часто приходится повторять вызовы одного и того же (внешнего) процесса с разными аргументами. Я взломал способ определить, когда был выполнен каждый конкретный вызов, но теперь у меня есть гораздо более чистый способ выпуска обратных вызовов.

Мне нравится эта реализация, потому что она очень проста, но она позволяет мне выпускать асинхронные вызовы для нескольких процессоров (обратите внимание, что я использую multiprocessing вместо threading) и получаю уведомление по завершении.

Я протестировал образец программы и отлично работал. Пожалуйста, отредактируйте по своему желанию и предоставите обратную связь.

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"

Пример вывода:

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

Ниже приведен исходный код sleeper.c - мой пример "трудоемкого" внешнего процесса

#include<stdlib.h>
#include<unistd.h>

int main(int argc, char *argv[]){
  unsigned int t = atoi(argv[1]);
  sleep(t);
  return EXIT_SUCCESS;
}

скомпилировать как:

gcc -o sleeper sleeper.c

Ответ 6

AFAIK нет такого API, по крайней мере, не в модуле subprocess. Вам нужно катить что-то самостоятельно, возможно, используя потоки.