저번 시간에 [exit, enter] 매직 메소드를 클래스 형태로 구현하고, with문을 커스터마이즈 했다. 파이썬의 내부로 조금 들어갔다고 볼 수 있다.
이렇게 내부적으로 접근을 할때, 처음엔 클래스 형태로 정의를 해서 접근하는 방법 뿐이었다. 그런데 annotation(@)로 더 쉽게 접근할 수 있는 방법이 있다.
클래스 형태가 아닌 함수 형태로 접근하게 되는 것이다.
- 코드가 직관적이고, 간단하다.
- 코드의 수가 줄어들기 때문에, 예외 처리에 용이하고 가독성이 좋다.
- 함수이기 때문에 재사용성이 더 좋다.
이전에 클래스 형태로 작성했던 코드를 함수 형태로 바꿔보면서 개념을 이해해보자.
import contextlib
class MyFileWrite():
def __init__(self,file_name, method):
self.file_obj = open(file_name,method)
def __enter__(self):
return self.file_obj
def __exit__(self, exc_type, value, trace_back):
if exc_type:
print('Logging exception {}'.format(exc_type, value, trace_back))
self.file_obj.close()
with MyFileWrite('./testfile3.txt','w') as f:
f.write('context manager test3\ncontextllib test3.')
import contextlib
@contextlib.contextmanager
def my_file_writer(file_name, method):
f = open(file_name,method)
yield f #__enter__
f.close() # __exit__
with my_file_writer('testfile4.txt','w') as f:
f.write('Context Manager Test4.\nContextlib Test4')
특징
- 클래스 형태와 비교해 훨씬 코드가 간단하다.
- 따로 enter 구문을 정의하지 않는다. 대신 제너레이터를 사용한다.
enter, exit 대신 제너레이터 ? 🤔
- 제너레이터는 이터레이터를 생성해주는 함수이다.
이터레이터는 클래스에 따로 메소드를 구현해야 하지만,
제너레이터는 함수 안에서 yield라는 키워드만 사용하면 끝이다.
그래서 제너레이터는 이터레이터보다 훨씬 간단하게 작성할 수 있다.- 이때, yield를 선언하면 enter 구문과 같은 역할을 하고,
yield 구문 아래가 exit 구문으로 정의 된다.제너레이터와 yield
한번에 메모리에 올리는 것이 아니라, 하나하나 원소를 반환하고
그 반환될 위치를 기억한다.
- yield를 사용하면 값을 함수 바깥으로 전달하면서 코드 실행을 함수 바깥에 양보한다.
- 위의 코드에서 yield를 쓰면 내부적으로 "아~ 여기서 with 구문에 들어갔구나" 라고 인식하게 된다.
import time
class ExecuteTimer(object):
def __init__(self, msg):
self._msg = msg
def __enter__(self):
self._start = time.monotonic()
return self._start
def __exit__(self, exc_type, exc_value, exc_traceback):
if exc_type:
print("Logging exception {}".format((exc_type, exc_value, exc_traceback)))
else:
print('{}:{} s'.format(self._msg, time.monotonic() - self._start))
return True
with ExecuteTimer('Start Job') as v:
print('Received start monotonic : {}'.format(v))
#excute job
for i in range(100000000):
pass
raise Exception('Raise! Exception!!')
import time
@contextlib.contextmanager
def ExcuteTimerDc(msg):
start = time.monotonic()
try:
yield start #__enter__
except BaseException as e:
print('Logging exception: {}:{}'.format(msg,e))
raise #예외 발생 시 상위 클래스로 던짐
else: #__exit__
print('{}: {}s'.format(msg,time.monotonic()-start))
with ExcuteTimerDc('start job') as v:
print('received start monotonid : {}'.format(v))
for i in range(10000000):
pass
# raise value error : 예외처리
raise ValueError('occurred.')
예외 처리
예외처리를 fm대로 할때는 class 형태가 더 좋다.
왜? 🤔 에러 타입, 에러값, 트레이스백까지 있으니까 더 꼼꼼하게 처리가 가능하다.
[ 출처 ]