[python 기초] module/package, import search (2)

EMMA·2022년 3월 6일
0

이번엔 파이썬 관점에서, import search에 대해 좀 더 알아보자

앞서, 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.modulessys.path의 차이점
파이썬이 module/package를 찾기 위해 방문하는 곳은 3곳이 있다. sys.modules, built-in modules, sys.path가 그것인데 sys.modulessys.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 직접 만들고, 실행해 보기

  • 4-1. calculator package를 만들고, main.py를 따로 만들었다.
    (2번째 이미지가 내가 만든 package 캡처본)

각 모듈의 내용은 아래와 같다.

  • 4-2. 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.pycalculator패지지 안에 있지 않기 때문에, 파이썬은 상대 경로에서 __main__의 위치를 알 수 없고, 여기에서 탐색을 시작하는 상대경로 방식으로는 함수의 위치를 알 수 없는 것이다.
따라서, 절대경로로 탐색 방식을 바꿔야 한다.

from calculator.add_and_multiply import add_and_multiply 
  • 4-3. 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.pymultiplication.py는 같은 package안에 동일한 module형태로 형제관계를 이루고 있기 때문에 아래와 같이 수정하면 된다.

from multiplication import multiply

5. __init__.py 의 역할

  • 해당 디렉토리가 package임을 알려주며,
  • import할 수 있는 변수/함수/클래스를 제한할 수 있다.
  • 또한 그 외 package가 import될 때 먼저 실행해야 하는 코드들 설정할 수 있다.
#__init__.py
from .mod1 import my_func
from .mod2 import my_func_new
__all__ = ['my_func','my_func_new']

위와 같이 __all__을 설정하면, 내가 import할 수 있는 함수는
my_funcmy_func_new 뿐이다.

profile
예비 개발자의 기술 블로그 | explore, explore and explore

0개의 댓글