← 기술 지식

기술 지식 / 프로그래밍 언어 / Python / 실행 환경·동시성

Python import Cache와 부분 초기화

import 이름이 sys.modules에 캐시되는 시점, 순환 import의 부분 모듈, reload와 실패 cleanup을 모듈 객체·외부 참조의 수명으로 추적합니다.

언어·런타임 · 원리·구현·운영

자료 대조일 · 적용 버전과 실행 여부는 본문의 검증 범위에 명시합니다.

기술 지식 트리

Python import는 파일을 매번 처음부터 실행하는 문장이 아니라 이름과 모듈 객체를 연결하는 machinery입니다. 새 모듈은 실행 전에 sys.modules에 삽입되고, 성공 후 외부에서 완전한 객체로 사용됩니다. 이 중간 상태를 알아야 순환 import에서 객체는 있는데 이름이 없고, 초기화 예외 뒤 하위 모듈은 남을 수 있으며, reload가 외부 별칭을 갱신하지 않는 이유를 구분할 수 있습니다.

이름과 정체성

sys.modules의 key는 파일 경로가 아니라 import name입니다. 같은 이름의 모듈이 이미 있으면 일반 import는 그 객체를 다시 실행하지 않고 참조합니다.

import sys
import math
first = sys.modules['math']
import math as second
print(first is second)  # True

이 코드는 설명용 trace입니다. 같은 파일을 다른 package 이름이나 다른 loader 경로로 노출하면 key가 달라져 별도 module identity가 생길 수 있습니다. 따라서 “파일이 같으니 같은 전역 상태”라고 말할 수 없습니다.

삽입과 실행 순서

개념적인 순서는 spec 탐색, module object 생성, sys.modules[name] 삽입, module code 실행입니다. 실행 중인 모듈도 cache에 보이기 때문에 순환 import 상대편이 부분 객체를 받을 수 있습니다. import를 시작한 모듈의 실행이 예외로 끝나면 그 실패한 모듈 entry는 정리되지만, 그 전에 성공적으로 import한 다른 모듈의 entry와 부수 효과까지 일괄 rollback된다는 보장은 없습니다.

T0에 a와 b가 cache에 없습니다. T1 a 객체가 cache에 들어가지만 a.ready 대입 전입니다. T2 a가 b를 import하고, T3 b가 다시 a를 찾으면 object identity는 얻지만 ready는 아직 없습니다. T4 a.ready=True 전에 예외가 나면 a는 실패 cleanup 대상이고 b는 이미 완료했다면 남을 수 있습니다.

순환 import 상태

# file-a.py source
from b import value_b
value_a = 'A'

# file: b.py
from a import value_a
value_b = 'B'

a에서 시작하면 a가 cache에 들어간 뒤 b가 실행됩니다. b의 from a import value_a는 파일을 새로 열기보다 부분 a를 확인합니다. value_a 줄은 아직 실행 전이므로 문제는 module-not-found가 아니라 초기화되지 않은 attribute입니다. package __init__의 재수출이 이 순서를 더 복잡하게 만들 수 있습니다.

해결 우선순위는 공통 타입·상수를 제3 모듈로 옮기고 구현 방향을 단방향으로 만드는 것입니다. 함수 내부 import는 초기화를 지연해 당장의 cycle을 피할 수 있지만 첫 호출 시 오류·지연·숨겨진 의존성이라는 비용을 남깁니다. TYPE_CHECKING은 타입 전용 cycle에는 도움이 되지만 runtime 객체 생성 cycle을 해결하지는 않습니다.

reload와 외부 별칭

importlib.reload(module)은 기존 module object를 재사용하여 그 namespace에서 코드를 다시 실행합니다. 이름을 from service import Handler로 가져온 consumer는 Handler 객체의 참조를 별도로 보관하므로 service namespace의 새 Handler로 자동 재바인딩되지 않습니다. import service 후 매번 service.Handler를 읽는 코드는 module namespace를 다시 볼 수 있습니다.

reload는 dictionary를 유지하므로 새 실행에서 다시 대입하지 않은 이전 이름이 남을 수 있습니다. 새 class를 만들더라도 기존 인스턴스의 __class__, callback, registry는 자동으로 교체되지 않습니다. reload 실패 시 namespace가 일부 새 값과 일부 옛 값의 조합으로 남을 수 있고, 공식 문서도 thread-safe하지 않다고 설명하므로 운영 hot reload는 명시적 lifecycle이 필요합니다.

초기화 실패 잔여 상태

