Принуждение subprocess.Popen для записи stdout/stderr в файлоподобный объект в python с использованием функции write(), а не fileno()

Моя цель — открыть процесс, использующий subprocess.Popen в python, и заставить этот процесс передавать свои stdout и stderr в пользовательский класс RingBuffer, который я написал, что позволяет мне периодически проверять содержимое буфера из того же пространства, в котором я создал экземпляр подпроцесса. из. Это важно, я знаю, что есть способы сделать отдельную программу, направить вывод подпроцесса в stdin этой программы кольцевого буфера, но тогда мне придется пойти и вручную проверить какой-то базовый файл, содержащий содержимое кольцевого буфера и т. д. и т.д. Идеальным было бы связать вывод подпроцесса с каким-то объектом, к которому у меня есть доступ.

Во-первых, из документации для subprocess (python 2.X) (https://docs.python.org/2/library/subprocess.html)

stdin, stdout и stderr определяют стандартный ввод, стандартный вывод и стандартный дескриптор файла ошибки исполняемой программы соответственно. Допустимые значения: PIPE, существующий файловый дескриптор (положительное целое число), существующий файловый объект и None. PIPE указывает, что необходимо создать новый канал для дочернего элемента. При настройках по умолчанию None перенаправление не происходит; дескрипторы дочерних файлов будут унаследованы от родителя. Кроме того, stderr может быть STDOUT, что указывает на то, что данные stderr из дочернего процесса должны быть записаны в тот же дескриптор файла, что и для stdout.

«существующий файловый объект», поэтому я предполагаю, что если я создам класс, соответствующий интерфейсу file, он должен работать, верно?

Скажем, я сделал такой класс

class RingBuffer(object):

    def __init__(max_size=1024*1024):
      self.max_size = max_size
      self.current_size = 0


    def write(self, data):
        self.current_size += len(data)
        self.data.append(data)
        if self.current_size >= self.max_size_bytes:
            while self.current_size >= self.trim_size_bytes:
                try:
                    popped = self.data.pop()
                    self.current_size -= len(popped)
                except IndexError as e:
                    break

def writelines(self, sequence):
    for item in sequence:
        self.write(item)

def dumps(self):
    ret = [line for line in self.data]
    return '\n'.join(ret)

def clear(self):
    self.data.clear()
    self.current_size = 0

конечно, в этой программе есть вероятные ошибки, но вы поняли суть, она предоставляет функцию write() и записывает данные в кольцевой буфер, обрезая буфер до определенного размера, когда он становится слишком ошибочным, и позволяя пользователю восстановить данные, когда они хотят с функцией dumps().

Теперь, если я попробую что-то вроде этого

r = RingBuffer()
pr = subprocess.Popen(["timeout", "15", "yes"], stdout=r, stderr=subprocess.STDOUT)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 382, in __init__
    errread, errwrite), to_close = self._get_handles(stdin, stdout, stderr)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 818, in _get_handles
    c2pwrite = stdout.fileno()
AttributeError: 'RingBuffer' object has no attribute 'fileno'

Итак, в моем «файлоподобном» объекте отсутствует функция fileno() для соответствия файловому интерфейсу. Вот в чем проблема. Зачем нужен файл? Почему он не может просто использовать предоставленную мне функцию write()? Я предполагаю, что он обойдет мою функцию write и вместо этого будет использовать fileno для записи непосредственно в файл?

Скажем, я добавляю заглушку этой функции

def fileno()
    return None

тогда это происходит

r = RingBuffer()
pr = subprocess.Popen(["timeout", "15", "yes"], stdout=r, stderr=subprocess.STDOUT)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 390, in __init__
    errread, errwrite)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 1024, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

поэтому мой вопрос: как я могу заставить subprocess.Popen использовать мою функцию write() для моего объекта, подобного file, вместо того, чтобы пытаться писать непосредственно в дескриптор файла, возвращаемый из несуществующей функции fileno()? Если нет способа сделать это... есть ли способ выполнить то, что я хочу здесь?

Я знаю, что теоретически я мог бы создать некоторый файл /tmp/ringlog.txt и открыть этот файл при создании экземпляра класса, а затем заставить программу писать в этот файл, а моя программа периодически просматривать файл и хранить его под max_size, используя аналогичный кольцевой буфер. алгоритм, но это беспорядок.

Другой вариант — создать программу, которая читает стандартный ввод, записывает в файл и буферизует содержимое, чтобы сохранить файл меньше определенного размера, но тогда я все еще имею дело с фактическим файлом, я просто хочу сохранить содержимое в памяти и доступный из вызывающей среды Python.


person John Allard    schedule 27.07.2018    source источник
comment
Совершенно невозможно убедить произвольный другой процесс, работающий в своем собственном пространстве памяти, направить его вывод на объект Python, который существует только в пространстве памяти вашего процесса. subprocess.PIPE - это именно то, что вам нужно здесь: другой процесс записывает в (предоставленный ОС) конвейер, ваш процесс читает с вашего конца конвейера.   -  person jasonharper    schedule 28.07.2018
comment
хорошо, это имеет смысл на самом деле. Существуют ли какие-либо (известные) проблемы с длительными процессами, передающими свой вывод обратно вызывающей стороне?   -  person John Allard    schedule 28.07.2018


Ответы (2)


Дочерний процесс будет записывать в свой стандартный вывод, используя стандартные вызовы записи файлов на уровне ОС, что означает, что ему нужно что-то, совместимое с этими вызовами. Дочерний процесс не может просматривать память Python или вызывать методы объектов Python.

Если вы хотите записать вывод подпроцесса в файлоподобный объект, который не представляет что-то, что ОС может рассматривать как файл, вам придется получать вывод через канал и записывать его в файлоподобный объект. самим собой. Вы можете создать рабочий поток для этого (и обязательно синхронизировать доступ к объекту, если вы планируете читать его до того, как рабочий поток завершится), но может быть проще взаимодействовать с каналом напрямую.

person user2357112 supports Monica    schedule 27.07.2018

Если вы собираетесь работать с subprocess.Popen, то я предлагаю разобраться в проблемах, возникающих при использовании каналов, которые, как правило, находятся в области взаимоблокировок.

См.: https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/

person Community    schedule 02.05.2019