개요
Python에서 파일을 다룰 때는 파일을 열고 닫는 작업이 필요하다.
file = open('somefile', 'w')
try:
file.write('Hola')
finally:
file.close()
일반적인 프로그래밍에서 연 파일은 반드시 닫는 작업을 해야 하는데, 명시적으로 close 함수를 호출하는 불편함을 해소하기 위해 with 문을 사용할 수 있다.
with open("asd.txt", "r") as file:
lines = file.readlines()
...
이 때 with문을 context manager라고 한다. 그럼 대체 context manger라는 게 뭘까? 알아보자.
Context manager
Context Manger는 Python의 독특한 기능 중 하나로, 사전 조건과 사후 조건을 처리할 때 유용해 다양한 방면에서 사용할 수 있다.
사용 예시
- 스레드 락 acquire/release
- 파일 open/close
- 데이터베이스 작업 예외 발생 여부에 따른 commit/rollback
- 숫자 연산 후 소숫점의 precision 계산
원하는 타이밍에 리소스를 할당하고 제공하는 역할을 하기 때문에 with문은 가장 많이 사용하는 context manager 중 하나이다.
with문은 다음과 같이 이해할 수 있다. 아래 코드는 with문 내부를 의사코드로 간단히 나타낸 것이다.
# naive approach
VAR = EXPR
VAR.__enter__()
try:
BLOCK
finally:
VAR.__exit__()
with문을 통해 context manager로 진입하는데, 진입 시에는 __enter__를, 종료 시에는 __exit__ 메소드를 호출한다. 각각의 함수에서 필요한 작업을 처리하면 된다.
__enter__
with문이 실행됨과 동시에 context manager로 진입하면서 실행된다. 필요한 사전작업을 하고 필요한 객체를 return할 수 있다. 반환값이 있는 경우 with문의 as 키워드를 통해 해당 객체를 받아 추가적인 작업을 이어갈 수 있다.
__exit__
with 블럭을 나갈 때, context manager가 종료될 때 호출된다. 사용하지 않을 리소스를 정리하는 등의 작업을 할 수 있다. 반환값은 필수가 아니지만 예외가 발생했을 때 False를 반환하여 잠재적으로 발생한 예외를 전파시킬 수 있다.
구현
Python에서 Context Manager를 구현하는 방법은 여러가지지만 대표적으로 매직 메서드를 구현하는 방법, 그리고 contextlib를 사용하는 방법을 주로 사용하는 것 같다. 이번 글에서는 매직 메서드를 구현하는 방향으로 알아본다.
Context Manager를 구현하기 위해서는 클래스에 __enter__, __exit__ 메서드를 구현해야 한다. 아래 예시는 context manager 진입 시 리스트를 생성하고, 종료 시 리스트의 내용을 호출하는 코드이다.
class ListManager:
def __enter__(self):
self.list = []
return self.list
def __exit__(self, exc_type, exc_val, exc_tb):
print(self.list)
__exit__ 메서드의 매개변수는 컨텍스트에서 벗어나게 만든 예외를 기술하는데 사용된다.
구현한 클래스를 사용하면 다음과 같다.
if __name__ == '__main__':
with ListManager() as lm:
lm.append(1)
lm.append(2)
with문 내에서 print 함수를 사용하지 않았음에도, with문을 빠져나가면서 __exit__ 메서드가 호출되며 리스트의 원소를 출력하는 모습을 확인할 수 있다.
참고 문서
https://docs.python.org/ko/3/library/contextlib.html
https://ddanggle.gitbooks.io/interpy-kr/content/ch24-context-manager.html