3 minute read

1. 파이썬 with statement의 소개와 사용법

파이썬의 with statement은 예외 처리에 사용되어 코드를 더 깔끔하고 가독성 있게 만든다. 보통 file stream, lock, database 연결과 같은 일반적인 리소스의 관리를 간소화한다.

파일 처리를 예로 들어보면, 일반적으로 파일 작업을 할 때는 오류가 발생하더라도 작업을 마친 후에 파일이 제대로 닫혔는지 수동으로 확인해야 하지만, with 문을 사용하면 예외가 발생하더라도 중첩된 코드 블록이 실행되면 파이썬이 자동으로 파일을 닫는 방법을 제공하여 이 작업을 간소화할 수 있다.

with statement 사용법

with [expression] as [변수명]

with statement의 기본 형태는 위와 같다.

예시

with open('file.txt', 'r') as file:
    data = file.read()
    print(data)



2. with statement의 내부 동작 이해

with statement은 컨텍스트 매니저 객체를 이용하기에 이를 먼저 살펴보자.

Context Manager

컨텍스트 매니저는 runtime context를 정의하는 객체이며 with statement을 실행할 때 사용된다. 컨텍스트 매니저는 코드 블록의 실행을 위해 원하는 런타임 컨텍스트로의 진입과 퇴출을 처리한다. 컨텍스트 매니저는 보통 with statement을 사용하여 호출되지만 메소드를 직접 호출하여 사용할 수도 있다.

  • 컨텍스트 매너지는 일반적으로 전역 상태 저장 및 복원, 리소스 잠금 및 잠금 해제, 열린 파일 닫기 등이 있다.


Context란 컴퓨터에서 context란 어떤 작업을 처리할 때 필요한 정보를 의미한다. 예를 들어, 함수가 호출되어 새로운 컨텍스트가 생성된다는 것은 변수 값과 호출 스택의 상태 등을 의미한다. context switching에서의 컨텍스트는 CPU의 레지스터 값, 프로세스 카운터, 시스템 메모리 등을 말한다. 일상생활에서의 예시는 치과에 간다고 가정한다면 접수원이 이름을 묻는 것은 예약을 하기 위해 필요한 context 정보이다.

__enter__, __exit__ 메소드

object.__enter__(self) 메소드는 이 메소드를 선언할 클래스의 객체와 관련된 런타임 컨텍스트를 입력한다. with statement은 이 메소드의 반환값을 as문의 지정된 대상(지정된 대상이 있는 경우)에 바인딩한다.

object.__exit__(self, exc_type, exc_value, traceback) 메소드는 이 메소드를 선언할 클래스의 객체와 관련된 런타임 컨텍스트를 종료한다. 메소드의 매개변수는 컨텍스트가 종료된 원인이 된 예외를 설명한다. 예외 없이 컨텍스트가 종료된 경우 세 매개변수 모두 None이 된다.


하나의 item이 있는 with statement의 실행

  1. 컨텍스트 expression(with_item에 주어진 표현식)을 평가하여 컨텍스트 매니저를 가져온다.
  2. 컨텍스트 매니저의 __enter__()가 나중에 사용할 수 있도록 로드된다.
  3. 컨텍스트 매니저의 __exit__()는 나중에 사용할 수 있도록 로드된다.
  4. 컨텍스트 매니저의 __enter__() 메서드가 호출된다.
  5. with 문에 대상이 포함된 경우 __enter__()의 반환값이 해당 대상에 할당된다.
  6. suite가 실행된다.
  7. 컨텍스트 관리자의 __exit__() 메서드가 호출된다. 예외로 인해 집합이 종료된 경우 해당 유형, 값 및 트레이스백이 __exit__()의 인수로 전달된다. 그렇지 않으면 세 개의 None 인수가 제공된다.
    • exceotion으로 인해 suite가 종료되었는데 __exit__() 메서드의 반환 값이 false인 경우 예외가 다시 발생한다. 반환 값이 참이면 예외가 억제되고 with 문 뒤에 오는 문으로 실행이 계속됩니다.

    • exception이 아닌 다른 이유로 suite가 종료된 경우 __exit__()의 반환 값은 무시되고 종료된 종류의 정상 위치에서 실행이 진행됩니다.

참고: with 문은 __enter__() 메서드가 오류 없이 반환되면 항상 __exit__()가 호출되도록 보장한다. 따라서 대상 목록에 할당하는 동안 오류가 발생하면 suite(with의 코드부분)내에서 발생하는 오류와 동일하게 처리된다.

이 코드는

with EXPRESSION as TARGET:
    SUITE

아래 코드와 같다.

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)


사용자 정의 class에 Context manager의 구현

공식문서해당블로그를 보면 이해가 잘된다. with statement으로 사용하고 싶은 객체가 있으면 해당 객체의 클래스에 __enter____exit__ 메소드를 구현해놓으면 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()
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)



3. 궁금증 해결

gzip에서 __enter__()__exit__() 메소드의 구현 위치

with gzip.open(file_path, 'rb') as f:
        labels = np.frombuffer(f.read(), np.uint8, offset=8)

파이썬에서 open gzip.open메소드를 사용할 때 with statement를 사용하는데, 그럼 __enter__(), __exit()__ 메소드가 어딘가에 구현되어 있다는 소리이다. 이것을 찾아보고 싶어 GzipFile 클래스의 상속구조를 계속 타고 올라가며 확인해보여도 찾을 수 없었다.

class IOBase(_io._IOBase, metaclass=abc.ABCMeta):
    __doc__ = _io._IOBase.__doc__

class RawIOBase(_io._RawIOBase, IOBase):
    __doc__ = _io._RawIOBase.__doc__

class BufferedIOBase(_io._BufferedIOBase, IOBase):
    __doc__ = _io._BufferedIOBase.__doc__

class TextIOBase(_io._TextIOBase, IOBase):
    __doc__ = _io._TextIOBase.__doc__

상속구조의 끝에는 마지막으로 IOBase가 나왔는데, IOBase에도 역시 __enter__()메소드와 __exit__()메소드의 형태도 보이지 않았다. (실 구현부는 C에 있겠지만, 함수 템플릿이라도 있을 줄 알았는데 존재하지 않았다.)

찾아본 결과 파이썬 공식 사이트에서 파이썬 전체의 소스코드를 다운받아볼 수 있었으며, 파이썬의 C 구현부에서 메소드들을 찾을 수 있었다.

image

  • 이것이 enter메소드와 exit메소드의 구현부이며, PyObject*타입을 반환하는 static 메소드의 형태로 구현되어 있었다.

이것이 파이썬에서 어떻게 호출되는지에 관한 과정은 공식문서에서 확인할 수 있습니다.




출처

파이썬 공식문서 - compound statement
스택오버플로
점프 투 파이썬 with 사용예시
https://projooni.tistory.com/entry/Python-with%EC%A0%88-%EB%AC%B8%EB%B2%95%EC%9D%98-%EC%9D%B4%ED%95%B4
https://ybworld.tistory.com/116
https://wikidocs.net/16078
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers

context의 뜻

https://stackoverflow.com/questions/72456914/what-does-context-in-def-myfunccontext-mean-in-python
https://stackoverflow.com/questions/6145091/the-term-context-in-programming

Categories:

Updated: