점프 투 파이썬 : https://wikidocs.net/book/1
파이썬 기본을 갈고 닦자 : https://wikidocs.net/16031
파일 다루기에 있어 파이썬은 굉장히 큰 장점을 가지고 있다. 단순하면서도 직관적인 파일 다루기는 다른 언어에 비해 사용하기 매우 좋다.
파일을 다룰 때는 기본 내장함수인 open()
을 사용한다. open()
은 다음과 같은 사용법을 가지고 있다.
open(filepath, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
굉장히 인자가 다양한데 자주 사용하는 인자만 정리하면 다음과 같다.
filepath
: 파일 경로mode
: 파일이 열리는 모드'r'
: default로 읽기 용으로 파일을 연다.'w'
: 쓰기위해 연다. 단, 파일이 이미있을 시 덮어쓴다.'a'
: 쓰기위해 열고, 파일 끝에 새로운 내용을 추가한다.'x'
: 베타적 생성을 위해 열리고, 이미 파일이 있다면 실패한다.'b'
: 바이너리 모드이다.buffering
: 버퍼링 끄기는 0(바이너리 모드에서만 동작한다.), 라인모드는 1 (텍스트 모드에서만 가능), 고정크기로 보내려면 임의의 바이트 수를 1보다 큰 양의 수로 입력한다.encoding
: 파일을 디코딩, 인코딩하는데 사용되는 이름이다. 대부분 utf8이지만, 명시적으로 하는 것이 좋다.주의!! 파일 객체는 반드시 열고, 작업이 완료되면 반드시 파일을 닫아야 한다. 이는 파일을 받지 않으면 버퍼링되어 있는 데이터는 기록되지 않고 소실될 수 있기 때문이다. 따라서 파일을 닫는
close()
메서드를 꼭 호출해주어야 한다.
이제 파일을 한 번 만들어보자
f = open('./hello.txt', 'w')
f.close()
다음의 코드를 실행하면 현재 코드에서 hello.txt
파일이 생성된 것을 확인할 수 있을 것이다. 이는 'write' 모드로 파일을 open
했기 때문에 파일을 새로 만든 것이다.
파일 쓰기는 f.write()
을 사용한다. 굉장히 간단한데 예제 코드를 보도록 하자
f = open('./hello.txt', 'w')
f.write("hello world\n")
f.write("my name is!!\n")
f.close()
file 객체
의 메서드인 write
를 사용하여 문자열을 적어주기만 하면 써진다. 아주 심플하면서도 파워풀하다.
hello.txt
파일을 열면 다음과 같이 글이 적혀있을 것이다.
hello world
my name is!!
좀 더 심화해보자, 사실 mode
를 w
로 쓴 것은 wt
와 같은 의미이다. t
는 텍스트 모드를 칭하며 기본값이다. 따라서 따로 바이너리 모드인 b
를 모드에 넣어주지 않는 한 t
가 같이 적혀있는 것이다.
f = open('./hello.txt', 'wt') # f = open('./hello.txt', 'w') 같다.
그래서 어떤 분들은 명시적으로 mode
를 wt
, at
, rt
이렇게 적어주시는 분들이 있다. 각각 w
, a
, r
과 다를 바 없으니 걱정말자.
encoding
도 설정해줄 수 있다.
f = open('./hello.txt', 'wt', encoding='utf-8')
기본적으로 utf-8
이지만 명시적으로 기입하는 것이 가독성에 좋을 때가 있다.
이전에 sys.stdin.readline
으로 입력을 받아봤었다. 여기서의 `readline
역시도 마찬가지로 단 한 줄을 가져온다
위에서 만든 hello.txt
파일은 다음처럼 되어있을 것이다.
hello world
my name is!!
이 글을 읽어보도록 하자
f = open('./hello.txt', 'r')
line = f.readline()
print(line) # hello world
f.close()
결과는 hello world
하나만 가져오고 나머지 my name is!!
는 안가져온다. 언급했듯이 readline
은 단 한 줄만 가져오기 때문이다.
따라서, while
문을 통해 반복해서 한 줄 씩 가져오는 방법을 사용하면 된다. 더 이상 가져올 텍스가 없다면 ''
이라는 빈 문자열이 나올 것이고, 이는 Falsy
이기 때문에 False
로 처리될 것이다. 그러면 반복문을 종료시키면 된다.
f = open('./hello.txt', 'r')
while True:
line = f.readline()
if not line: break
print(line, end="")
f.close()
if not line
으로 처리하면 line
이 ''
빈 문자열을 뱉을 때 False
가 되고, not
이 붙어서 True
로 바뀔 것이다.
hello world
my name is!!
제대로 모든 라인이 출력되었음을 확인할 수 있다.
readlines
는 텍스트 모든 내용을 받은 다음, 한 줄 씩 리스트에 넣어준다.
f = open('./hello.txt', 'r')
lines = f.readlines()
print(lines) # ['hello world\n', 'my name is!!\n']
f.close()
아주 이쁘게 처리된 것을 확인할 수 있다.
여기서 만약 줄 바꿈 문자 (\n
)을 없애고 싶다면 rstrip
이나 strip
을 사용하면 된다.
f = open('./hello.txt', 'r')
lines = f.readlines()
for line in lines:
line.rstrip()
print(line)
f.close()
개행 문자인 줄 바꿈 문자가 사라졌을 것이다.
read
함수는 파일의 내용 전체를 하나의 문자열로 둘려준다.
f = open('./hello.txt', 'r')
text = f.read()
print(text)
f.close()
결과는 다음과 같다.
hello world
my name is!!
read
함수는 인자를 받을 수 있는데 read(글자수)
이다. 즉, 내가 hello
만 가져오겠다. 하면 read(5)
를 쓰면 된다.
f = open('./hello.txt', 'r')
text = f.read(5)
print(text) # hello
f.close()
재밌는 것은 계속해서 read
에 인자를 주면 앞으로 나아간다는 것이다. 가령 지금 hello
를 얻기 위해 5
를 넣었으니 world
를 얻기위해서는 6칸
을 더가야한다. 총 11
을 넣어야 할 것 같지만, 이미 앞에서 5칸
을 갔기 때문에 6
만 써줘도 된다.
f = open('./hello.txt', 'r')
text = f.read(5)
print(text) # hello
text = f.read(6)
print(text) # world
f.close()
즉, read
의 인자로 들어가는 숫자는 하나의 포인터인 것이다. 포인터를 다시 원점으로 돌리거나 특정 구간으로 돌리기 위해서는 f.seek(위치)
를 쓰면 된다. 원점으로 돌리기 위해 f.seek(0)
으로 써보겠다.
f = open('./hello.txt', 'r')
text = f.read(5)
print(text) # hello
f.seek(0)
text = f.read(6)
print(text) # hello
f.close()
다음과 같은 결과가 나오게 된다.
쓰기 모드인 'w'
파일을 열면 이미 써있던 파일의 글들이 날라가는 효과를 보게된다. 이를 해결하기위해 원래 있던 값을 유지하면서 단지 새로운 값만 추가해야 하는 경우에는 추가 모드인 'a'
로 열면 된다.
그 이후로는 마치 'w'
에서 글을 쓰듯이 파일에 새로운 내용을 write
하면 된다.
f = open('./hello.txt', 'a')
for i in range(10):
data = "%d번째 줄입니다\n" % i
f.write(data)
f.close()
파일을 확인하면
hello world
my name is!!
0번째 줄입니다
1번째 줄입니다
2번째 줄입니다
3번째 줄입니다
4번째 줄입니다
5번째 줄입니다
6번째 줄입니다
7번째 줄입니다
8번째 줄입니다
9번째 줄입니다
다음과 같이 되어있을 것이다.
with
문은 c++의 RAII
기법과 유사한 패턴이 가능하도록 해주는 문법이다.
기본적인 사용방법은 다음과 같다.
with 객체 생성 as 변수:
변수.메서드()
변수.메서드()
...
이런 문법이다. with``는
:으로 닫히는 것을보면 하나의
본문을 가지고 있다는 것을 확인할 수 있다. 이 본문 안에서만 사용될 수 있는 변수가 바로
as 변수이다.
as 변수는
객체 생성```으로 부터 할당받은 변수이다.
즉, 변수 = 객체 생성
과 같은데, 다만 as 변수
가 다뤄질 수 있는 범위는 with
안이다. with
본문을 넘어서면 해당 변수
를 접근 , 호출할 수 없다.
이유는 간단하다. with
범위 안에서 객체가 생성되고 소멸되는 라이프 사이클이 모두 동작하기 때문이다. 즉, with
구문이 끝나면 변수
는 소멸된다.
왜 굳이 이런 복잡한 문법을 쓸까?? 라고 한다면 바로 file
과 같이 할당을 해주고 해제를 해야하는 객체를 사용할 때 with
패턴을 이용하면 효율적이기 때문이다.
할당하면 해제해주는게 뭐가 그렇게 어렵나! 싶으신 분들도 있을 텐데, 그런 분들은 둘 중 하나이다. 천재거나 아직 뉴비
거나, 왜냐하면 시스템이 복잡해질수록 에러 상황은 다양해지고, 에러 상황마다 해제 코드를 넣다보면 잊어버리거나, 중복 해제를 시켜버리는 상황이 나오기 때문이다. 그리고 이러한 해제를 안했다고 문제가 바로 나오지 않는다.
해제를 안했을 때 발생하는 문제는 한 달 뒤에 나오거나, 이유를 모를 원인으로 시스템이 터져버리거나 한다. 자원이 풍족한 웹과는 달리 자원이 매우 부족하거나 시스템적으로 극한까지 최적화를 해야하는 프로그래머에게는 숙명과도 같은 과제인 것이다.
그래서, C++에서는 RAII
패턴이, python에서는 with
구문이 나온 것이다.
with open('./hello.txt', 'a') as f:
f.write("hello world")
다음의 코드를 실행시켜보면 텍스트가 잘 추가되었음을 확인할 수 있을 것이다.
그런데, 잘 보면 f.close
가 없다? 사실 f.close
는 with
구문이 종료되면서 자동으로 호출된다. 그래서 with
구문 밖에서는 파일 객체인 f
에 접근할 수가 없는 것이다.
어떻게 이런게 가능한가? 라고 생각한다면 다음에 따로 클래스를 배우면 알게될 것이다.
이 내용은 클래스 부분을 보고 다시돌아와 보면 좋겠다. 사실은 class
인 파일 객체에는 Magic Method
가 있다. Magic Method
로 __enter__()
와 __exit__()
메서드가 있는데, with
안에 객체 생성
부분에 객체
가 있으면 __enter()__
을 자동으로 실행하고, with
구문이 끝나면 자동으로 객체의 __exit__
구문을 실행하는 것이다.
다음과 같이 with
구문을 사용하면 된다. 또한 file
객체 내부도 이렇게 __enter__
와 __exit__
으로 구현되어 있다.
class WithFile:
def __init__(self):
print("start file read!")
def __enter__(self): # 반환값이 있어야 with구문에서 as 변수에 할당된다.
self.f = open("./hello.txt", 'r')
return self
def printFile(self):
lines = self.f.readlines()
print(lines)
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
print("close file read")
withFile = WithFile()
with withFile as f:
f.printFile()
결과는 다음과 같다.
start file read!
['hello world\n', 'my name is!!']
close file read
파일까지 close하여 잘 실행된 것을 확인할 수 있다.