CS Repository/기초 강화학습

python - from A import B와 import A.B as b의 차이

조금씩 차근차근 2025. 8. 25. 13:34

과거 모듈 내 객체를 싱글톤처럼 사용하려던 기억이 있었는데, 이 둘의 차이를 구분하지 못해 고생했던 기억이 있다.
이번 기회에 글로 정리해두었다.
하나씩 알아보자.

시작하기 전에 - 모듈, 패키지, 라이브러리

모듈

모듈은 파이썬 파일을 의미한다.
특히 다른 파이썬 프로그램에서 import하여 사용하는 것을 가정하고 만들어진 파이썬 파일을 '모듈'이라 한다.
모듈은 import 시 sys.modules 딕셔너리에 보관되게 된다.

패키지

패키지는 여러 모듈을 묶은 것이다.
패키지를 만들려면 먼저 디렉터리를 만들고, 그 안에 모듈과 __init__.py 파일을 추가한다.

라이브러리

라이브러리는 여러 패키지를 묶은 것이다. 그래서 하나 이상의 디렉터리로 구성된다.
때로는 패키지를 가리켜 '라이브러리'라고 부르기도 한다.

import 동작 방식

파이썬은 기본적으로 모든 데이터를 객체 형태로 저장한다.

즉, 모듈 정보, 클래스 정보, 값, 함수, 그 외 모든 것을 객체 형태로 저장한다는 것이다.

 

즉 런타임에서 클래스 정보를 동적으로 바꾸는 것도 가능하고, 참조가 바뀌는 것 또한 가능하다.

 

즉 이제부터, 참조와 그 객체의 갱신 가능성에 집중해서 읽길 바란다. 

파이썬의 이름 바인딩 기본

  • 파이썬에서 이름은 객체에 대한 레퍼런스(참조)일 뿐, 복사본이 아니다.
  • import는 모듈 객체를 만들고(최초 1회 실행) 그 객체에 대한 참조를 로컬 네임스페이스에 바인딩한다.
  • 한 번 로드된 모듈은 sys.modules 캐시에 저장되어 재사용된다.
import sys, math
sys.modules["math"] is math   # True  ← math 모듈 객체는 한 군데에만 존재

import B as b 의 메모리 동작

  • 탑레벨 모듈/패키지 B의 모듈 객체가 생성(혹은 sys.modules에서 재사용)되고, 로컬 이름 b → 그 모듈 객체로 바인딩된다.
  • 로컬에서 b는 언제나 모듈 객체이다. 그 안의 속성은 접근할 때마다 현재 모듈 상태를 조회한다.
import concurrent.futures as cf 
import sys 
cf is sys.modules["concurrent.futures"]   # True
  • del b는 로컬 이름만 삭제한다.
    • sys.modules에 남아 있는 한, 모듈 객체는 계속 살아있다(다른 참조도 있을 가능성 있기 때문).

from A import B 의 메모리 동작 (두 경우)

A를 로드한 뒤, B하위 모듈이냐 속성(함수/클래스/변수)이냐에 따라 다르다.

B하위 모듈인 경우 (A.B)

  • import 시스템이 A.B를 모듈로 로드하고 sys.modules["A.B"]에 넣는다.
  • 로컬 이름 B는 그 모듈 객체로 바인딩됩니다.
  • 이때 A라는 로컬 이름은 생기지 않는다(따로 import A 하지 않는 한).
from concurrent import futures 
import sys 
futures is sys.modules["concurrent.futures"]  # True # 'concurrent'는 로컬에 없음

B가 속성(객체)인 경우

  • A 모듈 객체에서 그 시점의 A.B 값을 읽어 로컬 이름 B에 그 객체 참조를 바인딩한다.
  • 이후 A.B가 다른 객체로 재할당되어도, 로컬 B는 예전 객체를 계속 가리킨다(스냅샷처럼).
from math import pi    # pi(=3.1415...) "객체"에 대한 참조를 로컬에 바인딩 
import math 
math.pi = 3.0          # math 모듈의 pi 이름을 새 float로 "재바인딩" 
pi                      # 여전히 3.1415...  ← 로컬 이름은 옛 참조를 유지

객체의 “재할당(rebind)”과 “변경(mutate)”을 구분하자.
from A import x로 가져온 x가 리스트·딕셔너리처럼 가변 객체라면,
그 객체를 변경하면 A.x에서도 같은 변경이 보인다.

# 가변 객체 케이스(개념 예시) 
from A import buf   # buf는 A.buf와 "같은 리스트 객체"를 가리킴
import A 
buf.append(1)       # 객체 "수정" 
A.buf               # [1]  ← 같은 객체 변경이 반영 
A.buf = []          # 이름을 "새 리스트"로 재바인딩 
buf                 # [1]  ← 로컬은 여전히 옛 객체를 가리킴

4) “참조 스냅샷” vs “지연 조회” 관점

  • from A import name(속성 케이스) = 그 순간의 객체 참조 스냅샷을 로컬에 저장
  • import A as a(또는 import A.B as b) = 항상 모듈을 통해 현재 속성을 조회

이 차이는 재로드/핫리로드 때 특히 중요해진다.

import importlib, A 
from A import func      # func는 현재 함수 객체에 대한 "고정 참조" 
importlib.reload(A)     # A를 다시 실행하면 A.func는 "새 함수 객체"가 됨 
func is A.func          # False (로컬 func는 옛 참조를 유지)

5) sys.modules와 수명

  • 모든 로드된 모듈은 sys.modules[정규모듈이름] → 모듈 객체로 캐시된다.
  • 모듈이 GC되려면
    1. sys.modules에서 제거되고,
    2. 다른 모든 참조도 사라져야 한다.
    • 일반 코드에서 자동으로 제거되진 않으므로, 보통 프로세스 수명 동안 유지된다.

6) 실무 가이드 (메모리/참조 안정성 관점)

  • 속성의 최신 상태에 의존하거나 핫리로드가 있을 수 있다면
    from A import B(속성) 대신 import AA.B 접근을 수행하자.
  • 하위 모듈 전체를 쓰고 싶다면
    import A.B as ab 형태로 모듈 객체에 별칭을 붙여 사용하기.
  • 이름 충돌/섀도잉을 피하고 출처를 명확히 하려면
    → 접두사(모듈 별칭)를 유지하는 스타일이 안전하다.