앗! 광고가 차단되었어요!

글 내용이 방문자께 도움이 되었다면, 광고 차단 프로그램 해제를 고려해주세요 😀.

공돌이

Python: Context Manager

this-gpa 2020. 10. 26. 16:04

 

학습하면서 작성한 내용이니 틀린 내용이 있으면 말씀해주세요!

Context Manager의 개념

context manager란 with statement가 실행될 때의 runtime context를 결정하는 객체입니다. 여기서 runtime context란 with의 코드 블록이 실행되기 전에, 실행된 후에 수행할 행위로 볼 수 있습니다.

예를 들어, 한번쯤 사용해보셨을 open()도 context manager를 반환합니다.

>>> with open("sample.txt", "a") as f:
...     # do something
...     print(type(f))
...
<class '_io.TextIOWrapper'>

여기서 보실 수 있듯이, TextIOWrapper의 부모는 TextIOBase, TextIOBase의 부모는 IOBase입니다.

# _pyio.py
class TextIOWrapper(TextIOBase):
...

class TextIOBase(IOBase):
...

class IOBase(metaclass=abc.ABCMeta):
    ...
    ### Context manager ###

    def __enter__(self):  # That's a forward reference
        """Context management protocol.  Returns self (an instance of IOBase)."""
        self._checkClosed()
        return self

    def __exit__(self, *args):
        """Context management protocol.  Calls close()"""
        self.close()

    ### Lower-level APIs ###

여기서 IOBase__enter__with 코드 블럭 실행 전에, __exit__with 코드 블럭 실행 후에 호출되는 함수이자 약속입니다.

정리하면, open()은 대략 파일에 bind 된, context manager의 일종인 TextIOWrapper를 만들어 반환합니다. with는 코드 블럭 실행 전에 manager의 __enter__을 호출하여 반환 값(자신)을 f에 할당합니다. 그리고 실행 후에 __exit__을 호출하여 파일을 닫습니다. (fd를 닫거나 등등의 행위를 예상할 수 있습니다)

context manager을 사용한다면 상태를 저장하고 복구하거나, Lock 등에서 이용할 수 있을 것입니다.

with 행동 파악하기

문서에서는 다음과 같이 설명합니다.

with EXPRESSION as TARGET:
    SUITE

is semantically equivalent to:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False
>
try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

너무나 명확한 설명이라서 보탤 것이 없습니다. 주의할 점은 EXPRESSION은 manager 타입이거나 그런 것을 반환해야 할 것으로 보입니다.

추가로 __enter____exit__의 임무를 확인할 수 있습니다.

  • __enter__ 의 반환된 값은 as 뒤의 변수에 할당됩니다.
  • __exit__의 인자에는 Exception과 관련된 정보가 주어집니다. 만약 True를 반환하면 Exception은 raise되지 않습니다.

꼭 Manager는 class인가요?

contextlib.contextmanager을 이용해서 함수형 manager을 정의할 수 있습니다. 정의 시 이전에서 배웠던 generator을 사용합니다!

# pydocs
from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception

At the point where the generator yields, the block nested in the with statement is executed. The generator is then resumed after the block is exited. If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred. Thus, you can use a try…except…finally statement to trap the error (if any), or ensure that some cleanup takes place.

with 코드 블럭 실행 중 Exception이 발생하면, generator 내부에서 raise 되며 이를 처리하고 싶은 경우 try-except-finally 형식을 사용하면 됩니다.

이외에도 manager class를 decorator로 사용하는 방법도 있으니 참고하면 좋습니다.

한번 정의해볼까요?

간단하게 sqlite 커넥션을 만들고 종료시키는 manager을 정의해봤습니다. (사실 connection 객체도 manager랍니다!)

import sqlite3


class MyManager:
    # connect and close sqlite3 connection, with memory
    def __enter__(self):
        self.con = sqlite3.connect(":memory:")
        print("created sqlite3 connection!")
        return self.con

    def __exit__(self, *args):
        self.con.close()
        print("closed sqlite3 connection!")


with MyManager() as con:
    c = con.cursor()

    c.execute("create table person (id integer primary key, firstname varchar unique)")
    c.execute("insert into person(firstname) values (?)", ("Joe",))
    con.commit()

    c.execute("select * from person")
    print(c.fetchone())

결과는 다음과 같습니다.

created sqlite3 connection!
(1, 'Joe')
closed sqlite3 connection!

Reference

Python docs: 3.3.9. With Statement Context Managers
Python docs: Context Manager Types
Python docs: 8.5. The with statement
Python docs: contextlib.contextmanager

'공돌이' 카테고리의 다른 글

문서화를 위한 drf-yasg 적용하기  (2) 2020.10.26
Python: Decorator  (0) 2020.10.26
Python: Generator  (0) 2020.10.26
Coursera 재정지원 (Financial Aid) 요청하기  (0) 2020.07.26
C++로 웹서버 만들기  (0) 2020.06.28