시작은 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
등)를 사용하는 것이 바람직합니다.- 이를 통해 리소스 관리가 예측 가능하고, 코드가 더 견고해집니다.