본문 바로가기
Study/Python

python의 with 구문 (context manager) 한번 보기

by 개발새-발 2021. 5. 19.
반응형

대부분의 경우 파이썬을 배우면서 with를 처음 접하는 때는 파일을 다룰 때이다. 어떤 파일을 다루고 나서는 close를 해주어야 프로그램이 정상 동작한다. 그러나 파일을 다루던 도중에 오류로 인해 예상치 않게 프로그램이 종료가 된 경우 close를 미처 해주지 못한 경우도 발생한다. try - finally 구문을 이용하면 실행 도중 오류가 발생하였을 때 close를 해 줄 수 있다. 그러나 with를 사용하면 우리가 직접 close를 해 줄 필요가 없다. 컨텍스트 매니저를 통해 자원의 할당과 반납을 필요할 때 해주기 때문이다.

 

try finally vs with

# Handling file with try - finally
f = open('a.txt','w')
try:
	f.write('hello world')
finally:
	f.close()
# Handling File with context manager(with as)
with open('a.txt','w') as f:
	f.write('hello world')

두 코드 모두 a.txt 파일에 "hello world"를 적고 파일을 닫는다.  두 방법 모두 파일을 작성하던 중 오류가 발생하여도 파일을 닫는데, with as 를 이용한 코드에는 따로 close를 적지 않았다. with as 문을 사용한 코드에선 with as 문 내부의 코드가 모두 실행되거나 중간에 오류가 발생하면 파일이 닫힌다. try finally 구문보다 간결하다.

 

여러 개의 파일 관리하기

두 개 이상의 파일을 동시에 사용할 때 with as 문을 사용하는 방법이다. 단순히 두 개의 with as 문을 겹쳐도 되고, 하나의 with문에 두 개 이상의 파일을 열어도 된다.

with open('a.txt','w') as a:
	with open('b.txt','w') as b:
		a.write('hello world')
		b.write('hello b')
with open('a.txt','w') as a, open('b.txt','w') as b:
	a.write('hello world')
	b.write('hello b')

한 번에 다루고자 하는 파일이 많은데 with문을 사용하면 with문이 옆으로 길어진다. 그래서 파이썬 버전 3.10.0 b1 버전부터는 괄호와 함께 여러 개의 파일들을 여러 줄에 걸쳐서 작성할 수 있다.

# from python version 3.10.0b1
with (
	open('a.txt','w') as a,
	open('b.txt','w') as b,
	open('c.txt','w') as c
):
	a.write('aaa')
	b.write('bbb')
	c.write('ccc')

class로 context manager 구현하기

우리가 원하는 class를 with as문에서 작동하도록 할 수 있다. 그러기 위해선 __enter__, __exit__ 두 메서드를 작성해줘야 한다. 우리가 파일 객체를 with구문과 함께 사용할 수 있는 것도 두 메서드가 이미 구현되어있기 때문이다. 예를 들어 Logger 클래스가 있다고 하자. 이 클래스는 파일에 log메서드를 통해 로그를 작성한다. open, close 메서드를 통해 로그가 기록될 파일 객체를 열고 닫을 수 있다. 이 Logger클래스를 with문과 함께 사용하고 싶을 때는 다음과 같이 사용해주면 된다.

class Logger:

    def __init__(self,name):
        self.name = name
        self.file = None

    def log(self,content):
        now = datetime.now()
        timestr = f'[{now.year}-{now.month}-{now.day} {now.hour}:{now.minute}:{now.second}] '
        self.file.write(timestr+content+'\n')

    def open(self):
        self.file = open(self.name,'a')

    def close(self):
        self.file.close()

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, type,value,traceback):
        self.log('__exit__ from Logger called.')
        self.close()

작성된 Logger에서. enterclose 메소드만 보자. enter 메서드에서 open, exit 메서드에서 close가 이루어지고 있다. 이처럼 자기가 원하는 클래스를 with문과 함께 사용하고 싶다면 __enter__에 원하는 자원의 할당, 연결 등의 작업을 해 주고 __exit__에서 자원의 제거, 연결 해제 등의 마무리 작업을 해주면 된다.

 

위 클래스가 정상적으로 작동하는지 확인해보자.

if __name__ == '__main__':
    with Logger('log_a.txt') as logger:
        logger.log('Started')
        logger.log('log a logging')
        logger.log('Ended')

    with Logger('log_b.txt') as logger:
        logger.log('Started')
        logger.log('log b logging')
        raise Exception()
        logger.log('Ended')

첫번째 with 블록에선 모든 로그가 기록될 것이고, 두 번째 블록에선 중간에 예외가 발생하여 Ended가 찍히지 않았을 것이다. 만들어진 파일을 확인해보자.

[2021-5-19 18:7:43] Started
[2021-5-19 18:7:43] log a logging
[2021-5-19 18:7:43] Ended
[2021-5-19 18:7:43] __exit__ from Logger called.
[2021-5-19 18:7:43] Started
[2021-5-19 18:7:43] log b logging
[2021-5-19 18:7:43] __exit__ from Logger called.

여기서, 두번째 로그파일을 보면 중간에 예외가 발생하여도 __exit__이 호출되어 파일 객체의 close작업이 이루어진 것을 볼 수 있다.

반응형

댓글