Подпроцесс Python Popen.communicate() эквивалентен Popen.stdout.read()? - программирование

Подпроцесс Python Popen.communicate() эквивалентен Popen.stdout.read()?

Очень конкретный вопрос (надеюсь): Каковы различия между следующими тремя кодами?

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

Я также приветствую другие замечания/предложения (хотя я уже хорошо знаю опасности shell=True и кросс-платформенные ограничения)

Заметьте, что я уже читал взаимодействие с подпроцессом Python, почему мой процесс работает с Popen.communicate, но не Popen.stdout.read()? и что я делаю не нужно/нужно взаимодействовать с программой после.

Также обратите внимание, что я уже читал Альтернативы ограничениям памяти Python Popen.communicate()?, но я действительно не понял...

Наконец, обратите внимание, что я знаю, что где-то существует опасность тупика, когда один буфер заполняется одним выходом с использованием одного метода, но я потерялся при поиске ясных объяснений в Интернете...

Первый код:

from subprocess import Popen, PIPE

def exe_f(command='ls -l', shell=True):
    """Function to execute a command and return stuff"""

    process = Popen(command, shell=shell, stdout=PIPE, stderr=PIPE)

    stdout = process.stdout.read()
    stderr = process.stderr.read()

    return process, stderr, stdout

Второй код:

from subprocess import Popen, PIPE
from subprocess import communicate

def exe_f(command='ls -l', shell=True):
    """Function to execute a command and return stuff"""

    process = Popen(command, shell=shell, stdout=PIPE, stderr=PIPE)

    (stdout, stderr) = process.communicate()

    return process, stderr, stdout

Третий код:

from subprocess import Popen, PIPE
from subprocess import wait

def exe_f(command='ls -l', shell=True):
    """Function to execute a command and return stuff"""

    process = Popen(command, shell=shell, stdout=PIPE, stderr=PIPE)

    code   = process.wait()
    stdout = process.stdout.read()
    stderr = process.stderr.read()

    return process, stderr, stdout

Спасибо.

4b9b3361

Ответ 1

Если вы посмотрите на источник subprocess.communicate(), он показывает прекрасный пример разницы:

def communicate(self, input=None):
    ...
    # Optimization: If we are only using one pipe, or no pipe at
    # all, using select() or threads is unnecessary.
    if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
        stdout = None
        stderr = None
        if self.stdin:
            if input:
                self.stdin.write(input)
            self.stdin.close()
        elif self.stdout:
            stdout = self.stdout.read()
            self.stdout.close()
        elif self.stderr:
            stderr = self.stderr.read()
            self.stderr.close()
        self.wait()
        return (stdout, stderr)

    return self._communicate(input)

Вы можете видеть, что communicate использует вызовы чтения для stdout и stderr, а также вызывает wait(). Это всего лишь вопрос порядка операций. В вашем случае, потому что вы используете PIPE для stdout и stderr, он переходит в _communicate():

def _communicate(self, input):
    stdout = None # Return
    stderr = None # Return

    if self.stdout:
        stdout = []
        stdout_thread = threading.Thread(target=self._readerthread,
                                         args=(self.stdout, stdout))
        stdout_thread.setDaemon(True)
        stdout_thread.start()
    if self.stderr:
        stderr = []
        stderr_thread = threading.Thread(target=self._readerthread,
                                         args=(self.stderr, stderr))
        stderr_thread.setDaemon(True)
        stderr_thread.start()

    if self.stdin:
        if input is not None:
            self.stdin.write(input)
        self.stdin.close()

    if self.stdout:
        stdout_thread.join()
    if self.stderr:
        stderr_thread.join()

    # All data exchanged.  Translate lists into strings.
    if stdout is not None:
        stdout = stdout[0]
    if stderr is not None:
        stderr = stderr[0]

    # Translate newlines, if requested.  We cannot let the file
    # object do the translation: It is based on stdio, which is
    # impossible to combine with select (unless forcing no
    # buffering).
    if self.universal_newlines and hasattr(file, 'newlines'):
        if stdout:
            stdout = self._translate_newlines(stdout)
        if stderr:
            stderr = self._translate_newlines(stderr)

    self.wait()
    return (stdout, stderr)

Это использует потоки для чтения из нескольких потоков одновременно. Затем он вызывает wait() в конце.

Итак, чтобы подвести итог:

  • Этот пример читается из одного потока за раз и не ждет завершения процесса.
  • Этот пример читает оба потока одновременно с помощью внутренних потоков и ждет завершения процесса.
  • Этот пример ждет завершения процесса, а затем читает один поток за раз. И, как вы упомянули, есть потенциал для тупиковой ситуации, если слишком много написано в потоках.

Кроме того, вам не нужны эти два оператора импорта во втором и третьем примерах:

from subprocess import communicate
from subprocess import wait

Это оба метода объекта Popen.