앞서, 1편에서는 module/package/import에 대해서 정리했다.
2편에서는 import search 및 path 에 대해서 정리해본다.
우리가 package/module 등을 import 하면, 파이썬은 해당 파일들을 정확하게 찾아야 한다. 파이썬이 파일을 찾기 위해 3곳을 방문하다.
1️⃣ sys.modules
먼저 sys
는 파이썬에 내장된 module로, interpreter가 제공하는 변수/함수들에 직접 접근 및 제어할 수 있게 해준다. 아래는 sys.version
을 통해 python intepreter version 정보 등을 출력한 것이다.
import sys
print(sys.version)
> 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) [Clang 13.0.0 (clang-1300.0.29.30)]
출처: https://www.geeksforgeeks.org/python-sys-module/
일단 sys
에 대한 간단한 정보는 여기까지.
sys.modules
는 import된 module/package를 저장하고 있는 dictionary다.
(출력하면 { }
로 파일 정보가 묶임)
결국 파이썬이 이미 import된(혹은 이미 사용되고 있는) 파일을 확인해주는 것이지 새로 import한 내용을 찾아줄 수는 없다.
2️⃣ built-in modules
built-in module은 파이썬 공식 라이브러리를 통해 제공되는 것이다.
sys
가 built-in module 중 하나다.
3️⃣ sys.path
sys.path
는 각 파일의 경로를 string list 형태([ ]
)로 갖고 있는 곳으로,
아래와 같이 탐색할 수 있는 모든 경로를 보여준다.
['',
'/Users/song-eun-u/anaconda3/bin',
'/Users/song-eun-u/anaconda3/lib/python36.zip',
'/Users/song-eun-u/anaconda3/lib/python3.6',
'/Users/song-eun-u/anaconda3/lib/python3.6/lib-dynload',
'/Users/song-eun-u/anaconda3/lib/python3.6/site-packages',
'/Users/song-eun-u/anaconda3/lib/python3.6/site-packages/aeosa',
'/Users/song-eun-u/anaconda3/lib/python3.6/site-packages/IPython/extensions',
'/Users/song-eun-u/.ipython']
파이썬은 여기서 하나하나 확인해가며 import하려는 module/package 위치를 찾는다.
그래서 import하고 싶은 module이 이 안에 있다면 다 불러올 수 있는 것.
sys.path.append
를 통해 탐색하고 싶은 디렉토리를 추가할 수도 있다.
그리고pip
를 통해 설치된 외부 module도 자동으로 site-package라는 directory 에 저장되고, site-package는 다시 sys.path
에 저장된다.
최종적으로 찾을 수 없다면, ModuleNotFoundError
를 반환한다.
sys.path
는 외부 module의 경로도 저장하고, 현재 디렉토리 (current directory)도 default로 저장한다. 그래서 절대 경로 (absolute path)를 통해 모듈을 import 할 때 현재 디렉토리로부터 경로를 시작한다.
📍 그렇다면, 절대 경로(absolute path)과 상대 경로(relative path)는 무엇인가?
외부 module을 import할 때, 절대 경로는 무조건 현재 디렉토리(프로젝트들의 최상단 디렉토리)에서 시작한다. 아래와 같이 프로젝트 디렉토리가 만들어졌다고 가정해보자.
└── my_app
├── main.py
├── package1
│ ├── module1.py
│ └── module2.py
└── package2
├── __init__.py
├── module3.py
├── module4.py
└── subpackage1
└── module5.py
절대 경로를 통해 module5.py를 import하고 싶다면, 아래와 같이 작성한다.
from package2.subpackage1.module5 import function2
그런데 하위 module이 많을 경우, 계속 .
으로 연결해야 하므로 너무 길어질 수 있다.
이런 경우 절대 경로 대신 상대 경로(relative path)가 더 효율적이다.
상대 경로는 현재 디렉토리가 아닌 import하는 파일(위치)에서 시작한다.
예를 들어, 현재 import하려는 파일은 module3.py 이고, subpackage1의 module5.py 에 있는 function2를 갖고 온다고 해보자.
from .subpackage1.module5 import function2
처음 나오는 .
은 현재 위치를 말하는 것이고, ..
은 상위 디렉토리를 말한다.
좀 더 풀어 설명하면, 'import하려는 module이 있는 package와 과 형제관계에 있으면 .
, 한단계 위 부모 디렉토리로 가야하면 ..
을 작성해야 한다.
# subpackage1/module5.py에서 class4를 import하려면
from ..module4 import class4
만약 같은 package안에 있는 형제관계의 module끼리라면, .
을 사용할 필요 없다.
# module3.py에서 module4의 class4를 import하려면
from module4 import class4
상대 경로는 길이를 줄여준다는 장점은 있으나 헷갈리기 쉬워 보통 절대 경로를 많이 사용한다.
📌 Key takeaways & Wrap-up
1. sys.modules
과 sys.path
의 차이점
파이썬이 module/package를 찾기 위해 방문하는 곳은 3곳이 있다. sys.modules
, built-in modules
, sys.path
가 그것인데 sys.modules
와 sys.path
는 차이가 있다.
sys.modules
는 이미 import된 파일들을 저장하는 dictionary일 뿐이고, 일종의 재확인 기능만 할 뿐 새로 추가된 module/package를 찾을 수는 없다.
sys.path
는 list 형태며, 새롭게 import하는 module/package를 찾을 수 있다.
또한, 파이썬에게 특정 디렉토리도 search해달라고 추가할 수 있다.
2. 파이썬은 sys
를 어떻게 찾을까?
sys
는 파이썬의 built-in modules이므로, 여기서 찾아 import할 수 있다.
3. absolute path와 relative path의 차이점
절대경로는 현재 디렉토리(프로젝트들의 최상단 디렉토리) 기준으로 경로를 찾으며, 상대경로는 나의 위치를 기준으로 찾는다.
4. package 직접 만들고, 실행해 보기
calculator
package를 만들고, main.py
를 따로 만들었다. 각 모듈의 내용은 아래와 같다.
main.py
에서 add_and_multiply.py
를 상대경로로 import하면 ImportError
가 발생한다. from .calculator.add_and_multiply import add_and_multiply
> ImportError 발생
상대경로를 통한 import는 현재 module의 이름(__name__
)을 기반으로 탐색한다. 파이썬은 인터프리터가 최초로 실행한 스크립트 파일의 __name__
에는 __main__
을 부여하며, 이는 곧 프로그램의 entry point(시작점)이란 의미이기도 하다.
그리고 이 때 import되는 module은 해당 module명이 부여된다.
(만약 add_and_multiply.py
를 import하면, __name__
에는 add_and_multiply가 들어간다. 이는 고정적이지 않고, 시작점이 어디냐에 따라 __main__
이 들어갈 수도, module명이 그대로 들어갈 수도 있다.)
그런데 main.py
는 calculator
패지지 안에 있지 않기 때문에, 파이썬은 상대 경로에서 __main__
의 위치를 알 수 없고, 여기에서 탐색을 시작하는 상대경로 방식으로는 함수의 위치를 알 수 없는 것이다.
따라서, 절대경로로 탐색 방식을 바꿔야 한다.
from calculator.add_and_multiply import add_and_multiply
add_and_multiply.py
에서 multiply
함수를 상대경로 혹은 절대경로로 import하면 모두 error가 발생한다. from .multiplication import multiply
#ImportError: attempted relative import with no known parent package
from calculator.multiplication import multiply
#ModuleNotFoundError: No module named 'calculator'
main.py
(main module)와 달리, 이미 add_and_multiply.py
와 multiplication.py
는 같은 package안에 동일한 module형태로 형제관계를 이루고 있기 때문에 아래와 같이 수정하면 된다.
from multiplication import multiply
5. __init__.py
의 역할
#__init__.py
from .mod1 import my_func
from .mod2 import my_func_new
__all__ = ['my_func','my_func_new']
위와 같이 __all__
을 설정하면, 내가 import할 수 있는 함수는
my_func
와 my_func_new
뿐이다.