파이썬의 비밀스러운 삶: Import 시스템

발행: (2025년 12월 10일 오후 12:46 GMT+9)
6 분 소요
원문: Dev.to

Source: Dev.to

파이썬이 코드를 찾는 방법 (그리고 왜 가끔 사라지는지)

Timothy는 의자에 몸을 기대고, 같은 문제를 3시간째 디버깅하고 있는 사람의 지친 좌절감으로 터미널을 바라보았다.

“이해가 안 돼요,” 라며 그는 스크립트를 백번 째 정도 실행했다. “파일을 삭제했어요. old_utils.py를 프로젝트 디렉터리에서 물리적으로 지웠어요. 터미널도 다시 시작했고요. 그런데 코드를 실행하면…”

import utils

print(utils.get_version())
# Output: "Version 1.0 - DEPRECATED"

“아직도 옛 버전을 로드하고 있어요! 파일은 더 이상 존재하지 않는데!”

Margaret가 다가와서 알듯한 미소를 지었다. “당신의 파이썬이 귀신에 씌어진 게 아니라, 기억력이 아주 좋을 뿐이에요. 당신은 import 시스템과 싸우고 있는데—규칙을 몰라서 지고 있는 거죠.”

캐시: sys.modules

import utils를 입력하면 파이썬은 먼저 무엇을 할까요?

“내 디렉터리에서 utils.py를 찾는 건가요?”

파이썬은 게으릅니다—가능하면 일을 피하려고 합니다. 파일 시스템을 검색하기 전에 sys.modules라는 사전을 확인합니다.

import sys

print(type(sys.modules))   # <class 'dict'>
print(len(sys.modules))     # e.g., 347 (varies)

# Show a few entries
for name in list(sys.modules.keys())[:10]:
    print(name)
# sys, builtins, _frozen_importlib, _imp, _thread, ...

sys.modules는 모듈 이름을 모듈 객체에 매핑합니다. 'utils'가 이미 키로 존재한다면 파이썬은 파일 시스템에 손대지 않고 바로 그 캐시된 객체를 반환합니다.

import sys
import json

print('json' in sys.modules)          # True
print(sys.modules['json'])            # <module 'json' from '.../json/__init__.py'>
print(json.dumps({'test': 'data'}))   # {"test": "data"}
print(dir(sys.modules['json'])[:5])   # ['JSONDecodeError', 'JSONDecoder', ...]

이 캐시는 프로세스가 살아 있는 동안 유지되므로, 모듈 소스 파일을 수정해도 인터프리터를 재시작하거나 캐시를 새로 고치기 전까지는 변경 사항이 보이지 않습니다.

문제 시연

# Create a simple module file
with open('example.py', 'w') as f:
    f.write('''
def greet():
    return "Hello, version 1"
''')

import example
print(example.greet())   # Hello, version 1

# Modify the file
with open('example.py', 'w') as f:
    f.write('''
def greet():
    return "Hello, version 2"
''')

# Re‑import (still cached)
import example
print(example.greet())   # Hello, version 1
print('example' in sys.modules)  # True

모듈 다시 로드하기

위험한 방법: sys.modules에서 삭제 ❌

import sys

del sys.modules['example']   # Remove cached entry

import example               # Reloads from disk
print(example.greet())       # Hello, version 2

왜 위험한가: 프로그램의 다른 부분이 아직 오래된 모듈 객체에 대한 참조를 가지고 있을 수 있어, 일관성 없는 동작을 초래합니다.

import sys
if 'example' in sys.modules:
    del sys.modules['example']

# Write version 1
with open('example.py', 'w') as f:
    f.write('def greet(): return "Version 1"')

import example as ex1
from example import greet as greet1

# Replace file with version 2 and delete cache
del sys.modules['example']
with open('example.py', 'w') as f:
    f.write('def greet(): return "Version 2"')

import example as ex2
from example import greet as greet2

print(greet1())   # Version 1 (old reference)
print(greet2())   # Version 2
print(ex1 is ex2) # False

올바른 방법: importlib.reload()

import importlib
import example

# Edit the file to version 2
with open('example.py', 'w') as f:
    f.write('def greet(): return "Version 2"')

importlib.reload(example)   # Updates the existing module object
print(example.greet())      # Version 2
print(id(example))          # Same memory address as before

reload()는 모듈을 제자리에서 업데이트하므로 기존 참조도 새로운 코드를 보게 됩니다. 하지만 reload 이전에 from example import greet와 같이 가져온 경우는 여전히 옛 함수 객체를 가리킵니다.

경로 지도: sys.path

모듈이 아직 캐시되지 않았다면 파이썬은 sys.path에 정의된 디렉터리 목록을 사용해 디스크에서 검색합니다.

import sys
print(sys.path)

보통 포함되는 항목은 다음과 같습니다:

  1. 파이썬 인터프리터를 실행한 스크립트가 위치한 디렉터리.
  2. PYTHONPATH 환경 변수에 지정된 경로(설정된 경우).
  3. 표준 라이브러리 디렉터리.
  4. site‑packages(서드파티 라이브러리).

파이썬은 첫 번째로 일치하는 항목을 찾으면 바로 멈춥니다. sys.path를 조작해(예: 프로젝트의 src 폴더를 앞에 삽입) 어떤 버전의 모듈이 import될지 제어할 수 있습니다.

import sys
sys.path.insert(0, '/path/to/my/project')
import mymodule   # 이제 파이썬은 '/path/to/my/project'를 가장 먼저 탐색합니다

핵심 정리:
sys.modules는 파이썬이 메모리 상에 유지하는 import된 모듈 캐시이며, import를 빠르게 하지만 오래된 코드가 남아 있을 수 있습니다. 안전하게 다시 로드하려면 importlib.reload()를 사용하고, 디스크에서 모듈을 찾을 때는 sys.path를 유념하세요. import 문제를 디버깅할 때는 항상 모듈이 이미 캐시되어 있는지 확인하는 것이 좋습니다.

Back to Blog

관련 글

더 보기 »

팰린드롬 검사기

팔린드롬이란 무엇인가? 팔린드롬은 단어, 구절, 숫자 또는 기타 문자 시퀀스로, 공백, 구두점 및 대소문자를 무시하고 앞뒤가 동일하게 읽히는 것을 말한다.