컨텍스트 관리자는 파이썬에서 제공하는 유용한 기능으로, with문을 사용하여 함수의 사전 조건과 사후 조건을 처리할 수 있게 해준다.
컨텍스트 관리자
컨텍스트 관리자는 파이썬을 차별화하는 독특한 기능이다. 파이써닉한 테크닉이므로, 사용하면 좋다. 사후 조건과 사전 조건을 처리할 때 유용하며 다양한한 방면에서 사용할 수 있다.
사용 예시
1.
쓰레드 락 Acquire/Relesase
2.
파일 open/close
3.
데이터베이스 작업 후 예외 유무에 따라 rollback/commit
4.
숫자 연산 후 소숫점의 precision 계산
위의 예시들은 PEP-343 공식 문서에서 간단한 예시코드와 함께 확인할 수 있다. Practical한 use case를 구현하며 살펴보는 것은 해당 페이지의 범위를 벗어난다. 하지만 Context Manager에 대한 이해가 풍부해지면, 실제로 어떻게 사용될 수 있는지 문서를 확인해보는 것도 좋을 듯하다.
간단한 코드
def process(fd):
# do something
print('processing ...')
fd = open('test.txt')
try:
print('start of process')
process(fd)
finally:
fd.close()
print('end of process')
Python
복사
start of process
processing ...
end of process
Python
복사
다른 프로그래밍 언어도 위와 같은 방식으로 파일을 열고, 예외를 처리하며, 리소스를 관리한다. 파이썬에서는 with문을 이용하여 코드를 간결하게 처리할 수 있다.
with open('test.txt') as fd:
process(fd)
Python
복사
with문 이해하기
with문은 다음과 같이 이해할 수 있다. 다음 세 코드는 같은 역할을 하는 sudo code이다.
# naive approach
VAR = EXPR
VAR.__enter__()
try:
BLOCK
finally:
VAR.__exit__()
Python
복사
# pythonic approach
with VAR = EXPR:
BLOCK1
Python
복사
# more pythonic using as keyword
with EXPR as VAR:
BLOCK1
Python
복사
코드에서 살펴볼 수 있듯 with문을 통해 컨텍스트 관리자로 진입할 수 있다. 진입시는 __enter__, 종료시에는 __exit__ 메스드를 호출하게 하여, 여기서 필요한 작업을 처리하면 된다.
__enter__
해당 메서드는 with문이 실행됨과 동시에 컨텍스 매니저로 진입하며 실행된다. 내부에서 필요한 사전 작업을 하고 필요한 객체를 return할 수 있다. return 값이 있는 경우 with문의 as 키워드를 통해서 해당 객체를 받아 추가적인 작업을 이어갈 수 있다.
__exit__
해당 메서드는 with block을 나올 때, 즉 컨텍스트 매니저가 종료될 때 호출된다. 사용하지 않을 리소스를 정리하는 등의 작업을 할 수 있다. return 값은 필수적이지 않지만 예외가 발생했을 때 False를 반환하여 잠재적으로 발생한 예외를 전파시킬 수 있다. True는 이러한 예외를 전파하지 않는 것으로 단순히 True를 반환하는 것은 지양해야한다.
오류를 조용히 무시하는 것은 굉장히 나쁜 습관이다.
구현
파이썬에서 컨텍스트 매지너를 구현하는 방법은 여러가지가 있다. 여기서는 매직 메서드 두 가지를 구현하는 방법과 contentlib 내장 라이브러리를 이용한 방법 두 가지를 포함해 세 가지를 살펴보겠다.
1. Naive Implementation
위에서 살펴본 예시와 같다. 클래스에서 두 가지 메서드를 구현하면 된다.
class Process:
name = 'Custom_Process_no_1'
def __enter__(self):
print('start of process')
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
print('end of process')
with Process() as p:
print(f'processing {p.name} ...')
Python
복사
start of process
processing Custom_Process_no_1 ...
end of process
Python
복사
2. conextlib.contextmanager
contextlib 내장 모듈의 contextmanager 데코레이터를 이용하는 방법이 있다. 사용 방법은 다음과 같다.
1.
제너레이터 함수를 정의한다
2.
yield 이전에 진입 시 할 일을 처리한다
3.
yield를 통해 필요시 객체를 넘겨준다
4.
yield 이후에 종료시 할 일을 처리한다.
from contextlib import contextmanager
@contextmanager
def gen_process():
print('start of process')
yield 'gen_process'
print('end of process')
with gen_process() as gp:
print(f'processing {gp} ...')
Python
복사
start of process
processing gen_process ...
end of process
Python
복사
위와 같이 제너레이터를 이용하여 컨텍스트 매니저를 활용할 수 있다. 이 경우 컨텍스트 관리자를 구현하는 클래스를 정의하지 않아도 되고 함수 리팩토링이 쉬워진다는 장점이 있다.
객체지향적으로 프로그램을 설계하면 아무 의미 없는 가짜 부모클래스를 만들어야하는 번거로움이 있다.
3. contextlib.ContextDecorator
ContextDecorator는 일종의 믹스인 클래스로 데코레이터를 구현하기 위한 로직을 제공한다. 이용하기도 쉽고 장점도 많다. 코드를 먼저 살펴보자.
from contextlib import ContextDecorator
class process_decorator(ContextDecorator):
def __enter__(self):
print('start of process')
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
print('end of process')
@process_decorator()
def process():
print('processing ...')
Python
복사
위와 같이 구현하고 나면 process 함수를 여러번 재사용할 수 있다. 또한, 함수를 래핑하는 데코레이터를 사용했으므로 with문을 사용하지 않는다는 특징도 있다.
해당 방법의 가장 큰 특징 중 하나는 process 함수와 process_decorator 데코레이터 사이의 독립성이 보장된다는 것이다. as 등으로 무언가를 넘겨주는 작업도 없다.
불편할 수도 있지만 기능적으로 분리되고 접근을 제한하여 독립성을 보장하는 것은 좋은 특성으로 볼 수 있다.
process()
process()
Python
복사
start of process
processing ...
end of process
start of process
processing ...
end of process
Python
복사