본문 바로가기

개발

파이썬의 컨텍스트 매니저 (Context Manager) 에 대해 알아봅시다.

반응형

기초

컴퓨터에서 사용할 수 있는 리소스는 제한적입니다. 따라서 사용한(acquired) 리소스는 종료해주는 것(released)이 중요합니다. 그렇지 않다면 프로그램이 종료된 이후에도 사용한 리소스가 계속 열려있는 resource leak현상이 발생합니다.

f = open('memo.txt', 'r')
print(f.read())

위의 코드처럼 파일을 열고 닫아주지 않는다면, 파일이 계속 컴퓨터에 남아있을 확률이 있습니다. (대부분 Python이 종료해준다고 하긴 합니다.)

>> Hello, World

따라서 아래와 같이 열었던 파일을 닫아주어야 합니다.

f = open('memo.txt', 'r')
print(f.read())
f.close()

하지만, 코드가 복잡해지다보면 파일을 닫기전에 에러가 발생할 가능성이 높아집니다.

f = open('memo.txt', 'r')

...

print(f.read())

... (예외발생)

f.close() #실행되지 않음

try, (except), finally 구문을 사용하면 이 문제를 쉽게 해결할 수 있습니다.

f = open('memo.txt', 'r')

try:
    ...

    print(f.read())

    ... (예외발생)

finally:
    f.close() # 예외가 발생하더라도 실행됨

이렇게 tryfinally를 활용할 수도 있지만, with를 이용하면 더욱 간단하게 구현할 수 있습니다. with문의 범위를 벗어날 때, 혹은 with문 내에서 예외가 발생하더라도 파일 종료를 보장해줍니다.

with open('memo.txt', 'r') as f:
    ...

    print(f.read())

    ...

# with문을 빠져 나가거나, 예외가 발생하더라도 파일을 닫아준다.

Magic Methods for with statement

기본적으로 파이썬의 open메쏘드는 with구문과 함께 사용할 수 있습니다. 그 외에 사용이 끝난 리소스의 release를 보장하기 위해 with구문을 사용하고 싶다면 어떻게 해야할까요?

파이썬 클래스의 Magic Methods를 이용하면 됩니다. Magic Methods란 클래스의 __init__메소드와 같이 특정한 기능을 하도록 미리 설계된 메소드를 의미합니다. with구문을 사용하기 위해서는 클래스에 __enter____exit__이라는 Magic Methods를 정의해주면 됩니다.

  • __enter__(self): with구문에 진입하는 시점에 자동으로 호출되는 메소드
  • __exit__(self, type, value, traceback): with구문을 빠져나오기 직전에 호출되는 메소드 type, value, traceback는 with문을 빠져나오기 전에 예외가 발생했을 때의 정보를 나타냄

아래의 예제처럼 사용할 수 있습니다.
아래의 예제는 Joel Ramos의 Python Context Managers and the “with” Statement에 나온 예제와 동일합니다.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

class SQLAlchemyDBConnection(object):
    """SQLAlchemy database connection"""
    def __init__(self, connection_string):
        self.connection_string = connection_string
        self.session = None

    # with구문 진입시에 db와 connection을 하고
    # ORM을 사용하기 위한 session을 만들어준다.
    def __enter__(self):
        engine = create_engine(self.connection_string)
        Session = sessionmaker()
        self.session = Session(bind=engine)
        return self

    # with구문을 빠져나오기 전 session의 종료를 장한다.
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.session.close()

위와 같이 SQLAlchemyDBConnection클래스를 만들면 아래와 같이 with 구문과 함께 사용할 수 있습니다.

conn_str = 'mssql+pydobc://server_name/db_name?driver=SQL+Server'
db = SQLAlchemyDBConnection(conn_str)
with db:
    customer = db.session.query(Customer).filter_by(id=123).one()
    print(customer.name)

contextmanager decorator

클래스를 정의하는 것 외에도 contextmanager decorator를 사용하는 것도 방법입니다. 아래는 파이썬 공식문서에 있는 예제입니다.

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

Async Context Manager

위에서 설명한 Context Manager는 모두 동기적으로 실행됩니다. Python 3.7부터는 Context Manager를 비동기적으로 사용할 수 있습니다.

# from python 3.5

class AsyncContextManager:
    async def __aenter__(self):
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        await log('exiting context')
# from python 3.7

from contextlib import asynccontextmanager

@asynccontextmanager
async def get_connection():
    conn = await acquire_db_connection()
    try:
        yield conn
    finally:
        await release_db_connection(conn)

async def get_all_users():
    async with get_connection() as conn:
        return conn.query('SELECT ...')

참조 사이트

반응형