Python – 데이터를 입력받는 방법에 대해

데이터를 입력받는 것에 대하여

컴퓨터 프로그램은 함수와 같다. 프로그램은 입력장치로부터 데이터를 읽어들이고, 이 소스데이터를 가공하여 결과를 만들고, 이를 출력한다. 따라서 프로그램의 외부에서 그것이 실행되는 과정을 보는 것은 일종의 쥬스메이커와 같다고 볼 수 있다. 과일(입력될 데이터)을 투입구에 밀어넣고 동작 버튼을 누르면 기계속으로 들어간 과일이 잘리고 눌려서 과즙이 되고, 그것이 노즐을 통해서 퐁퐁 흘러나와 컵에 담기는 것과 동일하다.

파이썬에서 프로그램이 사용자로부터 입력을 받는 방법 중에 가장 대표적인 것은 input() 함수이다. 이 함수는 키보드로부터 한 줄의 문자열을 입력받아, 그 내용을 리턴한다는 동작을 수행한다. 예를 들면 다음과 같이 쓸 수 있다.

name = input('what is your name?: ')
print('hello, %s' % name)

그런데 왜 이 함수의 이름이 keyboard 따위가 아니라 input일까? 이 함수의 동작의 의미를 보다 엄밀하게 따져보도록 하자. 실제로 이 함수의 뜻은 다음과 같다.


input(prompt=None, /)
  Read a string from standard input. The trailing newline is stripped.

먼저 “키보드”가 아니라 표준입력으로부터 문자열을 입력받는다. 그리고 문자열 끝의 개행은 제거된다는 이야기다. 실제로 동작은 다음과 같이 일어난다.

  1. input()이 호출되면, 인자로 주어진 프롬프트 문자열을 화면에 출력하고, 프로그램은 사용자의 입력을 기다린다.
  2. 콘솔 환경의 표준입력은 다름 아닌 키보드이다.
  3. 사용자가 키를 하나씩 누르기 시작하면 버퍼에 눌려진 키에 대응하는 데이터가 들어간다.
  4. enter키를 누르면 개행문자(newline, \n)이 입력된다. 개행문자를 받게되면 버퍼의 입력이 종료된것으로 간주한다.
  5. 입력된 문자열은 해당 시스템의 콘솔 입출력 인코딩을 사용하여 디코드되어 유니코드 문자열로 변환된다.
  6. input() 함수는 변환된 문자열 값을 반환하면서 종료한다.

표준입력

표준 입력 그리고 표준 출력의 개념은 당장은 약간 이해하기 힘들 수 있다. 우선 표준입력에 대한 위키백과의 내용을 인용하겠다.

유닉스 이전의 대부분의 운영 체제에서, 프로그램은 명시적으로 적절한 입력장치와 출력장치에 연결해 줄 필요가 있었다. 이 작업은 OS마다 처리 방식이 달랐기 때문에 매우 방대한 작업이었다.

 

수많은 시스템에서 환경 설정을 제어하거나, 파일 테이블에 접근하거나, 필요한 데이터 셋을 결정하기 위해 펀치 카드 리더기나 자기 테이프 드라이브, 라인 프린터, 카드 펀치, 대화식 터미널을 적절하게 제어할 필요가 있었다.

이런 상황에서, 유닉스의 획기적인 발전 중 하나는 장치의 추상화였다.

프로그램은 더 이상 어떤 장치와 연결되는지 알 필요가 없었다.

 

유닉스는 기존의 복잡성을 데이터 스트림이라는 개념으로 해소시켰다. 데이터 스트림은 순차적인 데이터 바이트들을 파일의 끝(EOF)까지 읽는다. 이런 방식으로, 프로그램은 쓸 데이터가 얼만큼 남았는지, 혹은 어떤 식으로 묶여있는지 알 필요 없이 필요한 데이터를 쓸 수 있었다.

 

또 다른 유닉스의 획기적인 발전은, 자동으로 연결되는 입출력 장치였다. 프로그래머나 프로그램에서 입출력을 사용해야 할 때, 입출력 장치를 연결하기 위한 그 어떤 추가 작업도 필요하지 않게 되었다. 이전의 운영 체제들에서 입출력 장치를 연결하기 위해 복잡한 작업 제어 언어가 필요하거나, 그와 동일한 역할을 하는 프로그램이 필요했던 것과는 대조적이다.

