파이썬을 배워보자 12일차 - 파일 입출력과 with

0

Python

목록 보기
12/18

점프 투 파이썬 : https://wikidocs.net/book/1
파이썬 기본을 갈고 닦자 : https://wikidocs.net/16031

파일

파일 다루기에 있어 파이썬은 굉장히 큰 장점을 가지고 있다. 단순하면서도 직관적인 파일 다루기는 다른 언어에 비해 사용하기 매우 좋다.

1. 파일 생성하기

파일을 다룰 때는 기본 내장함수인 open()을 사용한다. open()은 다음과 같은 사용법을 가지고 있다.

open(filepath, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

굉장히 인자가 다양한데 자주 사용하는 인자만 정리하면 다음과 같다.

  1. filepath : 파일 경로
  2. mode : 파일이 열리는 모드
    1. 'r': default로 읽기 용으로 파일을 연다.
    2. 'w': 쓰기위해 연다. 단, 파일이 이미있을 시 덮어쓴다.
    3. 'a': 쓰기위해 열고, 파일 끝에 새로운 내용을 추가한다.
    4. 'x': 베타적 생성을 위해 열리고, 이미 파일이 있다면 실패한다.
    5. 'b': 바이너리 모드이다.
  3. buffering : 버퍼링 끄기는 0(바이너리 모드에서만 동작한다.), 라인모드는 1 (텍스트 모드에서만 가능), 고정크기로 보내려면 임의의 바이트 수를 1보다 큰 양의 수로 입력한다.
  4. encoding : 파일을 디코딩, 인코딩하는데 사용되는 이름이다. 대부분 utf8이지만, 명시적으로 하는 것이 좋다.

주의!! 파일 객체는 반드시 열고, 작업이 완료되면 반드시 파일을 닫아야 한다. 이는 파일을 받지 않으면 버퍼링되어 있는 데이터는 기록되지 않고 소실될 수 있기 때문이다. 따라서 파일을 닫는 close() 메서드를 꼭 호출해주어야 한다.

이제 파일을 한 번 만들어보자

f = open('./hello.txt', 'w')
f.close()

다음의 코드를 실행하면 현재 코드에서 hello.txt 파일이 생성된 것을 확인할 수 있을 것이다. 이는 'write' 모드로 파일을 open했기 때문에 파일을 새로 만든 것이다.

1.1 파일 쓰기

파일 쓰기는 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!!

좀 더 심화해보자, 사실 modew로 쓴 것은 wt와 같은 의미이다. t는 텍스트 모드를 칭하며 기본값이다. 따라서 따로 바이너리 모드인 b를 모드에 넣어주지 않는 한 t가 같이 적혀있는 것이다.

f = open('./hello.txt', 'wt') # f = open('./hello.txt', 'w') 같다.

그래서 어떤 분들은 명시적으로 modewt, at, rt이렇게 적어주시는 분들이 있다. 각각 w, a, r과 다를 바 없으니 걱정말자.

encoding도 설정해줄 수 있다.

f = open('./hello.txt', 'wt', encoding='utf-8')

기본적으로 utf-8이지만 명시적으로 기입하는 것이 가독성에 좋을 때가 있다.

2. 파일을 읽는 여러 방법

2.1 readline 함수 이용하기

이전에 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!!

제대로 모든 라인이 출력되었음을 확인할 수 있다.

2.2 readlines 함수 이용하기

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()

개행 문자인 줄 바꿈 문자가 사라졌을 것이다.

2.3 read 함수 사용하기

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()

다음과 같은 결과가 나오게 된다.

3. 파일에 새로운 내용 추가하기

쓰기 모드인 '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번째 줄입니다

다음과 같이 되어있을 것이다.

4. with문

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.closewith 구문이 종료되면서 자동으로 호출된다. 그래서 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하여 잘 실행된 것을 확인할 수 있다.

0개의 댓글