Context Manager란?
가장 흔하게 사용되는 컨텍스트 관리자는 파일 입출력에 사용되는 "with open()" 구문이다.
with open("file.txt", "r") as f:
f.read()
위 구문은 처음에 파일을 열고, 구문이 종료될 때 파일을 닫는다. 따라서 내부적으로 open과 close를 모두 수행하고 있는 것이다. 즉, 컨텍스트 관리자(context manager)는 특정 작업의 시작과 끝에 정해진 행동을 수행할 수 있도록 한다. 마치 함수의 데코레이터(decorator)와 비슷한 형태이다.
객체 활용
class Context(object):
def __enter__(self):
# 사전 작업
return self
def __exit__(self, exc_type, exc_value, traceback):
# 사후 작업
__enter__은 컨텍스트의 시작에서 수행할 작업을, __exit__는 컨텍스트가 종료될 때 수행되는 코드를 담고 있다. 주의할 점은 __exit__가 내부적으로 발생한 예외를 파라미터로 받아온다는 것이다. 이렇게 만들어진 객체는 아래와 같이 사용된다.
with Context() as context:
# 필요한 처리
context # __enter__의 return 값
as 뒤에 선언된 변수는 __enter__ 메서드의 반환값을 받는다. 위 코드의 경우, self를 반환함으로써 Context 객체를 받을 수 있었다.
주의할 점은 __exit__ 메서드가 True를 반환하도록 하면 예외가 발생해도 암묵적으로 넘어간다는 것이다. 아래 예시를 보면 이해할 수 있다.
class Context(object):
def __enter__(self):
print("-- 시작 --")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("-- 종료 --")
return True
with Context() as context:
raise TypeError
-- 시작 --
-- 종료 --
일반적으로는 TypeError가 발생해야 정상이지만 True를 반환함으로써 예외를 발생시키지 않고 넘어가게 된다.
객체 활용 안 하는 경우
위에서 봤던 with문을 사용하는 문법의 경우 as를 통해 객체에 대한 정보를 받아올 수 있었다. 하지만 만약 이러한 반환값이 필요하지 않다면 아래와 같이 ContextDecorator를 활용해 선언하는 방법도 있다.
import contextlib
class Context(contextlib.ContextDecorator):
def __enter__(self):
...
def __exit__(self, exc_type, exc_value, traceback):
....
위에서 봤던 코드와 같이 __enter__와 __exit__을 선언하면 된다. 대신 ContextDecorator 객체를 상속받아야 한다. 그리고 사용법에도 차이가 있다.
@Context
def test():
pass
with문을 사용하지 않고 데코레이터를 활용해 구현할 수 있다. with문을 활용할 때보다 코드가 간결해지는 장점이 있지만 객체로부터 반환값을 받아올 수는 없다.
제너레이터 활용
앞에서는 __enter__와 __exit__ 메서드를 포함한 객체를 활용했다면 함수를 이용해 컨텍스트 매니저를 구현하는 방법도 있다. yield를 활용한 제너레이터가 실행된 라인 정보를 기억한다는 점을 활용한 것이다.
import contextlib
@contextlib.contextmanager
def context():
# 사전 작업: __enter__과 같은 역할
yield 반환값
# 사후 작업: __exit__과 같은 역할
yield 키워드를 중심으로 앞에 선언된 코드는 __enter__과 같이 먼저 실행되고, yield 후에 선언된 코드는 __exit__과 같이 작업이 모두 끝난 후에 실행된다. 만들어진 제너레이터는 with 구문을 통해 활용된다.
with context() as 반환값:
# 필요한 작업
__enter__메서드의 반환값을 as 뒤 변수를 통해 받던 것과 같이 yield를 통해 반환되는 값은 as 뒤 변수에 할당된다.
이러한 방식은 기존의 데코레이터와 같이 함수의 재사용을 용이하게 해준다.