본문으로 바로가기

데이터의 입력과 출력

category language 2019. 9. 24. 12:06

이번 일요일에 코딩 테스트가 있다.

그런데 사용 가능 언어에 Python이 없어서 Java를 리마인드해볼 겸 생활코딩의 강의를 듣고 있다가 문득 궁금해졌다.

데이터의 입력과 출력은 어떻게 이루어지는 걸까?

모든 언어를 배울 때 가장 처음 해보는 것은 "Hello world"의 출력이다.

(내가 했던 게임에서 프로그래밍...이라고 해야 하나? 그런 것을 모토로 한 보스가 나오는 레이드가 나온 적이 있었는데,

그 보스의 치명적인 기술 이름도 헬로 월드였다.)

 

최근 알고리즘 문제를 풀면서 파이썬의 기본 input()이 아니라

from sys import stdin을 하여

input = stdin.readline이라고 정의한 뒤에 사용하고 있는데 이 stdin은 input과 어떻게 다르고,

왜 이걸 사용해야 하는 것인지에 대한 궁금증이 와르르 쏟아졌다.

 

먼저 stdin, stdout이라고 검색해보았다.

그랬더니 가장 상단에 C의 stdin, stdout에 대해 설명한 블로그 글이 떴다. 간단히 정리하자면

  • stdin : standard input
  • stout : standard output
  • stdin은 대부분 표준 입력에 대한 포인터라고 한다. 내부 구조를 보면 다음과 같음
FILE* __cdecl __acrt_iob_func(unsigned);
#define stdin (__acrt_iob_func(0))
  • 함수를 호출할 뿐, 포인터 변수가 아니다.
  • stdin을 호출하여 얻는 값이 FILE* 형식의 주소 값이기 때문에 표준 입력에 대한 포인터라고 얘기한다.
  • 1->표준 출력, 2->표준 에러
  • 그런데 표준입력장치는 시스템마다 달라질 수 있고 동일한 시스템이라도 설정에 따라 달라질 수 있다.
  • 특정 함수의 이름으로 고정하면 안 된다.
  • 그렇기 때문에 stdin이라는 이름으로 치환하여 사용한다.
  • C언어에서는 데이터를 읽거나 저장할 때 FILE* 를 사용한다. (보통의 표준 입력 장치는 키보드 같은 외부 장치인데...)
  • C언어가 Unix 시스템을 만들면서 설계되었던 것과 관련이 있다.
  • Unix 시스템에서는 외부 I/O 장치를 모두 동일한 형식으로 관리하기 위해서 외부 장치를 파일 시스템처럼 사용할 수 있도록 설계했다.
  • 그래서 정의된 파일을 열어서 데이터를 읽거나 쓰면 해당 장치와 통신이 된다.

파이썬의 기본이 되는 C의 입출력 방식과 그 이유에 대해서는 어느 정도 이해했다.

그런데 내가 궁금한 건 파이썬의 입출력 방식이었다.

* 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() 함수는 변환된 문자열 값을 반환하면서 종료한다.

 

'<'는 리다이렉트. python r.py < l.txt 라고 하면 l.txt를 표준입력으로 python r.py를 실행한다는 것. r.py의 내부에서 input()이 호출될 때마다 라인의 한 줄이 프로그램 속으로 전달된다.

출력 역시 파일로 전환할 수 있다.

python r.py < l.txt > r.txt

r.txt에 출력의 내용이 저장된다.

 

즉, 프로그램의 표준 입력은 실행하는 옵션에 따라서 키보드가 아닌 파일이 될 수 있음을 보았고, 표준 출력 역시 콘솔화면이 아닌 파일이 될 수 있다. 이는 곳 입력장치 > 프로그램 > 출력장치의 연결에서 입력장치와 출력장치 중 하나 혹은 둘 모두를 다른 것으로 전환할 수 있다.

 

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

도 가능하다. 즉 프로그램1의 출력을 다른 프로그램2의 입력으로 바로 연결하는 것!

두 개 프로그램의 출력 -> 입력을 맞붙여 연결하는 것을 '파이핑'이라고 하고 파이프 문자('|')를 사용한다.

 

input() 함수의 도움말은 다음과 같다.

If the user hits EOF, raise EOFError.

입력 중에 파일의 끝을 만나면 EOFError가 발생한다. 표준입력을 통해 들어오는 데이터는 사실 항상 EOF(End of File)로 끝나기 때문에 이 프로그램은 종국에 EOFError를 내게 되어 있다.

그렇지만 에러 메시지는 기본적으로 표준 에러를 통해서 출력되고, 콘솔 환경에서 표준 에러는 화면으로 출력되기 때문에 위의 w.txt에는 에러 내용이 들어가지 않는다.

 

에러 없이 처리하고 싶다면 try~ except문으로 EOFError를 처리해주면 된다.

python u.py < l.txt | python r.py > ur.txt
or
python u.py < l.txt | r.py

파이썬 공식 문서

sys.stdin # 표준입력
sys.stdout # 표준출력
sys.stderr # 표준에러
    File objects used by the interpreter for standard input, output and errors:
    표준 입력, 표준 출력, 표준 에러를 위한 인터프리터에 사용되는 파일 오브젝트들:
        stdin is used for all interactive input (including calls to input());
        stdin은 모든 상호작용하는 입력에 사용된다. (input()으로 인한 call도 포함한다.)
        stdout is used for the output of print() and expression statements and for the prompts of input();
        stdout은 print()로 인한 출력과 표현식으로 인한 출력, 그리고 input()의 prompt에 대한 출력에 사용된다.
        The interpreter's own prompts and its error messages go to stderr.
        인터프리터 그 자신의 prompt와 에러 메시지는 표준 에러로 전송된다.

 

그래서 왜 sys.stdin.readline()이 input()보다 빠른가?

입출력 스트림에 관한 문제 때문에 속도 차이가 나는 것.

파이썬3에서는 input()이 파이썬2의 raw_input()과 같은 것

raw_input()은 문자열을 반환 <- 문자열만을 받는 input

input()은 raw_input()을 evaluate한 결과를 반환

sys.stdin.readline()은 한 줄의 문자열을 반환

자바에서 Scanner보다 Buffered~가 더 처리가 가볍고, C++에서 cout보다 printf를 이용하는 것이 시간적인 측면에서 효율적인 것과 같음.

 

즉 캐릭터 단위로 읽어들이는 것 (input(), Scanner()) 이기 때문에

한번에 읽어와 버퍼에 보관하고 사용자가 요구할 때 버퍼에서 읽어오게 하는 것 (sys.stdin.readline(), BufferedReader())이 더 빠르다.

버퍼 사이즈 차이.

입력이 반복될수록 BufferedReader(), sys.stdin.readline()이 우위를 가진다.