Python with절 문법의 이해

2021. 10. 25. 15:40[개발] 지식/Python

Python with절 문법의 이해

자원을 획득하고, 사용하고, 반납할때 주로 사용한다.

예를들어 파일을 여는 경우, 다른 프로세스를 위해 사용한 뒤에 닫아주어야 한다.

또는 DB 세션을 사용하는 경우, 다른 프로세스를 위해 반납해야 한다.

try / except / finally 구문을 통해 이와같은 구현이 가능하긴 하다.

하지만 이러한 방법 역시 예외가 발생하는 케이스 및 탈출 조건을 만족하는 케이스에 대해서 리소스를 정리하는 코드가 중복으로 작성된다.

파이썬의 컨텍스트 매니저는 이러한 리소스를 with문법을 통해 with 절 내에서만 액세스를 가능하게 하고, 블록을 나가는 경우 어떤 이유든간에 리소스를 해제하게 된다.

다음과 같은 구조로 사용한다.

with {expression} as {variable}:
    block..

샘플 코드로 보면..

아래와 같이 파일을 읽고, 종료하는 전통적인 코드를

f = open('myFile.txt', 'w', encoding='utf8')
f.write("test")
f.close()

아래처럼 좀 더 간결하고, 안전하게 사용할 수 있다.

with open('mytextfile.txt', 'r', encoding='utf8') as f:
  f.wirte("test")

이와 같이 사용할 수 있는 이유는 파이썬의 파일핸들러가 내부적으로 ContextManager 프로토콜을 따르고 있기 때문이다. 다시말해, open()을 통해 생성되는 파일 핸들이 아래와 같은 ContextManager 프로토콜을 따르고 있기 때문에 with절에서 사용이 가능하다.

  1. 객체가 생성된 후, with 블럭에 진입하면서 미리 지정된 특정한 작업을 수행한다.
  2. with 블럭을 떠나는 시점에 미리 지정된 특정한 작업을 수행한다.

또한, with문의 장점은 중첩이 가능하다는 점이다. 어느 한 지점에서 문제가 발생하여 모든 블럭을 빠져나와야 하는 상황에서도 가장 하위의 블럭에서부터 파일을 순차적으로 닫고, 안전하게 리소르를 회수할 수 있다. 이는 로직의 전후관계를 추적할 필요가 없으므로 코드를 간결하게 만든다.

컨텍스트 매니저

컨텍스트 매니저는 with 구문에 쓰일 수 있는 객체의 타입이며, Context Manager 프로토콜을 준수한다. 컨텍스트 매니저는 다음 두 개의 메소드를 정의하고 있는 것으로 간주한다.

  • __enter__(self) : with 문에 진입하는 시점에 자동으로 호출
  • __exit__(self, type, value, traceback) : with 문이 끝나기 직전에 자동으로 호출

__exit__() 메소드가 받는 세 개의 인자는 해당 객체와 연관된 컨텍스트 내에서 예외가 발생되었을때, 해당 예외에 관한 정보이다. 예외없이 with 구문이 종료되었다면 이 세 인자는 모두 None이 될 것이다.

DB 세션 예시

sqlalchemy를 통한 postgresql db 접속 클래스이다.

예시이므로 코드의 완성도는 생각하지 않았다.

from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy import create_engine, text

class SessionMaker:    
  def __enter__(self, engine):
    # 초기화가 끝나고 with 블럭에 진입할 때 호출된다.
        # 해당 함수는 하는일이 없더라도 필수로 구현되어야 한다.        
    if self.engine is not None:
      self.session = sessionmaker(autocommit=True, autoflush=True, bind=engine)
    else:
      ## 아직 컨텍스트 블럭에 진입 전이므로, 여기서 발생하는 예외는 밖으로 던져진다.
      raise IOError("Cannot access DB")
  def __exit__(self, e_type, e_value, tb):
    ## with 블럭이 끝나면 session을 닫는다.
    print("Closing Database...")
    self.session.close()
  def getContacts(name):
    query = """SELECT * FROM Contacts WHERE name = ? ;"""
    self.cursor.execute(query, (name,))
    return [dict(x) for x in self.cursor.fetchall()]

그리고 아래와 같이 사용한다.

engine = create_engine('postgresql://postgres:1234/localhost:9999/testDB')
with PostgresConnector(engine) as dbSession:
  dbSession.execute('SELECT * from postgres')

engine은 별도로 생성한다고 가정하고, 이를 파라미터로 넘기면..

with 절에 진입할 때 __enter__ 에 진입해서 session을 생성한다.

with 절 내부에서 쿼리를 실행하고, 빠져나올 때 자동으로 __exit__ 함수가 실행되면서 session을 종료한다.

sqlalchemy에서는 아마 이러한 기능을 수행하는 sessionmaker가 이미 구현되어 있을 것이므로, 가져다가 사용하면 될 것이다.

Ref.

with 구문과 컨텍스트 매니저 · Wireframe

[Python] with문 이해하기

<