# 파일 읽기
이번 장에서는 다음과 같은 작업을 코드로 작성하겠다.
- 기상관측자료 (90.txt) 읽어오기.
- 변수에 값 할당하기
format
이용해서 읽어온 값 추려서 출력하기.
# 자료 준비
예제로 사용할 데이터는 아래 링크를 통해 다운받을 수 있다. 깃북 특성상 클릭으로는 다운로드가 잘 안되는 것 같다. 아래 링크에 마우스 오른쪽 버튼을 클릭해서 "다른 이름으로 링크 저장"을 누르면 파일을 다운받을 수 있다.
파일명과 디렉토리 구조를 정하는 부분은 프로그래머가 느끼는 가장 어려운 부분 중 하나인데, 다운 받은 파일은 data
디렉토리를 만들어서 넣고, 파이썬 코드는 python101
이라는 폴더를 만들어서 read_file.py
라는 이름으로 저장한 것으로 가정하겠다.
편의상 예시를 이렇게 들었을 뿐, 파일명이나 디렉토리명, 혹은 파일 위치 등은 각자가 마음대로 정할 수 있다.
PyCharm에서 빈 프로젝트를 하나 만들었고, 그 안에는 data
와 python101
이라는 2개의 폴더가 있게 된다. data
폴더에는 방금 내려받은 90.txt
파일을 넣어두었고, python101
폴더에는 read_file.py
라는 이름으로 본 장에서 작성할 파이썬 코드를 만들었다. 이제 read_file.py
라는 코드를 작성해 보자.
# 변수
변수는 프로그램에서 값을 저장해두기 위해서 사용자가 지정해놓은 특정한 이름들을 이야기한다.
엑셀을 생각해보면 쉬운데, 엑셀을 이용하여 계산을 할 때 우리는 셀참조를 이용한다. 변의 길이가 5
인 사각형의 면적을 구하기 위해서 =5*5
라고 셀 수식을 쓸 수도 있지만, C1
셀에 5
를 쓰고, =C1*C1
이라고 셀 참조를 할 수도 있다. 셀 참조를 이용하면, 다른 길이의 사각형의 면적을 쉽게 구할 수 있다. C1
셀에 넣은 숫자 5
대신 다른 값을 써주기만 하면된다. 계산식이 복잡해지면 셀참조를 쓰는것과 수식에 직접 숫자를 입력하는 것 차이의 활용성은 극명하게 갈리게 된다. 여기서 셀 이름이 "변수"이다.
# 파일 열고, 읽기
파이썬에서 파일을 읽는 과정은 아래 코드와 같이 작성할 수 있다.
filename = "../data/90.txt"
with open(filename, "r") as f:
for line in f:
print(line)
filename
이라는 변수에 입력 파일의 위치를 지정하고, with
로 시작하는 문장에서 파일을 열어준다. 파일을 열면, 디스크에 저장된 파일을 메모리 공간으로 옮겨올 준비를 하게 된다. 파일을 읽거나 쓰는 작업이 끝나면, 더 이상 해당 파일을 쓰지 않음을 알려주고, 메모리와 디스크 사이에 연결된 고리를 끊어주어야 한다. 보통 close()
라는 이름의 함수가 그런 역할을 하는데, 파이썬에서는 with
키워드를 이용해서 위와 같이 코드를 작성하면 with
블록이 끝나는 시점에 자동으로 close()
를 호출하여, 파일을 닫아준다.
파일을 열어서 f
라는 변수에 할당해주었고, for
로 시작하는 문장에서 파일을 한줄씩 읽어서 line
이라는 변수에 저장해주게 된다. 파이썬에는 이러한 과정이 매우 간단히 기술할 수 있는데, c 언어에서는 이 과정이 다소 복잡하다. 교수님 코드가 긴 이유이다.
파일을 제대로 읽는지 확인하기 위하여 print
로 시작하는 줄에서 각 줄을 출력해본다. 실행해보면, 아래에 읽어온 값이 출력됨을 확인할 수 있을 것이다.
여기서, 다시 한번 강조하자면, 들여쓰기가 매우 중요하다. 들여쓰기 된 부분이 같은 블록으로 묶이게 된다. 다른 대부분의 프로그램 언어에서 대괄호를 이용하여 블록을 구분하는데, 파이썬은 들여쓰기로 이를 대체 한다.
그리고 앞으로도 계속되겠지만, 제시한 코드를 절대 카피해서 붙이지 말라. 눈으로 코드를 읽고, 외운 다음, 직접 타이핑을 해봐야한다. 그래야 빨리 는다. 컴퓨터는 적당히 해서 알아듣는 법이 없다. 굉장히 엄격하게 문법을 준수하기 때문에, 사소한 오타 한 글자 때문에 프로그램이 안 돌아가기도 한다. 그러니 꼭 직접 타이핑해서 연습하도록 하자.
# 더 읽기: 파일 경로
파일 입출력 과정에서 가장 흔한 실수 중 하나는 파일 경로이다. 다시 말해, 파일의 위치를 프로그램에게 알려주어야 하는데, 이 부분에서 실수하기 쉽다. 위 코드에서 filename
이라는 변수에 90.txt
파일 위치를 알려준다. .
는 현재 폴더, ..
는 상위 폴더를 의미하는데, 현재 코드는 python101
디렉토리에 있고, 90.txt
파일은 data
폴더에 있다. PyCharm에서 코드를 실행하면, 해당 코드의 위치를 기준으로 프로그램이 실행되므로 해당 위치를 이용하여 입력파일 위치를 적절히 넣어주어야 한다.
앞서와 같이 경로를 지정하는 방식을 상대 경로(relative path)라고 한다. 상대 경로는 디렉토리가 많아지고, 위치가 복잡해지면 그 상하관계를 일일이 따라가기가 쉽지 않다는 단점이 있다. 예를 들어 3단계 상위 폴더로 이동한 후, 4단계 폴더에 접근하려고 하면, ../../../abc/sub/data/ver1/input.txt
와 같이 작성해야 한다.
다른 방식으로는 절대 경로(absolute path)라고 해서, c:/code/python101/data/90.txt
와 같이 전체 경로를 적어주는 방식이 있다. 이 방식은 파일 경로를 직관적으로 알 수 있고, 프로그램이 어느위치에서 실행되든 해당 파일의 위치를 정확히 찾을 수 있으므로 오해의 소지가 적다. 하지만 프로그램을 작성할 때, 단순히 절대경로 전체를 코드에서 쓰지는 않는 편이다. 그 이유는 해당 코드를 다른 컴퓨터에 전달하였을 때, 경로를 수정해주어야 하기 때문이다. 사용자마다 동일한 경로로 파일을 저장한다고 가정하기엔 무리가 있기 때문이다.
그래서 두 개념을 조합하여, 기준 경로를 절대경로로 지정하고, 해당 기준 경로를 중심으로 상대경로를 지정하는 방식이 널리 이용된다. 아래와 같이 작성할 수 있다. 하지만 예제에서는 편의상 상대 경로를 계속 쓰겠다.
BASE_DIR = "c:/code/python101/"
filename = os.path.join(BASE_DIR, "data/90.txt") # BASE_DIR + "data/90.txt"라고 쓸 수도 있다.
# 변수에 할당하고 출력하기
읽은 자료를 처리하기 위해서는, 값을 변수에 저장해야 한다. 이 과정에서 적절한 형태의 자료형태로 바꾸어준다.
filename = "../data/90.txt"
with open(filename, "r") as f:
for line in f:
tokens = line.split()
year = int(tokens[0])
tavg = float(tokens[4])
print("Year: {:d}, Temp_Avg: {:.1f}".format(year, tavg))
앞서 예제에서 확인을 위해서 써두었던 print(line)
코드를 지우고, 위 코드처럼 몇 줄 더 추가하였다. 첫번째는 90.txt
파일을 열어보면, 한줄에 여러개의 숫자들이 있는데, 이 숫자를 하나씩 나누어주어야 한다. 엑셀에서 해당 파일을 카피해서 붙여보면, 잘 나누어질 때도 있지만, 한칸에 한줄 내용이 다 들어가 있기도 한다. 그러면 데이터 > 텍스트 나누기
메뉴를 통해 나누어 준다. 이 같은 작업이 line.split()
이라는 명령이다.
이렇게 나누어진 값들은 tokens
라는 변수에 저장이 된다. print(tokens)
라고 써서 해당 내용을 출력해보면, 어떻게 나누어지는지 이해할 수 있을 것이다. 이렇게 나누더라도 각 값은 여전히 문자열이다. 예를 들어, '2016'
이라고 표시될텐데, 이는 문자열이지 숫자가 아니다. 그래서 숫자로 바꾸어 주기 위하여 그 다음 2줄을 작성하였다. 다른 변수가 더 필요하면, 추가하면된다. token[0]
과 같이 숫자는 위치를 나타내는데, 첫번째 항목이 0부터 시작하는 점만 주의하자. 대부분의 언어는 0부터 세기 시작하지만, 포트란과 비주얼베이직은 1부터 센다. 비주얼 베이직은 기본적으로 0부터 세긴하지만, 엑셀 VBA나 많은 예제에서 1부터 센다. 1부터 세는 언어는 다음 링크 (opens new window)를 참고하자. 기본적으로 프로그래밍 언어는 0부터 시작한다고 생각하면 된다.
마지막으로 출력 부분은 %
를 이용하여 c언어의 printf
와 유사하게 작성한 예제를 많이 보았을텐데, 앞장에서도 제시하였지만, %
연산자보다는 format
메서드를 추천한다. 다시 한번 "PyFormat: Using % and .format() for great good! (opens new window)" 페이지를 참고하자.
여기서는 평균온도만 출력하지만, 강수량이나 습도처럼 다른 기상자료로 출력해보길 바란다.
# 마지막 줄에 빈칸이 포함된 경우
텍스트 파일을 처리하다 보면, 심심찮게 마지막 줄에 빈줄이 포함되어 있기 마련이다. 대부분 경우에는 마지막에 빈줄이 있든 없든 큰 상관이 없는데, 우리가 앞서 작성한 코드에서는 에러가 발생한다. 프로그램은 정확히 지시한 방식대로 동작하기 때문에 발생하는 것인데, 빈 줄이 포함되었을 경우 해당 줄을 처리하지 않고 넘기면 간단히 해결할 수 있다. 이를 반영한 코드는 다음과 같다.
filename = "../data/90.txt"
with open(filename, "r") as f:
for line in f:
if not line.split():
continue
tokens = line.split()
year = int(tokens[0])
tavg = float(tokens[4])
print("Year: {:d}, Temp_Avg: {:.1f}".format(year, tavg))
# 더 읽기: 자료형
자료형은 크게 문자열, 숫자, 날짜로 나눌 수 있다. 우리 분야에서 다루는 파일은 대부분 일반텍스트 파일이기 때문에 대부분 문자열로 저장할 수 있다. 하지만 수식 계산을 위해서는 이 문자열을 숫자로 바꾸어주어야 하며, 숫자는 크게 정수와 실수로 나누어진다. 이는 컴퓨터가 0, 1로 구성되는 이진수를 바탕으로 자료를 저장하도록 설계되었고, 제한된 메모리 공간 내에서 효율적으로 계산하기 위해서 정수와 실수로 구분하게 되었다.
정수형은 int()
라는 함수로, 실수형은 float()
이라는 함수를 이용하여 변환할 수 있다. float
이라는 이름을 쓰는 이유는 부동소수점, 즉 떠 다니는 소수점이라는 개념이다. 자세한 건 부동소수점으로 검색해보기 바란다. 그리고 double
이라는 키워드도 많이 쓰는데, 이건 배정도 부동소수점, 즉 2배 정밀하단 뜻이다. 그래서 double
이다. 2배 정밀하단 의미도 약간 오해의 소지가 있지만, 자세한 설명은 생략하겠다.
# 깃허브 활용하기
깃허브는 프로그래머 혹은 프로그램을 작성하는 모든 이에게 유용한 플랫폼이다. 몇 번을 강조해도 지나치지 않을정도로 훌륭한 서비스이다. 깃허브를 익히고 나면, 쓰는 방법이 어렵지 않으나, 진입장벽이 높은 것 역시 사실이다. 주변에 도와주는 사람없이 써보려고 하면, 막막하다는 것을 이해한다. 하지만 한번 익숙해지고 나면, 어렵지 않게 사용할 수 있는 것 역시 사실이다.
오늘은 깃헙을 바로 배우라고는 하지않겠다. 대신 훨씬 쉽게 쓸 수 있는 서비스를 소개하고자 한다. 깃허브의 또다른 서비스로 gist라는 걸 제공한다. 당연히 github.com 계정이 있어야 한다.
굉장히 쉽다. 그냥 코드를 갖다 붙이면 된다. 제일 위에는 해당 gist의 설명을 적으면 되고, 하나의 gist에는 여러개의 파일을 포함할 수 있다. 처음 커서가 가 있는 곳에는 파일명을 쓰면 된다. 그리고 아래에 코드를 붙이면 된다. 그리고 이걸로 질문을 하면 된다. 페이스북의 "생활코딩"이라는 그룹이 현재 가장 질문하기 좋은 곳이라고 생각된다. 네이버는 버려라. 영어가 익숙하면 구글에서 검색하면 되고, 대부분의 답변은 StackOverflow로 귀결될 것이다.
Asana에 질문을 남길 때도, Gist를 이용해서 남기면, 수정하기가 수월하다. 사진을 찍어서 올리는 경우 읽을 수는 있으나, 그 코드를 재현하기 위해서 다시 타이핑을 해야 하는 어려움이 있다. 현재는 간단한 코드이지만, 코드가 수백줄, 수천줄이 되면, 사진으로 남긴 코드에 대한 답변은 한계가 있다.