[CPython] PEP 810 -- CPython에 명시적 Lazy Import 구현
PR 링크: python/cpython#142351 상태: Merged | 변경: +5126 / -197
들어가며
대규모 Python 애플리케이션은 깊은 의존성 트리 때문에 시작 시간이 수 초에 달하는 문제가 있다. 실행 중 실제로 사용하지 않는 모듈까지 모두 로드하기 때문이다. 기존 해결책은 함수 내부로 import를 옮기거나 importlib를 직접 사용하는 것이었는데, 이는 코드 가독성을 해치고 유지보수 부담을 늘린다. PEP 810은 lazy라는 새 soft keyword를 도입해 import 시점을 최초 사용 시점까지 지연시키는 공식 메커니즘을 제공한다.
핵심 코드 분석
Grammar 변경: lazy soft keyword 추가
Before:
# Grammar/python.gram
import_name[stmt_ty]: 'import' a=dotted_as_names { _PyAST_Import(a, EXTRA) }
import_from[stmt_ty]:
| 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
_PyPegen_checked_future_import(p, b->v.Name.id, c, _PyPegen_seq_count_dots(a), EXTRA) }
After:
# Grammar/python.gram
import_name[stmt_ty]:
| lazy="lazy"? 'import' a=dotted_as_names { _PyAST_Import(a, lazy ? 1 : 0, EXTRA) }
import_from[stmt_ty]:
| lazy="lazy"? 'from' a=('.' | '...')* b=dotted_name 'import' c=import_from_targets {
_PyPegen_checked_future_import(p, b->v.Name.id, c, _PyPegen_seq_count_dots(a), lazy, EXTRA) }
Grammar 레벨에서 lazy를 optional prefix로 추가했다. lazy는 soft keyword이므로 기존 변수명 lazy를 사용하는 코드에 영향을 주지 않는다. parser가 import 또는 from 앞에 lazy가 올 때만 키워드로 인식한다.
AST 구조 변경: is_lazy 필드
Before:
struct {
asdl_alias_seq *names;
} Import;
struct {
identifier module;
asdl_alias_seq *names;
int level;
} ImportFrom;
After:
struct {
asdl_alias_seq *names;
int is_lazy;
} Import;
struct {
identifier module;
asdl_alias_seq *names;
int level;
int is_lazy;
} ImportFrom;
AST의 Import와 ImportFrom 노드 모두에 is_lazy 필드가 추가되었다. Compiler는 이 플래그를 확인해 lazy import 전용 opcode 경로를 생성한다.
LazyImport Proxy 객체
// Include/internal/pycore_lazyimportobject.h
typedef struct {
PyObject_HEAD
PyObject *lz_builtins;
PyObject *lz_from;
PyObject *lz_attr;
PyCodeObject *lz_code;
int lz_instr_offset;
} PyLazyImportObject;
lazy import가 실행되면 실제 모듈 대신 PyLazyImportObject proxy가 생성되어 이름에 바인딩된다. 이 proxy는 최초 attribute 접근 시 실제 모듈을 로드하고 자신을 대체한다. lz_code와 lz_instr_offset을 저장해서 에러 발생 시 원래 import 위치를 traceback에 포함할 수 있다.
글로벌 모드 제어: sys API와 환경변수
# 사용 예시
import sys
def myapp_filter(importing, imported, fromlist):
return imported.startswith("myapp.")
sys.set_lazy_imports_filter(myapp_filter)
sys.set_lazy_imports("all")
import myapp.slow_module # lazy (filter 통과)
import json # eager (filter 거부)
-X lazy_imports=all 옵션이나 PYTHON_LAZY_IMPORTS 환경변수로 소스 코드 수정 없이 전역적으로 lazy import를 활성화할 수 있고, filter 함수로 모듈별 세밀한 제어가 가능하다.
왜 이게 좋은가
- 시작 시간 단축: 사용하지 않는 모듈의 로드를 완전히 건너뛴다. 의존성이 깊은 CLI 도구나 웹 프레임워크에서 체감 효과가 크다.
- 코드 구조 유지: import문을 파일 상단에 유지하면서도 지연 로딩의 이점을 얻는다. 함수 내부 import 패턴이 불필요해진다.
- 점진적 도입: soft keyword이므로 기존 코드와 완전히 호환된다.
lazy라는 변수명을 쓰는 기존 코드도 영향받지 않는다. - Cycle 감지:
ImportCycleError라는 새 예외를 추가해 lazy import가 자기 자신을 재귀적으로 import하려 할 때 명확한 에러를 제공한다.
정리
PEP 810은 Python의 import 시스템에 lazy soft keyword를 도입해 명시적 lazy import를 가능하게 한다. Grammar, AST, Compiler, Interpreter(opcode) 전 레이어에 걸친 대규모 변경이며, proxy 객체 기반의 투명한 지연 로딩 메커니즘을 구현했다. 대규모 애플리케이션의 시작 시간 최적화에 실질적인 해법을 제공하면서도 기존 코드와의 완전한 하위 호환성을 유지한 설계가 인상적이다.
참고 자료
- PEP 810 -- Lazy Imports -- Lazy import 공식 제안서
- What's New in Python 3.15 -- 3.15 릴리스 노트
알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [Open WebUI] 모델 캐시 활용으로 TTFT(첫 토큰 도달 시간) 대폭 단축
- 현재글 : [CPython] PEP 810 -- CPython에 명시적 Lazy Import 구현
- 다음글 [Ultralytics] IMX 벤치마크에 세그멘테이션 모델 지원
댓글