OS가 사용할 수 있는 자원은 한정되어 있기에 우리는 할당을 받았다면 다시 돌려줘야 원활한 자원 순환이 가능하다.
간단하게는 다음과 같은 코드가 있다.
file = open('./test.txt', 'w')
try:
file.write('test text\nend.')
finally:
file.close()
이렇게 file.close()
를 해서 자원을 돌려줘야 OS는 다시 자원을 다른 곳에 할당해줄 수 있다.
흔히 사용하는 다음 코드는 자동으로 자원을 반환한다.
with open('./test.txt', 'w') as file:
file.write('test text\nend.')
컨텍스트 매니저는 리소스 관리를 위한 구조로, 주로 파일, 네트워크 연결, 데이터베이스 세션과 같은 리소스의 할당 및 해제를 자동화하는 데 사용된다.
위에서 볼 수 있듯이 with 문과 함께 사용되며, 이를 통해 코드의 가독성과 안정성을 높일 수 있다.
컨텍스트 매니저는 __enter__
, __exit__
두 개의 매직 메서드를 구현한다.
__enter__
with 문이 시작될 때 실행된다. 여기서 리소스를 할당하거나 초기화하는 코드를 작성할 수 있고, with 문과 함께 사용되는 변수에 할당될 값을 반환할 수 있다.
__exit__
with 문이 끝날 때 실행된다. 인자로 (예외 유형, 예외 값, 백트레이스) 세 개의 인자를 받는다.
여기서는 리소스의 정리 작업을 수행하고, 예외가 발생한 경우 이 메서드 내에서 처리할 수 있으며, 예외를 무시하려면 True를 반환하면 된다.
class MyFileWriter():
def __init__(self, filename, method):
print("init")
self.file_obj = open(file_name, method)
def __enter__(self):
print("enter")
return self.file_obj
def __exit__(self, exc_type, value, trace_back):
print("exit")
if exc_type:
print("exc!")
self.file_obj.close()
# Custom
with MyFileWriter('./test.txt', 'w') as f:
f.write('test text\nend.')
# Origin
with open('./test.txt', 'w') as file:
file.write('test text\nend.')
위 코드에서 보듯이 context manager을 통해 커스텀한 방식으로 파일을 다룰 수 있다.
만약 어떤 함수의 실행시간을 측정하려 할 때, time
함수 대신 다음 context manager 객체를 만들 수 있다.
import time
class ExcuteTimer():
def __init__(self, msg):
self._msg = msg
def __enter__(self):
self._start = time.monotonic()
return self._start
def __exit(self, exc_type, exc_val, exc_traceback)
if exc_type:
print("exception!")
else:
print(f'{self._msg}: {time.monotonic() - self._start} sec'
return True
with ExcuteTimer("function excute time") as v:
print(v) # __enter__ 메서드의 반환값
# 시간을 측정하고 싶은 함수나 코드 . . .
for i in range(10**8):
pass
파이썬의 표준 라이브러리 중 하나이다.
위에서 매직 메서드를 이용하여 구현하던 부분을 데코레이터 형식으로 지원하여 좀 더 쉽게 context manager를 구현할 수 있다.
import contextlib
import time
@contextlib.contextmanager
def ExcuteTimerDe(msg):
start = time.monotonic()
try:
yield start # __enter__
except BaseException as e:
print(f"logging: {e}")
else: # __exit__
print(f"{msg} : {time.monotonic() - start} sec")
with ExcuteTimerDe("function excute time") as v:
print(v)
for i in range(10**8):
pass
# raise ValueError("error msg")가 있고 이 부분이 발생한다면 ExcuteTimerDe의 에러문구로 출력된다.
이것은 위에서 클래스로 선언한 타이머와 완전히 동일한 기능을 한다.
contextlib.contextmanager
을 데코레이터로 한 함수는 yield
의 시작을 __enter__
, 이후의 코드를 __exit__
로 구분한다.