간단히 말해서 표준입력입력 장치를 추상화한 것이라 할 수 있다. 추상화라는 말이 생소하거나 어렵게 느껴질 수 있는데, “구체적으로 결정하다”의 반대말로 생각해본다면 “그게 뭔지 상관하지 않는다”라고 해석할 수 있다. 말 그대로 입력장치가 어떻게 생겼고, 어떤 기술적인 원리로 동작하든 간에 상관없이 “입력장치”라 불리는 모든 컴퓨터의 주변 기기는 컴퓨터 외부로부터 어떤 데이터를 받아서 그것을 컴퓨터 내부로 넘겨주는 역할을 한다. 따라서 “컴퓨터 내부로 데이터를 넘겨주는” 방법만 한 가지로 약속해놓는다면, 그 장치가 무엇이든 간에 프로그램 외부로부터 어떤 데이터들을 프로그램 속으로 전달해 줄 수 있다는 말이다.

그리고 이때 표준입력은 데이터가 흘러들어올 수도꼭지 같은 것이다. 그 너머에 연결된 입력 장치가 무엇이든 간에, 프로그램은 이 수도꼭지에서 물이 콸콸 흘러나오기를 기다렸다가 그 데이터를 이용할 수 있는 것이다. 표준입력의 반대편에 연결될 수 있는 장치에는 다음과 같은 것들이 있을 수 있다.

  1. 키보드 – 엔터키를 누를 때까지 입력된 글자들을 모아서 문자열로 보낼 수 있다.
  2. 파일 – 파일의 내용을 프로그램에서 읽을 수 있다.
  3. 네트워크 스트림 – 네트워크를 통해서 다른 곳에서부터 전송된 데이터를 받는다.
  4. 바코드 리더 – 바코드리더기가 읽어낸 숫자들을 프로그램이 받을 수 있다. 물론 일반 가정에서 바코드리더를 PC에 연결해 쓸 일은 없겠지만….
  5. 다른 프로그램의 출력 – 파이프(‘|‘)를 사용하여 다른 프로그램의 출력을 현재 프로그램의 표준 입력에 연결할 수 있다.

표준 입력과 마찬가지로 프로그램의 출력 역시, “표준 출력”을 통해서 흘러나간다. 콘솔 환경에서 표준 출력은 콘솔 창으로 연결되어 있어서 print() 함수를 사용해서 문자열을 출력하면 표준출력으로 문자열이 흘러나가서 화면에 출력되는 것이다.

예제

간단한 예제를 만들어보자. 어떤 문자열을 입력받아, 그것을 역순으로 출력하는 프로그램을 작성해보겠다. 파일이름은 r.py로 저장한다.


def reversedString(s):
    return s[::-1]

def main():
    n = '0'
    while len(n.strip()) > 0:
        n = input()
        print(reversedString(n))

if __name__ == '__main__':
    main()

키보드로 입력받은 내용을 한 줄 씩 거꾸로 출력하기

이 프로그램을 실행해보면 (> python r.py ) 프롬프트가 깜빡거리는데, 이 때마다 문자열을 입력하면 그것을 역순으로 뒤집은 결과가 출력된다. 빈 칸을 입력하면 프로그램이 종료될 것이다. (빈 줄을 입력하면 종료된다)


apple
elppa
hello
olleh


이렇게 실행된 상황에서는 프로그램이 실행되고, 표준입력으로부터 한줄의 텍스트를 입력받고 다시 이를 뒤집어서 출력하기를 빈줄이 입력될 때까지 반복하는 식으로 동작한다.

파일의 내용을 뒤집기

이 때 다음과 같은 내용의 텍스트 파일 l.txt 가 있다고 하자. 이 파일의 각 라인을 거꾸로 출력하고 싶다. 어떻게 하면 좋을까?

Hello, my name is apple.
I have two friends of mine.
Oragne is good.
Banana is tall.
# 빈줄을 한 줄 남겨준다.

문자열을 뒤집는 함수를 위의 r.py에서 준비했으니, open 함수를 이용해서 파일을 열어서 각 라인을 읽고 이를 거꾸로 출력하는 코드를 작성할 수도 있을 것이다. 하지만, “파일” 역시 표준 입력의 한쪽에 끼워질 수 있다고 했다. 따라서 다음과 같이 “리다이렉트”를 이용해서 처리할 수 있다.

