[py] __del__에서 리소스 해제를 하면 안 되는 이유

@codemaru · December 10, 2024 · 4 min read

시작은 mysql 클라이언트였다. 해당 클래스를 사용하는 코드에서 close를 하지 않아서 리소스 릭이 발생하는 문제. pymysql은 왜 이걸 파괴될 때 자동으로 해제하지 않게 만들어서 개발자가 실수하게 만들었지?! 라고 삽질하다 알게 된 이유. 오늘의 교훈: 언어 별로 비슷한 점도 있지만, 다른 점도 많다.

파이썬에서 __init__에서 리소스를 획득하고, __del__에서 리소스를 해제하는 패턴은 피하는 것이 좋습니다. 이유는 다음과 같습니다:


1. __del__ 호출 보장이 없음

  • __del__ 메서드는 객체가 가비지 컬렉션에 의해 삭제될 때 호출되지만, 호출 시점이 보장되지 않습니다.
  • 특히, 프로그램이 종료할 때 전역 객체들의 소멸 순서에 따라 필요한 리소스가 이미 해제된 상태일 수 있습니다.
    • 예: 파일 핸들러, 네트워크 연결 등이 이미 닫혀있는 경우 문제가 발생할 수 있음.

2. 순환 참조 문제

  • 객체가 다른 객체와 순환 참조를 형성하면, 파이썬의 가비지 컬렉터가 __del__을 호출하지 않을 수 있습니다.
    • 파이썬의 순환 참조를 감지하는 가비지 컬렉터는 __del__이 있는 객체를 처리하기 어렵습니다.
    • 결과적으로, 리소스가 영구적으로 해제되지 않을 수 있습니다.

3. 명시적 리소스 관리 부족

  • 리소스 관리가 암묵적으로 이루어져 가독성과 유지보수가 어려워집니다.
    • 리소스 사용자가 객체의 수명을 정확히 제어하지 못합니다.
    • 누군가 __del__ 호출을 의도적으로 방지하거나 놓치면, 리소스 누수가 발생합니다.

4. GC 타이밍에 의존

  • 가비지 컬렉션은 언제 실행될지 보장되지 않습니다.
    • 예를 들어, 리소스를 빨리 해제해야 하는 상황에서 __del__에 의존하면, 리소스가 불필요하게 오래 유지될 수 있습니다.

대안: 컨텍스트 관리자 (with 문법) 사용

컨텍스트 관리자를 통해 리소스를 명시적으로 관리하는 것이 더 안전하고 직관적입니다.

class ResourceHandler:
    def __init__(self, resource):
        self.resource = resource

    def __enter__(self):
        # 리소스 획득
        print(f"{self.resource} 획득")
        return self.resource

    def __exit__(self, exc_type, exc_value, traceback):
        # 리소스 해제
        print(f"{self.resource} 해제")
        # 예외 처리도 가능
        if exc_type:
            print(f"예외 발생: {exc_value}")
        return False  # 예외를 억제하지 않음

# 사용 예
with ResourceHandler("파일") as resource:
    print(f"{resource} 사용 중")

요약

  • __del__ 대신 컨텍스트 관리자나 명시적인 리소스 해제 메서드(close, release 등)를 사용하는 것이 바람직합니다.
  • 이를 통해 리소스 관리가 예측 가능하고, 코드가 더 견고해집니다.
@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중