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

발행: (2025년 12월 10일 오후 12:46 GMT+9)
6 min read
원문: 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

관련 글

더 보기 »

Part 2: 첫 번째 Django 프로젝트 설정

이것은 “Learn Backend Development by Building a Social Media App” 시리즈의 파트 2입니다. 다시 오신 것을 환영합니다! 이전 파트에서는 백엔드가 실제로 무엇인지 배웠습니다.

Python 2에서 Python 3으로 마이그레이션

Python 2는 2020년 1월에 수명 종료(end‑of‑life)에 도달했지만, 많은 레거시 프로젝트가 여전히 이를 사용하고 있습니다. Python 3로 마이그레이션하는 것은 최신 기능을 활용하고, 더 나은 …