2017. 7. 18. 13:29

python input시 timeout 지원하기 (python input timeout)

python script로 자동화된 프로세스를 만들때, 이때는 시간이 긴 작업이 동반될 수 있습니다. 만일, 사용자 input을 받아야 한다면, 이러한 긴 작업에 대해 모니터링을 해야 하는 단점이 있습니다.


python의 사용자 입력 함수인 input이 사용되긴한데, 해당 함수는 timeout이 지원되지 않아, 무한 대기하게 됩니다. 이러한 단점을 방지하기 위해, input의 timeout 속성을 넣는 시도가 많이 있는데, 모두 매우 어렵게 구현되어 있습니다. 즉, windows/linux OS의 특징을 이용하여, 통합 코드로 진행되지 못한다는 점입니다.


아래 함수, 즉, python 함수를 공유하며, 이는 input에 대한 timeout을 지원합니다.

현재 python3 대상으로 개발되었으며, 필요시 python2로 수정할 수 있을 겁니다.


중요한 논리는,

직접 input()을 호출하지 않고,

python -c "input()"

프로세스를 새로 생성한뒤, timeout으로 wait하는 방식입니다.

그래서 논리가 매우 단순합니다.


def input_timer(prompt, timeout_sec):
    import subprocess
    import sys
    import threading
    import locale

    class Local:
        # check if timeout occured
        _timeout_occured = False

        def on_timeout(self, process):
            self._timeout_occured = True
            process.kill()
            # clear stdin buffer (for linux)
            # when some keys hit and timeout occured before enter key press,
            # that input text passed to next input().
            # remove stdin buffer.
            try:
                import termios
                termios.tcflush(sys.stdin, termios.TCIFLUSH)
            except ImportError:
                # windows, just exit
                pass

        def input_timer_main(self, prompt_in, timeout_sec_in):
            # print with no new line
            print(prompt_in, end="")

            # print prompt_in immediately
            sys.stdout.flush()

            # new python input process create.
            # and print it for pass stdout
            cmd = [sys.executable, '-c', 'print(input())']
            with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
                timer_proc = threading.Timer(timeout_sec_in, self.on_timeout, [proc])
                try:
                    # timer set
                    timer_proc.start()
                    stdout, stderr = proc.communicate()

                    # get stdout and trim new line character
                    result = stdout.decode(locale.getpreferredencoding()).strip("\r\n")
                finally:
                    # timeout clear
                    timer_proc.cancel()

            # timeout check
            if self._timeout_occured is True:
                # move the cursor to next line
                print("")
                raise TimeoutError
            return result

    t = Local()
    return t.input_timer_main(prompt, timeout_sec)

# example code here...
try:
    a = input_timer("* prompt >>> ", 3)
    print(a)
except TimeoutError as e:
    print("timeout...")
    pass

a = input_timer("* prompt >>> ", 3)
print(a)

print("done")
 

위와 같이 input_timer("...", 초)를 호출하면 되며,

timeout 발생시 TimeoutError exception이 발생하니,

해당 부분을 try 하시면 됩니다.