코딩을 할 때, 필요에 의해서 module과 package를 만들고 그렇게 생성한 module과 package를 import해서 변수,함수,클래스 등을 사용한다. 그렇다면 파이썬은 module과 package를 어떻게 찾는것인지 알아보자. 앞으로 코딩할 때 각종 라이브러리, 패키지 등을 import하는 경우가 아주 많으므로 해당 내용을 알고있으면 많이 도움이 될 것이다.
파이썬은 다음의 3가지 장소를 순서대로 보면서 찾는다.
파이썬이 module이나 package를 찾기위해 가장 먼저 확인하는 곳이다. sys.modules
는 단순한 dictionary
이며, 이미 import 된 module과 package들을 저장하고 있다. 그렇기 때문에 한번 import 한 module이나 package는 파이썬이 다시 찾지 않아도 되도록 하는 기능을 갖는다.
파이썬에서 제공하는 파이썬 공식 라이브러리들이다. Built-in modules
는 이미 파이썬 파일에 포함되어있기 때문에 파이썬이 쉽게 찾을 수 있다.
마지막으로 확인하는 장소가 sys.path
이다. sys.path
는 string
요소들을 가지고 있는 list
이다. 각 string
요소들은 다음처럼 경로를 나타낸다.
['/run_dir',
'/usr/local/lib/python38.zip',
'/usr/local/lib/python3.8',
'/usr/local/lib/python3.8/lib-dynload', '/home/runner/.local/share/virtualenvs/python3/lib/python3.8/site-packages']
위와 같이 sys.path
혹은 sys.modules
를 확인하고 싶다면 sys
파이썬에 포함되어 있는 모듈이기 때문에 import
시켜서 확인할 수 있다.
import sys
print(sys.path)
print(sys.modules)
파이썬은 import 하고자 하는 모듈과 package를 찾을때에 먼저 sys.modules를 보고, 없으면 파이썬 built-in 모듈들을 확인 하고 마지막으로 sys.path에 지정되어 있는 경로들을 확인해서 찾는다.
마지막 sys.path 에서도 못찾으면?
ModuleNotFoundError 에러를 리턴한다.
파이썬의 built-in modules
와 pip
로 설처한 라이브러리들은 파이썬이 경로를 찾는데 문제가 없기 때문에 import하는데에 있어 문제가 발생하지 않는다. (설치한 라이브러리들은 sys.path
가 포함하고 있는 site-packages
에 저장되기 때문에 파이썬이 쉽게 찾는다.) 하지만 경로에 대해서 이해하고 넘어가야하는 이유는 직접 개발한 local package
때문이다. 직접 개발한 local package
는 파이썬에서 경로를 검색할 수 있도록 직접 지정해주어야하는데 여기서 알아야하는 개념이 바로 경로에 대한 개념이다.
import
하는 파일이나 경로에 상관없이 항상 경로가 동일
위의 그림으로 이해해보자. my_app
이라는 프로젝트가 있고 프로젝트에는 package1
, package2
라는 두개의 패키지를 가지고 있다. 각 패키지는 파이썬 파일을 가지고 있고 package2
는 subpackage1
라는 패키지도 포함하고 있다. 절대경로를 사용해서 package1
과 package2
를 import
해보면 다음과 같이 할 수 있다.
from package1 import module1
from package1.module2 import function1
from package2 import class1
경로들의 시작점이 프로젝트의 가장 최상단 경로인 my_app
에서 시작하는것을 확인할 수 있다.
만약 module5.py
의 function2
함수를 import
하고싶다면,
from package2.subpackage1.module5 import function2
이렇게 절대 경로를 주어 import
시켜줄 수 있다.
이러한 경로 표현 방식은 OS에 따라 다르게 표현되는데
리눅스
my_app/package2/subpackage1/module5.py
윈도우
my_app\package2\subpackage1\module5.py
파이썬
my_app.package2.subpackage1.module5.py
위에 사용한 예시에서는 이미 my_app
의 위치이므로 my_app
은 생략되고, 다음과 같이 표현된다.
package2.subpackage1.module5.py
해당 내용을 from ~ import ~
를 사용하면 다음과 같이 표현된다.
from package2.subpackage1.module5 import function2
절대경로는 어느 위치에서 import
를 하던 경로가 항상 동일하다. 일반적으로 보통 절대 경로를 사용하지만 절대 경로를 사용한다면 경로가 길어질 수 있다는 단점이 존재한다. 그래서 이러한 단점을 보완하기 위해서 사용되는것이 상대 경로(Relative Path)
이다.
상대 경로는 절대 경로와는 다르게 현재 위치를 기준으로 하여 경로를 정의한다. 때문에 일반적으로 상대 경로는 local package 에서 다른 local package를 import할 때 주로 사용된다. 다음과 같은 예를 들 수 있다.
# package2/module3.py <- 현재 디렉토리의 위치
from . import class1
from .subpackage1.module5 import function2
여기서 from
뒤에 사용된 .
은 현재 위치를 표현할 때 사용된다. 현재위치는 package2/module3.py
이므로 현재 위치에서부터 원하는 모듈의 경로만 선언해주면 된다.
( ..
을 사용해서 현재 위치에서 상위 디렉토리로 위치를 이동할 수 있다. )
상대 경로는 선언해야 하는 경로의 길이를 줄여준다는 장점은 있지만 헷갈리기 쉽고 파일 위치가 변경되면 경로 위치도 변경되어야 하는 단점이 있다.
dictionary
구조이다.import
한 모듈이나 패키지를 다시 찾지 않아도 된다.string
요소들을 가지고 있는 list
구조이다.위 문제의 답을 찾기 위해서 실제로 sys.modules
를 출력해보자.
import sys
print(sys.modules)
출력 결과 중 다음과 같은 부분을 확인할 수 있을것이다.
'sys': <module 'sys' (built-in)>
dictionary
형태로 sys
라는 key
값이 이미 저장되어 있다. sys 모듈
은 이미 built-in
돼있기 때문에 built-in module
이 있는 부분에서 찾게 된다.
Absolute path
절대경로는 import하는 파일이나 경로에 상관없이 항상 프로젝트의 최 상단 경로로부터 시작하며, 경로가 동일하다. 경로가 길어질 수 있는 단점은 있지만 경로를 수정해 줄 필요가 현저히 적고 경로를 파악하는데도 훨씬 명확하기 때문에 주로 사용된다.
Relative path
상대경로는 현재 위치를 기준으로 경로를 정의한다. 현재 위치는 .
으로 표현되며, 프로젝트의 최상단이 아닌 현재 위치를 기준으로 하기때문에 작성하기 편리하고, 경로의 작성이 짧아진다는 장점이 있지만 파일 위치가 변경되면 경로 위치 또한 수정해줘야 하는 유지보수의 어려움이 존재한다.
위와 같은 경로를 가진 calculator package를 직접 만들어보자.
패키지 구성
main.py
add_and_multiply.py
multiplication.py
위에서 작성한 코드를 실행시켜보면 다음과 같은 에러가 발생한다.
부모 패키지가 정의되어있지 않은 상태에서 상대 경로로 import를 시도해서 발생한 import error다.
절대경로로 import 경로를 정의해줄 것.
파이썬 공식문서를 참고해보면 다음과 같이 권고하는데,
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.
즉, 상대 경로 import는 현재 모듈의 이름을 기반으로한다. 기본 모듈의 이름은 항상 __main__
이므로 Python 응용 프로그램의 기본 모듈로 사용하려는 모듈은 항상 절대 가져 오기를 사용해야한다. 이제 위의 내용들을 참고하여 절대 경로로 변경하여 오류를 해결 후 결과를 출력해보자.
오류 해결
출력 결과
위와 같이 정상 동작한다.
from calculator.multiplication import multiply
def add_and_multiply(a,b):
return multiply(a,b) + (a+b)
from .multiplication import multiply
def add_and_multiply(a,b):
return multiply(a,b) + (a+b)
실행 결과는 동일하다.
add_and_multiply.py
는 __main__.py
가 아니기 때문에 절대 경로든 상대 경로든 문제 없이 동작한다. __main__.py
가 아니면 절대 경로든 상대 경로든 작동에는 문제가 없다.
__init__.py
가 존재하는 디렉토리는 패키지의 일부임을 알려주는 역할을 한다.- package 초기 설정을 할 수 있는 역할을 한다.
__init__.py
파일이 존재하지 않는다면 패키지의 일부인지 인식을 하지 못하기 때문에 속성을 가지고 있지 않는다는 에러가 발생하게 된다.__init__.py
파일은 빈 파일이지만 package 실행 시 설정해주고 싶은 초기 설정이 있다면 이 파일에 초기 설정에 관련된 코드들을 작성해준다. 그러면 작성 한 코드의 내용대로 package 실행 시 자동으로 실행된다.