Python | How import statement finds modules and packages

Sua·2021년 1월 16일
0

Python

목록 보기
26/28
post-thumbnail

module과 package 검색하는 방법

모듈과 패키지를 import하기 위해서는 어디에 위치해있는지 파이썬이 찾을 수 있어야 한다. 파이썬은 다음 3가지 장소를 순서대로 보면서 찾는다.

1. sys.modules

파이썬이 가장 먼저 확인하는 곳이다.
sys.modules는 dictionary로 이미 import된 모듈과 패키지들을 저장하고 있다. 즉, 한번 import된 모듈과 패키지들은 파이썬이 또 다시 찾지 않아도 되도록 한다. 그러므로 새로 import 하는 모듈은 sys.modules에서 찾을 수 없다.

2. built-in modules

파이썬에서 제공하는 파이썬 공식 라이브러리들이다.

3. sys.path

마지막으로 보는 장소가 바로 sys.path이다.
sys.path는 기본적으로 list로 각 요소는 string 형태로 경로를 나타낸다. 파이썬은 list의 각 경로를 하나 하나 확인하면서 해당 경로에 import 하고자 하는 패키지가 위치해 있는지 확인한다. (참고로 sys는 파이썬에 포함되어 있는 모듈이다.)

파이썬은 import하고자 하는 모듈을 sys.path에서도 못찾으면 ModuleNotFoundError 에러를 리턴한다.

sys도 import 해야하는 모듈이다. 파이썬은 sys 모듈의 위치를 어떻게 찾을 수 있을까?

sys는 build-in module로 파이썬이 설치된 위치에 같이 저장되어 있다.


Absolute Path와 Relative Path

Built-in 모듈은 당연히 잘 찾아지고, pip 으로 설치한 외무 모듈도 자동으로 site-packages 라는 디렉토리에 설치가 되는데, 이 site-packagessys.path에 이미 포함되어 있기 때문에 찾는데 문제가 없다.

문제는 직접 개발한 local package이다. 직접 개발한 local package를 import 할때는 해당 package의 위치에 맞게 import 경로를 잘 선언해야 한다. Local package를 import 하는 경로에는 absolute path 와 relative path 가 있다.

Absolute Path(절대 경로)

Absolute path는 절대 경로로 import하는 파일이나 경로에 상관없이 항상 경로가 동일하다.

└── my_app
    ├── main.py
    ├── package1
    │   ├── module1.py
    │   └── module2.py
    └── package2
        ├── __init__.py
        ├── module3.py
        ├── module4.py
        └── subpackage1
            └── module5.py

my_app이라는 프로젝트는 package1과 package2라는 2개의 package를 가지고 있다. 그리고 package2는 subpackage2 라는 중첩 package를 가지고 있다.

from package1 import module1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2

경로들의 시작점이 전부 "my_app" 프로젝트의 가장 최상위 디렉토리에서 시작한다.

예를 들어, subpackage1의 module5 모듈의 function2 함수를 import 하기 위해서는 다음 경로를 거친다.

my_app => package2 => subpackage1 => module5.py

my_app/package2/subpackage1/module5.py # 리눅스의 directory 경로 형식
my_app\package2\subpackage1\module5.py # 윈도우즈 형식
my_app.package2.subpackage1.module5.py # 파이썬 형식

이미 my_app 프로젝트 안에 있으므로 my_app 은 생략한다

package2.subpackage1.module5.py

Relative Path(상대 경로)

Relative Path는 최상단 디렉토리를 기준으로 경로를 잡는게 아니라 import 하는 위치를 기준으로 경로를 정의한다. 그래서 일반적으로 relative path는 local package 안에서 다른 local package를 import 할때 사용한다.

# package2/module3.py
 
from . import class1
from .subpackage1.module5 import function2

여기서 dot(.)은 import가 선언되는 파일의 현재 위치이다. 현재위치는 package2/module3.py 이므로 현재 위치에서부터 원하는 모듈의 경로만 선언해주면 된다.

# subpackage1/module5.py

from ..module4 import class4

dot 2개(..) 는 현재위치에서 상위 디렉토리로 가는 경로이다.

Absolute Path와 Relative Path의 차이점

absolute path는 선언해야 하는 경로가 길어질 수 있다.

Relative path는 경로의 길이를 줄여준다. 하지만 헷갈리기 쉽고 파일 위치가 변경되면 경로 위치도 변경되어야 하는 단점이 있다.

그러므로 웬만한 경우 absolute path를 사용하는게 권장된다.


calculator 패키지 만들기

디렉토리 구조

__init__.py
__init__.py에 아무코드도 없지만 해당 디렉토리가 패키지임을 알려주는 역할을 한다.

main.py

add_and_multiply.py

