Python에서 with 구문은 내용을 열어서 사용할때, Airflow DAG를 정의할때 등...자연스럽게 코드에서 찾을 수 있습니다. 어떤 포인트 때문에 with와 붙여서 사용하는건지 정리해보면 python의 장점을 잘 활용할 수 있습니다.
with 구문은 자원(파일, 네트워크 연결, DB 연결 등)의 관리를 간소화하고 안전하게 수행할 수 있도록 돕는 표현입니다. 특히 파일 작업이나 Airflow의 DAG과 같이 반드시 자원을 닫아야 하는 상황에서 유용하며, 코드의 가독성을 높이는 데도 큰 역할을 합니다. 이 글에서는 그 개념과 사용법, 그리고 유용한 활용 예제를 통해 with 구문이 왜 중요한지 알아보겠습니다.
공식문서는 'with문은 context manager 객체의 제어를 받아서 임시로 context를 생성하고 신뢰성 있게 해제한다.'라고 설명합니다. 우선 반복해서 나오게 될 context manager 개념에 대해서 짚고 가겠습니다.
Context?
코드가 실행되는 환경 또는 상태를 의미합니다. 즉, with구문에서 context는 코드가 특정 자원(파일, 데이터베이스 연결, 네트워크 소켓 등)을 사용하기 위한 진입과 종료를 관리하는 환경을 뜻합니다. 주로 간단하게 볼 수 있는 예시인 파일을 열고 닫는 작업에서 context는 파일을 여는 시점부터 닫을 때까지입니다.
Context Manager?
https://docs.python.org/3/reference/datamodel.html#context-managers
Context Manager는 이러한 context의 진입과 종료 시점을 관리하는 객체입니다. with구문이 시작할때 자원을 초기화하고 해당 블록이 끝나면 자원을 정리하는 작업을 수행한다고 볼 수 있습니다. 즉, 블록 시작시 관리가 시작되고 블록이 끝날때 관리도 끝납니다.
enter(), exit()
with구문의 시작과 끝에서 다음 매직 메서드를 통해서 컨텍스트 매니저에 도입할 수 있습니다.
with 구문 개념과 필요성
자원 관리는 프로그래밍에서 중요한 부분입니다. 예를 들어, 파일을 열어 데이터를 읽고 쓸 때, 작업이 끝난 후 파일을 반드시 닫아야 합니다. 만약 코드가 에러를 일으키거나 예외가 발생하면 close() 메서드가 호출되지 않아 메모리 누수나 데이터 손상이 발생할 수 있습니다. with 구문은 이를 방지하며, 코드 블록을 벗어날 때 자원을 자동으로 정리해 줍니다.
with구문 없이 구현할 때
# 일반적인 파일 열기와 닫기 방식
file = open('data.txt', 'r')
try:
data = file.read()
finally:
file.close()
with 구문을 사용한 파일 열기와 닫기
with open('data.txt', 'r') as file:
data = file.read()
위 코드는 with 구문 덕분에 file.close()를 명시적으로 호출하지 않아도 됩니다. 블록이 끝나면 자동으로 파일이 닫히기 때문에 안전하고 간결하게 작성할 수 있습니다.
with 구문 예시
connect db
import sqlite3
with sqlite3.connect('example.db') as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()
데이터베이스 연결에서도 with 구문을 사용하면 연결을 자동으로 닫을 수 있어 자원 누수를 방지할 수 있습니다.
File IO
with open('output.txt', 'w') as file:
file.write("Hello, Python!")
Context Manger in File IO
예시로 들었던 with open()으로 파일을 열고 닫는 과정의 내부 로직은 cpython 소스 코드를 통해서 볼 수 있습니다.
with open() -> TextIOWrapper -> TextIOBase -> IOBase -> enter() -> with block end - exit()
Airflow DAG
https://airflow.apache.org/docs/apache-airflow/2.0.0/concepts.html#context-manager
Airflow 1.8이상부터 context manager를 사용해서 자동으로 새로운 오퍼레이터를 DAG에 추가할 수 있습니다.
with DAG(
dag_id="my_dag_name",
start_date=datetime.datetime(2021, 1, 1),
schedule="@daily",
):
task1 = EmptyOperator(task_id="task")
task2 = PythonOperator(task_id="task")
task1 >> task2
...
with DAG(...) as dag: 구문을 통해 DAG 객체와 그 하위 태스크들을 한 블록 내에서 관리할 수 있는 코드 예시를 볼 수 있습니다. with 구문이 끝나면 dag 객체가 자동으로 생성되고, 정의된 태스크와 의존성 정보가 DAG에 포함됩니다.
https://stackoverflow.com/questions/60114399/is-there-a-benefit-to-use-the-with-dag-as-dag-clause-to-create-a-dag
Airflow DAG 생성시 with문을 사용하여 선언하는 장점에 대한 질문에 대한 답변은 위에서 다룬 with구문의 특징입니다.
DAG 클래스의 인스턴스를 생성할때 관리되지 않는 리소스(operator 등)를 자동으로 정리할 수 있습니다. 또한 예외가 발생해도 제대로 정리할 수 있는데 이는 try/except 블록을 사용해서 수동으로 처리하지 않아도 with를 사용해서 간단하게 구현할 수 있는 점입니다.
Context Manager in Airflow DAG
with절에서 context관리자에 진입 -> enter 호출 -> with 블록 마지막 행이 끝나면 context 종료되면서 -> exit 호출
class DAG(LoggingMixin):
# Context Manager -----------------------------------------------
def __enter__(self):
DagContext.push_context_managed_dag(self)
return self
def __exit__(self, _type, _value, _tb):
DagContext.pop_context_managed_dag()
# /Context Manager ----------------------------------------------
with 구문은 자원 관리를 간소화하고 예외 발생시에도 자원을 안전하게 정리해주는 장치입니다. 특히 파이썬을 이용해서 데이터를 처리하는 예제나 분석에서 파일을 여는 코드는 매우 흔합니다. open() 메서드와 거의 붙어서 오는 with의 존재가 익숙하기만 했다면 이제는 그냥 따라오는 구문이 아니라는 것을 알 수 있습니다:)