3-1. Context manager annotation

uoayop·2021년 3월 20일
0

Leaf와 Python

목록 보기
10/21
post-thumbnail

저번 시간에 [exit, enter] 매직 메소드를 클래스 형태로 구현하고, with문을 커스터마이즈 했다. 파이썬의 내부로 조금 들어갔다고 볼 수 있다.

이렇게 내부적으로 접근을 할때, 처음엔 클래스 형태로 정의를 해서 접근하는 방법 뿐이었다. 그런데 annotation(@)로 더 쉽게 접근할 수 있는 방법이 있다.

클래스 형태가 아닌 함수 형태로 접근하게 되는 것이다.

Context manager annotation

  • 오늘의 키워드
    • Decorator, Contextlib, context manager, with 비교

왜 contextlib 데코레이터를 함수 형태로 접근하는데요? 🤔

  1. 코드가 직관적이고, 간단하다.
  2. 코드의 수가 줄어들기 때문에, 예외 처리에 용이하고 가독성이 좋다.
  3. 함수이기 때문에 재사용성이 더 좋다.

이전에 클래스 형태로 작성했던 코드를 함수 형태로 바꿔보면서 개념을 이해해보자.


데코레이터 : 클래스 형태

🔗 2-4. Context Manager 참고 링크

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 구문에 들어갔구나" 라고 인식하게 된다.

타이머 기능 : 클래스로 구현

🔗 2-5. Context Manager 참고 링크

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 형태가 더 좋다.
왜? 🤔 에러 타입, 에러값, 트레이스백까지 있으니까 더 꼼꼼하게 처리가 가능하다.


[ 출처 ]

  1. 인프런 - 모두를 위한 파이썬 : 필수 문법 배우기 Feat. 오픈소스 패키지 배포 (Inflearn Original)

  2. 제너레이터와 yield 알아보기

profile
slow and steady wins the race 🐢

0개의 댓글