multiplication.py

main module에서 패키지의 모듈을 import하는 방법

main.py에서 상대경로로 add_and_mutiply를 import 시도하니

# relative path
from .calculator.add_and_multiply import add_and_multiply
 
if __name__ == '__main__':
    print(add_and_multiply(1,2))

ImportError: attempted relative import with no known parent package 에러가 발생한다.

파이썬공식문서를 통해 어떤 문제인지 파악해보자.

Note that relative imports are based on the name of the current module. Since the name of the main module is always __main__, modules intended for use as the main module of a Python application must always use absolute imports.

해석하면

relative imports는 현재 모듈의 이름을 기반으로 한다. 메인 모듈의 이름은 항상 __main__이기 때문에, 파이썬 어플리케이션의 메인 모듈로 사용하려는 모듈은 항상 absolute imports를 사용해야한다.

main module?

여기에서 이해가 안되는 부분은 main module이 무엇을 의미하는가이다. main.py에 main이 들어갔다고 main module은 아닐 것이고..

공식문서를 다시 살펴보니 main module을 다음과 같이 정의해놓았다.

the collection of variables that you have access to in a script executed at the top level and in calculator mode

해석하면

메인 모듈은 최상위 수준에서 실행되는 스크립트나 계산기 모드(인터프리터 모드)에서 액세스하는 변수들의 컬렉션이다.

쉽게 설명하면 최상위 수준에서 실행되는 스크립트란 처음 실행하는 소스 파일을 의미한다. (최상위 모듈이라고도 한다.) 그 파일(모듈)이 프로그램 실행의 시작점이 된다. 그리고 스크립트가 아닌 인터프리터에서는 무엇을 전달하든, 그것이 메인 모듈이 된다. 이름이 무엇이든 상관없다. 참고1 참고2

반대로 어떤 모듈이 다른 곳에 import되어 사용된다면 메인 모듈이 아닌 것이다.

여기서main.py는 메인 모듈이므로 상대경로가 아닌 절대경로로 import해야 한다!

# absoulte path
from calculator.add_and_multiply import add_and_multiply 
 
if __name__ == '__main__':
    print(add_and_multiply(1,2))
5 # 출력

참고로 여기서 if __name__ == '__main__':은 메인 모듈인지 식별하기 위한 조건문이다. 해당 모듈이 메인 모듈일 때 __name____main__이 되고 아래의 코드를 실행한다. 해당 모듈이 다른 모듈로 import되었을 때는 코드가 실행되지 않는다. 참고사이트-코딩도장

add_and_multiply.py에서 multiply함수를 import하면?

2가지 케이스로 나눠서 생각해볼 수 있겠다. 첫 번째, add_and_multiply.py이 메인 모듈일 경우, 다시 말해 그 파일에서 바로 실행했을 경우이다. 두 번째, add_and_multiply.py이 메인 모듈이 아닐 경우, 다시 말해 다른 파일(main.py)이 add_and_multiply.py을 import했을 경우이다.

Case1. add_and_multiply.py이 메인 모듈일 경우

# relative path
from .multiplication import multiply

-> ImportError: attempted relative import with no known parent package 에러 발생

# absoulte path
from calculator.multiplication import multiply

-> ModuleNotFoundError: No module named 'calculator' 에러 발생

# absoulte path
from multiplication import multiply

-> 정상동작

여기서 add_and_multiply.py는 메인 모듈이므로 상대경로가 아닌 절대경로로 import해야 한다. 이미 calculator 패키지 안에 있으므로 calculator는 경로에서 생락한다.


Case2. add_and_multiply.py이 메인 모듈이 아닐 경우
main.py이 다음과 같이 add_and_multiply.py을 import했다.

# main.py
from calculator.add_and_multiply import add_and_multiply 
 
if __name__ == '__main__':
    print(add_and_multiply(1,2))

이때는 add_and_multiply.py의 import문이 절대 경로이든 상대 경로이든 상관없다.

# add_and_multiply.py
# absoulte path
from calculator.multiplication import multiply
 
def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)

또는

# add_and_multiply.py
# relative path
from .multiplication import multiply
 
def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)

그런데 절대 경로일 때 from multiplication import multiply로 하면 오류가 난다. 왜 calculator까지 적어줘야 하는지는 모르겠다..

참고사이트
https://docs.python.org/3/tutorial/modules.html#intra-package-references
https://wikidocs.net/84414
https://dojang.io/mod/page/view.php?id=2448
https://python.bakyeono.net/chapter-10-1.html
https://velog.io/@choonghee-lee/WeCode-How-Import-Statement-Finds-Modules-Packages
https://realpython.com/absolute-vs-relative-python-imports/

profile
Leave your comfort zone

0개의 댓글