Как динамически взаимодействовать с порожденными процессами, используя модуль подпроцесса в Python - PullRequest
0 голосов
/ 03 апреля 2020

Чтобы дать некоторый контекст, я должен оценивать коды C моих учеников. Это структура папок:

    Root
      |_____ID#1
        |____task1.c
        |____task2.c
        |____task3.c
      |_____ID#2
        |____task1.c
        |____task2.c
        |____task3.c
        ...

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

Я хочу иметь возможность сохранить:

а. входные данные для процессов, которые я должен предоставить им в интерактивном режиме (рассмотрите следующий снимок экрана для того, что я подразумеваю под интерактивностью после «Компиляции кода»),

enter image description here

б. их вывод после обработки ввода

c. их ошибки (если есть)

в json или даже в текстовый файл на данный момент.

Это то, что у меня так далеко:

import os
import subprocess
from zipfile import ZipFile

student_folders_root = "/home/mahieyin/Documents/Code/Python/CSE115/Mid #1-20200403T150847Z-001/Mid #1/"

for idx, (directory, _, zipfiles) in enumerate(os.walk(student_folders_root)):
    result = ""
    student_id = directory.rsplit(os.sep, 1)[-1]

    if idx == 0:
        continue
    elif len(zipfiles) == 0:        
        result += f"{student_id} did not attend the mid term exam."
        print(result)
    else:
        zipfiles = list(filter(lambda x: x.endswith(".zip"), zipfiles))

        path_to_zip = os.path.join(directory, *zipfiles)

        if path_to_zip.endswith('.zip'):
            with ZipFile(path_to_zip, 'r') as zip_object:
                zip_object.extractall(directory)

        print(f"\033[1;33;40mProcessing student: {student_id}...")

        for _, _, files in os.walk(directory):
            files = list(filter(lambda x: x.endswith('.c'), files))
            files = list(map(lambda x: os.path.join(directory, x), files))

            executable_name = "a.out"
            exec_path = os.path.join(directory, executable_name)

            for c_file in files:
                try:                
                    print(f"\n\n\033[1;35;40mProcessing file: {c_file}", end="\n\n")
                    compile_command = f"gcc \"{c_file}\" -o \"{exec_path}\""
                    run_command = f"\"{exec_path}\""
                    # command = f"gcc \"{c_file}\" -o \"{exec_path}\""

                    # subprocess.call(command, shell=True)
                    compile_result = subprocess.check_output(compile_command, shell=True)
                    compile_result = compile_result.decode('utf-8').strip()

                    if len(compile_result) == 0:
                        print("Code compiles.")                        
                        run_process = subprocess.run(run_command, shell=True)
                    else:
                        print("Compilation error.")
                        print("\033[1;31;40m", compile_result)
                except subprocess.CalledProcessError:
                    pass

Это работает как и ожидалось. Я вижу ошибки компиляции, если исходный файл не удается скомпилировать. Я могу предоставить вход для порожденных процессов и увидеть результат. Однако я не могу обернуть голову вокруг способа, которым я могу получить выходные данные, сохраняя при этом интерактивность.

По сути, всякий раз, когда я пытаюсь установить параметры stdout и stderr в subprocess.Popen() или subprocess.call(), я теряю взаимодействие с порожденным процессом. Запросы порожденных процессов не показывают (e.g. "Please enter the value: ") до тех пор, пока я не предоставлю ввод, что на самом деле не очень элегантно.

Заранее спасибо.

Ответы [ 2 ]

1 голос
/ 04 апреля 2020

Посмотрите на pexpect:

Pexpect - это чистый Python модуль для порождения дочерних приложений; контролировать их; и реагирование на ожидаемые закономерности в их выводе.

0 голосов
/ 04 апреля 2020

Следуя совету Ролана Смита, я решил использовать решение, которое не изящно, но экономит мое время. Я заменил второй subprocess вызов на pexpect.

import pexpect
import subprocess
import os
from zipfile import ZipFile

student_folders_root = "/home/mahieyin/Documents/Code/Python/CSE115/Mid #1-20200403T150847Z-001/Mid #1/"

for idx, (directory, _, zipfiles) in enumerate(os.walk(student_folders_root)):
    result = ""
    student_id = directory.rsplit(os.sep, 1)[-1]

    if (idx == 0):
        continue
    elif len(zipfiles) == 0:        
        result += f"{student_id} did not attend the mid term exam."
        print(result)
    else:
        zipfiles = list(filter(lambda x: x.endswith(".zip"), zipfiles))

        path_to_zip = os.path.join(directory, *zipfiles)

        if (path_to_zip.endswith('.zip')):
            with ZipFile(path_to_zip, 'r') as zip_object:
                zip_object.extractall(directory)

        print(f"Processing student: {student_id}...")

        for _, _, files in os.walk(directory):
            files = list(filter(lambda x: x.endswith('.c'), files))
            files = list(map(lambda x: os.path.join(directory, x), files))

            executable_name = "a.out"
            exec_path = os.path.join(directory, executable_name)

            for c_file in files:
                try:
                    print(f"\n\nProcessing file: {c_file}", end="\n\n")
                    compile_command = f"gcc \"{c_file}\" -o \"{exec_path}\""
                    run_command = f"\"{exec_path}\""
                    # command = f"gcc \"{c_file}\" -o \"{exec_path}\""

                    # subprocess.call(command, shell=True)
                    compile_result = subprocess.check_output(compile_command, shell=True)
                    compile_result = compile_result.decode('utf-8').strip()

                    if len(compile_result) == 0:
                        print("Code compiles.")
                        # the change
                        c_process = pexpect.spawn(run_command)
                        c_process.interact()
                    else:
                        print("Compilation error.")
                        print(compile_result)
                except subprocess.CalledProcessError:
                    pass

И для перенаправления вывода в файл журнала я использую:

python GradeStudents.py 2>&1 | tee logfile.txt
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...