[Python] Popen 클래스
Subprocess 모듈 이란?
subprocess 모듈은 파이썬 프로그램 내에서 새로운 프로세스를 스폰하고 여기에 입출력 파이프를 연결하며 리턴코드를 획득할 수 있도록 하는 모듈로, 다른 언어로 만들어진 프로그램을 통합, 제어할 수 있게 만드는 모듈이다.
사용방법
새로운 서브 프로세스를 만들기 위해 권장되는 방법은 다음의 편의 함수들을 사용하는 것이다. 보다 세부적인 제어를 위해서는 Popen 인터페이스를 직접 사용한다.
Popen 클래스
프로세스 생성과 관리 모듈은 내부적으로 Popen 클래스를 통해서 관리한다. 실질적으로 call-*
류의 함수들은 이 클래스를 기반으로 하고 있다. Popen은 많은 유연성을 제공하기 때문에 기본적으로 제공되는 편의 함수들만으로 다룰 수 없는 케이스를 커버할 때, 세밀한 옵션들에 대해서 직접 제어하고자 할 때 사용할 수 있다.
Popen 생성하기
class subprocess.Popen(args, bufsize=-1, excutable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=None, shell=False,
cwd=None, env=None, universal_newlines=False,
startupinfo=None, creationflags=0, restoreflags=0,
restore_signals=True, start_new_session=False, pass_fds=()
)
args
는 명령행에 문자열 혹은 입력될 인자들을 공백으로 자른 리스트이다.
stdin, stdout, stderr
은 각각 표준입력, 표준출력, 표준에러의 표준입출력 리다이렉션에 대응한다. 파일디스크립터를 사용해서 파일의 내용을 표준입출력으로 대체할 수 있다. 보통은 생략되면 현재의 표준입출력이 그대로 적용된다.
shell=True
가 되면 서브 프로세스로 주어진 명령을 바로 실행하는 것이 아니라, 별도의 서브 쉘을 실행하고 해당 쉘 위에서 명령을 실행하도록 한다. 이 값이 True
가 되는 경우에는 args
가 리스트 보다는 하나의 문자열인 쪽이 좋다고 한다. 참고로 쉘을 사용하면 쉘이 제공하는 파이프, 리다이렉션과 쉘의 문자열 확장 (예를 들어 FILES*
라고 쓰면 FILES1 FILES2 FILES3
… 이렇게 만들어지는 것)을 함께 사용할 수 있게 된다.
기본적으로 이 함수는 서브프로세스가 종료될 때까지 기다리는데 timeout 값을 설정하면 주어진 시간(초단위)만큼 대기한 후, 대기 시간을 초과하면 TimeoutExpired
예외를 일으키게 된다.
Popen 객체의 메소드들
다음 메소드들은 Popen 생성자를 이용해서 만든 객체가 제공하는 메소드들이다.
.poll()
: 자식 프로세스가 종료되었는지를 확인한다.
.wait(timeout=None)
: 자식 프로세스가 종료되길 기다린다. 자식 프로세스의 리턴 코드를 돌려준다.
.communicate(input=None, timeout=None)
: 자식 프로세스의 표준 입력으로 데이터를 보낸다음, 표준 출력의 EOF르 만날 때까지 이를 읽어온다. (프로세스가 끝날 때까지 기다린다.) 이 함수는 (stdout_data, stderr_data)의 튜플을 리턴한다. 이 함수를 사용하려면 stdin=PIPE 옵션으로 자식 프로세스를 시작해야 한다.
Popen.send_signal(signal)
: 자식 프로세스에 시그널을 보낸다.
Popen.terminate()
: 자식 프로세스에 종료 시그널을 보낸다.
Popen.kill()
: 자식 프로세스를 강제로 죽인다.
Popen.args
: 주어진 args 를 리턴한다.
Popen.stdin
: 만약 PIPE라면 이 객체는 읽고 쓸 수 있는 스트림 객체이다.
Popen.stdout
: PIPE라면 읽을 수 있는 객체이다.
Popen.stderr
: PIPE라면 읽을 수 있는 객체이다.
Popen.pid
: 자식 프로세스의 pid값
Popen.returncode
: 자식 프로세스가 종료되었을 때 리턴코드 값을 가진다.
Quick Demo
subprocess로 자식 프로세스 실행하는 방법은 간단하다. 다음 코드에서는Popen
생성자가 프로세스를 시작한다. communicate
메서드는 자식 프로세스의 출력을 읽어오고 자식 프로세스가 종료할 때까지 대기한다.
import subprocess
proc = subprocess.Popen(
['echo', 'Hello world'],
stdout=subprocess.PIPE
)
out, err = proc.communicate()
print(out)
print(out.decode('utf-8'))
# 결과
# b'Hello world\n'
# Hello world
Poepn.communicate
Popen(input=None, timeout=None)
Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be data to be sent to the child process, or None
, if no data should be sent to the child. If streams were opened in text mode, input must be a string. Otherwise, it must be bytes.
communicate()
returns a tuple (stdout_data, stderr_data)
. The data will be strings if streams were opened in text mode; otherwise, bytes.
Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE
. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE
and/or stderr=PIPE
too.
If the process does not terminate after timeout seconds, a TimeoutExpired
exception will be raised. Catching this exception and retrying communication will not lose any output.
The child process is not killed if the timeout expires, so in order to cleanup properly a well-behaved application should kill the child process and finish communication:
proc = subprocess.Popen(...)
try:
outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
proc.kill()
outs, errs = proc.communicate()