main이 helper를 성공적으로 import한 뒤 top-level에서 예외를 낸 상태를 보겠습니다. 실패한 main은 다음 import가 부분 객체를 재사용하지 않도록 제거되지만, 완전히 초기화된 helper는 cache에 남을 수 있습니다. helper가 logging handler나 registry를 등록했다면 그 side effect도 자동으로 되돌아가지 않습니다.

시점 main helper 해석
T0 없음 없음 첫 요청
T1 부분 객체 없음 main 실행 중
T2 부분 객체 완전 객체 helper 성공
T3 제거 남을 수 있음 main 예외
T4 새 객체 시도 기존 재사용 재시도 경계

따라서 재시도는 helper code를 다시 실행하지 않고 이전 상태를 활용할 수 있습니다. import-time 작업을 멱등적으로 만들거나 명시적 startup/cleanup 함수로 옮겨야 중복 등록을 줄일 수 있습니다.

cache 삭제와 중복 identity

del sys.modules['service'] 뒤 재import하면 이전 module object와 새 module object가 동시에 살아 있을 수 있습니다. 이전 Handler와 새 Handler는 이름과 소스가 같아도 class identity가 다르므로 old instance에 대해 isinstance(old, NewHandler)가 false가 될 수 있습니다. 다른 모듈의 alias, thread callback, global registry도 삭제만으로 바뀌지 않습니다.

importlib.invalidate_caches()는 finder가 directory contents를 다시 확인하도록 하는 기능이며 이미 만들어진 module object를 교체하지 않습니다. plugin 재로딩이라면 새 module 공개 전에 이전 callback 중지, registry 제거, 인스턴스 drain을 순서대로 수행해야 합니다.

검증 절차

작은 재현에서는 import 전후 sys.modules.get(name) is object와 id(module)을 기록합니다. 순환 사례는 각 top-level 대입 직후 marker를 남겨 어느 이름이 비어 있는지 확인합니다. 실패 사례는 주 모듈 entry, 하위 모듈 entry, 외부 registry를 각각 검사합니다. reload는 module identity, member function identity, from-import alias, 기존 instance class를 네 칸으로 나누어 비교합니다.

이 노트의 순서는 Python 3 import reference와 importlib 문서의 일반 계약에 근거합니다. finder·loader·namespace package의 상세는 구성에 따라 달라질 수 있으며 특정 마이너 버전의 내부 최적화나 오류 문구를 고정하지 않습니다.

비용과 설계 선택

top-level 네트워크 연결·설정 파싱·registry 등록은 import 실패를 프로세스 상태로 확장합니다. 지연 초기화는 첫 요청 지연과 동시성 경합을 만들지만 상태 전이와 lock을 명시할 수 있습니다. cycle을 함수 내부 import로 숨기는 편법은 단기적으로는 빠르나 의존 그래프와 실패 위치를 감춥니다.

reload가 필요한 plugin은 module cache보다 lifecycle이 핵심입니다. 새 버전이 준비되기 전 old version을 계속 서비스하고, 참조 교체·in-flight 요청 종료·실패 rollback을 별도 단계로 두는 편이 안전합니다.

참고 자료

import 부분 상태import 요청: 이름 해석. sys.modules 삽입: 부분 객체. 모듈 코드 실행: top-level 대입. 완전 초기화: 사용 가능한 상태. 실패 cleanup: 주 모듈 entry 제거새 이름이면 생성실행 시작성공 완료예외 시 실패 entryimport 요청이름 해석sys.modules 삽입부분 객체모듈 코드 실행top-level 대입완전 초기화사용 가능한 상태실패 cleanup주 모듈 entry 제거
import 부분 상태 새 모듈은 실행 전에 cache에 들어가며 성공 완료 전까지는 외부에서 부분 상태로 관찰될 수 있습니다.
그림을 글로 읽기
  • import 요청: 이름 해석
  • sys.modules 삽입: 부분 객체
  • 모듈 코드 실행: top-level 대입
  • 완전 초기화: 사용 가능한 상태
  • 실패 cleanup: 주 모듈 entry 제거
  1. import 요청 → sys.modules 삽입: 새 이름이면 생성
  2. sys.modules 삽입 → 모듈 코드 실행: 실행 시작
  3. 모듈 코드 실행 → 완전 초기화: 성공 완료
  4. 모듈 코드 실행 → 실패 cleanup: 예외 시 실패 entry

후속·관련 개념

함께 읽을 문서

분야별 학습 경로

전체 기술 지식