### 윈도 명령 프롬프트에서 실행
### 명령 프롬프트는 원래  > 로 표시되지만, 
### 리다이렉트 기호와의 구분을 위해 $로 표기합니다.
$ type l.txt
Hello, my name is apple.
I have two friends of mine.
Oragne is good.
Banana is tall.

$ python r.py < l.txt
.elppa si eman ym ,olleH
.enim fo sdneirf owt evah I
.doog si egnarO
.llat si ananaB

위 예에서 < 는 리다이렉트를 말하는데, l.txt를 표준입력으로 python r.py를 실행한다는 것이다. 그러면 r.py의 내부에서 input()이 호출될 때 마다 라인의 한 줄이 프로그램 속으로 전달된다. 이는 텍스트 파일이 키보드를 누른 데이터를 기록하고 있는 것과 같이 동작하는 것처럼 보인다.

출력 역시 파일로 전환할 수 있다. 다음과 같이 실행해보자.

$ python r.py < l.txt > r.txt
### 아무것도 표시되지 않음
$ type r.txt
.elppa si eman ym ,olleH
.enim fo sdneirf owt evah I
.doog si egnarO
.llat si ananaB

> r.txtr.py의 출력을 표준 출력(콘솔 화면)이 아닌 r.txt라는 파일로 리다이렉트한다는 뜻이다. 따라서 파이썬 스크립트를 실행하는 동안에는 화면에 아무것도 표시되지 않는다. 하지만 실행이 끝나고 나면 실제로 r.txt라는 파일이 만들어져 있고, 그 속에 실제 프로그램의 출력 내용이 들어가있다.

더 깊이 파고들기 – 파이프

데이터를 입력받는 방법에 대해 이해하는 것은 단순히 input함수를 사용하여 키보드로부터 입력을 전달받는다는 것을 넘어 프로그램이 입력을 받을 수 있는 창구를 확장한다는데 있다. 앞서 살펴본 간단한 예에서 우리는 프로그램의 표준 입력은 실행하는 옵션에 따라서 키보드가 아닌 파일이 될 수 있음을 보았고, 표준 출력 역시 콘솔화면이 아닌 파일이 될 수 있음을 보았다. 이는 곧 입력장치 -> 프로그램 -> 출력장치의 연결에서 입력장치와 출력장치 중 하나 혹은 둘 모두를 다른 것으로 전환할 수 있다는 것을 의미한다.

그렇다면 다음과 같은 도식은 어떨까?

FileA -> 프로그램1 -> 프로그램2 -> FileB

즉 프로그램1의 출력을 다른 프로그램2의 입력으로 바로 연결하는 것이다. 이것은 데이터가 흐르는 2단 싱크대가 있고, 2개의 싱크대를 파이프를 사용해서 직렬로(?) 연결한 것과 유사하다. 실제로 이렇게 두 개 프로그램의 출력->입력을 맞붙여 연결하는 것을 ‘파이핑’이라 하고 파이프 문자 (|, 키보드에서 원화표시의 위에 있는 글자)를 사용한다.

파이프를 실제로 체험해보기 전에 먼저 만들었던 r.py를 조금 손볼 필요가 있다. 다시 input() 함수의 도움말 내용을 살펴보자.

If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.

입력 중에 파일의 끝을 만나면 EOFError가 발생한다는 것이다. 표준입력을 들어오는 데이터는 사실 항상 EOF로 끝나기 때문에 이 프로그램은 종국에 EOFError을 내게 되어 있다. 방금 생성한 r.txt를 돌려보면 여기에는 빈줄 없이 EOF를 만나기 때문에 에러가 발생할 것이다.

그럼에도 불구하고 r.py < r.txt > w.txt 와 같은 식으로 실행한 경우에 결과 파일은 정상적으로 생성되고 에러 내용은 w.txt에 들어가지 않는다. 왜냐하면 에러 메시지는 기본적으로 표준입력, 표준출력외 제 3의 기본 입출력인 “표준에러”를 통해서 출력되기 때문이다. (그리고 콘솔환경에서 표준에러는 화면으로 출력된다.)

따라서 r.pymain 함수 부분을 다음과 같이 바꾼다.

