Python은 여러 모듈이 합쳐져서 프로젝트를 이루게 된다 pip를 이용하여 모듈 패키지를 추가할 수 있고 용도에 따라 웹개발이나, AI/ML개발, 게임 개발 등이 가능하다. 개발자 개인이 만든 local package module 을 이용하여 더 다양한 프로젝트를 만들 수 있다. 그렇다면 import 를 써서 어떻게 모듈과 패키지를 찾는 것일까?
Python은 크게 세가지 구역에서 아래와 같은 순서로 module/package들을 찾게 된다.
1. sys.modules
2. built-in modules
3. sys.path
Python이 modules이나 packages를 찾기 위해 우선적으로 살피는 영역으로 dictionary형태로 되어 있다.
>>> import sys
>>> print(sys.modlues)
python을 실행하고 한 번 이상 import가 되었다면 다시 모듈을 찾지 않고 곧바로 sys.modules를 확인하여 module이나 package를 사용하게 된다. 한 번 이상 사용되었거나 이미 내장 되어 있는 module이나 패키지만 존재하므로 새롭게 import되는 것들은 없다고 보면 된다.
python에서 제공하는 python 공식 라이브러리들이다.
Built-in 모듈들은 이미 python에 포함되어 나오므로 python이 쉽게 찾을 수 있다.
위에 sys.modules print() 출력 결과를 확인하게 되면 어떤 것이 built-in modules인지 쉽게 확인 할 수 있다.
파이썬이 module이나 package를 찾을 때 가장 마지막으로 확인하는 부분이다. pip 로 새롭게 설치한 패키지도 이 곳을 통해 찾게 되며 새롭게 작성한 패키지나 모듈을 사용하고자 할 때 이 곳에 path를 등록 해서 찾게끔 설정해 준다.(설정하는 방법은 해당 시스템의 OS에 따라 다소 차이가 있다) 해당 변수는 list 의 형태로 구성되어 있다(쉽게 할당 삭제가 가능하다)
>>>import sys.path
>>>print(sys.path)
그렇다면 module/package 검색에 근원이 되는 sys 모듈은 어떻게 찾게 되는 것일까 ?
파이썬은 import 하고자 하는 모듈과 package를 찾을때에 먼저 1. sys.modules를 보고, 없으면 파이썬 2. built-in 모듈들을 확인 하고 마지막으로 3. sys.path에 지정되어 있는 경로들을 확인해서 찾는다.
'sys': <module 'sys' (built-in)>
sys모듈은 이미 built-in 되어 있기 때문에 built-in module들이 있는 부분에서 찾게 된다.
sys.path 에서도 못찾으면 ModulesNotFoundError 에러를 리턴한다.
파이썬의 built-in 모듈과 pip 을 통해 설치한 외부 모듈 및 package는 일반적으로 import 하는데 큰 문제가 되지 않는다. Built-in 모듈은 당연히 잘 찾아지고, pip 으로 설치한 외무 모듈도 자동으로 site-packages 라는 디렉토리에 설치가 되는데, 이 site-packages 는 sys.path에 이미 포함되어 있기때문에 찾는데 문제가 없다.
문제는 직접 개발한 local package 이다. 직접 개발한 local package를 import 할때는 해당 package의 위치에 맞게 import 경로를 잘 선언해야 한다. Local package를 import 하는 경로에는 absolute path 와 relative path 가 있다.
Absolute path는 이름 그대로 절대 경로 이다. import를 하는 파일이나 경로에 상관없이 항상 경로가 동일하기 때문이다.
my_app
├── main.py
├── pkg1
│ ├── __init__.py
│ ├── module1.py
│ └── module2.py
├── pkg2
│ ├── __init__.py
│ ├── module3.py
│ ├── module4.py
│ ├── module5.py
│ └── pkg4
│ ├── __init__.py
│ └── module6.py
└── pkg3
├── __init__.py
└── module7.py
다음과 같은 구조를 가진 my_app이라는 프로젝트 사례를 보자.
위 프로젝트는 package1과 package2 라는 2개의 package를 가지고 있다.
그리고 package2는 subpackage2 라는 중첩 package를 가지고 있다.
absolute path의 경우 모듈 및 패키지의 시작부터 끝까지 생략이나 축약되지 않고 명확히 명시된 경로를 뜻한다. 어디에서 쓰든 항상 같아서 사용하는데 헷갈리지 않다는 장점이 있다.
#main.py
from pkg1 import module1
from pkg1.module2 import func3
from pkg2 import module3
from pkg2.pkg4.module6 import func10
경로들의 시작점이 전부 "my_app" 프로젝트의 가장 최상위 디렉토리에서 시작하는것을 볼 수 있다.
예를 들어, subpackage1의 module5 모듈의 function2 함수를 import 하기 위해서는 다음 경로를 거치게 된다.
예를 들어, package2의 module3에서 package2의 class1과 package2의 하위 package인 subpackage1의 module5의 function2 함수를 import하려고 하면 다음 처럼 할 수 있다.
# package2/module3.py
from . import class1
from .subpackage1.module5 import function2
relative path 는 absolute path와 다르게 프로젝트의 최상단 디렉토리를 기준으로 경로를 잡는게 아니라 import 하는 위치를 기준으로 경로를 정의한다. 그래서 일반적으로 relative path는 local package 안에서 다른 local package를 import 할때 사용된다.
예를 들어, package2의 module3에서 package2의 class1과 package2의 하위 package인 subpackage1의 module5의 function2 함수를 import하려고 하면 다음 처럼 할 수 있다.
# package2/module3.py
from . import class1
from .subpackage1.module5 import function2
여기서 dot(.)은 import가 선언되는 파일의 현재 위치를 이야기 한다. 현재위치는 package2/module3.py 이므로 현재 위치에서부터 원하는 모듈의 경로만 선언해주면 되는 것이다.
또한 dot 2개를 사용할 수도 있습니다. dot 2개(..) 는 현재위치에서 상위 디렉토리로 가는 경로이다.
# subpackage1/module5.py
from ..module4 import class4
Relative path는 선언해야 하는 경로의 길이를 줄여준다는 장점은 있지만 헷갈리기 쉽고 파일 위치가 변경되면 경로 위치도 변경되어야 하는 단점이 있다. 그러므로 웬만한 경우 absolute path를 사용하는게 권장 된다.
Relative path는 absolute path에 비해 간결해 지는 장점이 있지만 프로젝트가 커지고 복잡도가 높아질 수록 혼동이 될 수 있으며 만일 파일의 위치가 바뀌게 되면 그에 맞춰서 path도 재설정 해주어야 한다. 따라서 아무리 복잡한 프로젝트라 하더라도 코드의 일관성 및 실수를 막고자 absolute path의 사용이 권장되는 편이다.
개발 과정에서 여러가지 에러를 만나고, 그 에러를 해결하는 과정을 겪게 된다. 여러 모듈을 가지는 패키지를 만들때 발생하는 에러의 경우를보자.
ImportError: attempted relative import with no known parent package
이 에러가 발생하는 이유가 무엇일까?
다음과 같은 디렉토리를 가진 파일이 있다.
mymath.py 에는 다음과 같은 함수가 있고,
mypath.py 에는
다음과 같이 구현되어 있다.
여기서 mypath.py를 실행시키기 위해 터미널을 실행시키면 다음과 같은 에러가 발생합니다.
에러가 발생하는 것을 확인했으니 원인을 찾아보도록 하자.
원인
python interpreter는 relative import 의 module 위치를 정할 때 즉, 기준이 되는 모듈의 위치를 정할 때, name속성에 의해 결정된다. 터미널에서 직접 python 파일을 실행시킬 때 name == 'main' 이 된다. 그러면 당연히 main이라는 모듈의 위치를 파이썬 interpreter는 알 수가 없기 때문에 에러가 발생한다.
해결방안
python 의 -m 옵션을 이용한다.
-m mod : run library module as a script (terminates option list)
-m 옵션은 모듈을 스크립트로 수행할 때 쓰는 옵션이다.
1) 먼저 프로젝트 root directory인 test2 디렉토리로 이동한다.
2) 그리고 python3 -m package.package.mypath 를 입력하여 실행시키면 해결된다.
python의 절대경로를 이용한다.
name == 'main' 은 터미널에서 스크립트 형태로 실행시켰을 때는 절대경로를 이용해서 import 해주고 그가 아닌 경우 상대경로를 이용한다. print() 함수로 디렉토리 위치를 확인하면서 하면 훨씬 좋다.
참고
https://www.napuzba.com/story/import-error-relative-no-parent/