def main():
    try:
        while True:
            n = input()
            print(reversedString(n))
    except EOFError:
        pass
    

이제 r.txt 파일을 r.py로 처리해보라. 에러 없이 처리되는 것을 볼 수 있다. 이제 두 번째 프로그램을 만들어보자. u.py 를 파일이름으로 하고 다음과 같이 쓴다. 기본적으로 입력된 내용을 받아서 대문자로 만들어서 출력하기 때문에 r.py와 매우 비슷한 모양이 될 것이다.

def main():
    try:
        while True:
            n = input()
            print(n.upper())
    except EOFError:
        pass
 
if __name__ == '__main__':
    main()

그리고 실행해본다.


$ u.py < l.txt
HELLO, MY NAME IS APPLE.
I HAVE TWO FRIENDS OF MINE.
ORANGE IS GOOD.
BANANA IS TALL.

그럼 다음과 같이 실행해보자.

$ python u.py < l.txt | r.py
.ELPPA SI EMAN YM ,OLLEH
.ENIM FO SDNEIRF OWT EVAH I
.DOOG SI EGNARO
.LLAT SI ANANAB

## 혹은

$ python u.py < l.txt | python r.py > ur.txt
$ type ur.txt
.ELPPA SI EMAN YM ,OLLEH
.ENIM FO SDNEIRF OWT EVAH I
.DOOG SI EGNARO
.LLAT SI ANANAB


파이프에 연결할 수 있는 명령어는 이렇게 따로 만든 프로그램이 아니어도 된다. 대부분의 내장/기본외장 쉘 명령은 다 사용할 수 있다. 이를 테면 dir 같은 명령도 가능하다.

$ dir | python r.py
stnemucoD :륨볼 의브이라드 E
CF27-AF8E :호번 련일 륨볼

리터렉디 pmet\:E

.          >RID<    55:90 전오  82-70-7102
..          >RID<    55:90 전오  82-70-7102
sevihcrA          >RID<    11:11 전오  81-70-7102
.
.
.
txt.l 39                72:90 전오  82-70-7102
yp.r 712               05:90 전오  82-70-7102
txt.r 98                03:90 전오  82-70-7102
yp.u 771               45:90 전오  82-70-7102
txt.ru 39                55:90 전오  82-70-7102
트이바 697,2               일파 개21
음남 트이바 635,104,623,92  리터렉디 개61

사실 프로그램 내에서도 이렇게 어떤 함수의 입력과 출력을 연결하는 일은 빈번하다. 우리가 만든 r.py를 보면 다음과 같은 코드가 있다.

n = input()  ## n 은 input() 함수의 출력이다. 
print(
      reversedString(n)  ## reversedString() 함수의 입력은 input()의 출력이며,
      ## reversedString()의 출력은 다시 print의 입력으로 넣는다.
)

쉘의 관점에서 프로그램 역시 하나의 함수로 볼 수 있으며, 각각의 입력과 출력을 연결하여 처리하는 것은 사실 CLI에 익숙하지 않은 사람에게는 생소하겠지만, 쉘의 많은 내장명령과 프로그램들은 이런 방식으로 동작하는 것을 전제로 만들어져 있다.

조금 더 깊이 파고들기

프로그램에 제공되는 기본 입출력은 3가지로 표준입력, 표준출력, 표준에러이며 (이들은 각각 0, 1, 2의 숫자로도 대표된다.) 파이썬에서는 input(), print()를 이용해서 표준입력 및 표준 출력과 상호작용한다. 이들은 sys 모듈의 stdin, stdout, stderr로도 대표되며 각각은 스트림을 래핑한 객체이다.

보다 자세한 내용은 파이썬 표준 라이브러리 문서를 참고하도록 하자. (링크 : https://docs.python.org/3/library/sys.html#sys.stdin)

유닉스/리눅스 쉘에서는 3개의 표준 입출력 외의 추가 입출력 디스크립터를 생성하고 사용할 수 있다. 이는 프로세스간에 데이터를 주고받는 쉬운 방법이기도 하다. 해당 내용을 다룬 튜토리얼 페이지를 참고하자.

  1. 입력을 위한 파일 디스크립터 할당
  2. 출력을 위한 파일 디스크립터 할당
  3. 디스크립터 닫기
  4. 입/출력을 위한 파일 디스크립터 할당