파이썬은 다음 3가지 장소를 순서대로 확인하며 모듈, 패키지가 존재하는지 체크한다.
- sys.modules
- built-in modules
- sys.path
sys.modules
이것은 모듈 이름을 이미 로드된 모듈로 매핑하는 딕셔너리입니다. 모듈의 재로딩과 기타 트릭을 강제하기 위해 조작할 수 있습니다. 그러나 딕셔너리를 교체하는 것은 예상대로 작동하지는 않으며 딕셔너리에서 필수 항목을 삭제하면 파이썬이 실패할 수 있습니다.
참고자료 : https://docs.python.org/ko/3/library/sys.html
sys.modules는 딕셔너리 구조로 이미 import된 모듈과 패키지를 저장하고 있다. 즉, 한번 import된 모듈과 패키지는 또 다시 찾지 않아도 된다.
파이썬 자체에서 제공하는 공식 모듈이다.파이썬에서 제공하는 라이브러리가 들어있다.
sys.path
모듈의 검색 경로를 지정하는 문자열 리스트. 환경 변수 PYTHONPATH와 설치 종속 기본값으로 초기화되었습니다.프로그램 시작 시 초기화된 대로, 이 리스트의 첫 번째 항목인 path[0]은 파이썬 인터프리터를 호출하는 데 사용된 스크립트가 포함된 디렉터리입니다. 스크립트 디렉터리를 사용할 수 없으면 (예를 들어, 인터프리터가 대화형으로 호출되거나 표준 입력에서 스크립트를 읽을 때) path[0]은 빈 문자열이 되는데, 파이썬이 현재 디렉터리에서 모듈을 먼저 검색하도록 합니다. 스크립트 디렉터리가 PYTHONPATH의 결과로 삽입된 항목 앞에 삽입됨에 유의하십시오.
프로그램은 자체 목적으로 이 리스트를 자유롭게 수정할 수 있습니다. 문자열과 바이트열만 sys.path에 추가해야 합니다; 임포트 하는 동안 다른 모든 데이터형은 무시됩니다.
참고자료 : https://docs.python.org/ko/3/library/sys.html
sys.path는 파이썬에서 마지막으로 확인한다. sys.path는 기본적으로 문자열 리스트이다.
['',
'/Users/song-chi-heon/anaconda3/bin',
'/Users/song-chi-heon/anaconda3/lib/python36.zip',
'/Users/song-chi-heon/anaconda3/lib/python3.6',
'/Users/song-chi-heon/anaconda3/lib/python3.6/lib-dynload',
'/Users/song-chi-heon/anaconda3/lib/python3.6/site-packages',
'/Users/song-chi-heon/anaconda3/lib/python3.6/site-packages/aeosa',
'/Users/song-chi-heon/anaconda3/lib/python3.6/site-packages/IPython/extensions',
'/Users/song-chi-heon/.ipython']
따라서 파이썬에서는 해당 경로에 모듈이나 패키지가 있는지 리스트를 하나씩 확인해간다.
sys는 파이썬에 포함되어 있는 모듈이다. 따라서 다음과 같이 sys.modules와 sys.path를 확인해 볼 수 있다.
import sys
print(sys.modules)
#result
#엄청 긴 결과가 나왔지만 그 중 일부만 적음
#dictionary형태로 저장
'''
{
'sys': <module 'sys' (built-in)>,
'builtins': <module 'builtins' (built-in)>,
'_frozen_importlib': <module 'importlib._bootstrap' (frozen)>
}
'''
print(sys.path)
#result
#문자열 list형태로 저장
'''
[
'/home/runner/57-How-Import-Statement-Finds-Modules-and-Packages-hrpp1300',
'/opt/virtualenvs/python3/lib/python3.8/site-packages', '/usr/lib/python38.zip',
'/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload'
]
'''
마지막 순서인 sys.path에서까지 모듈을 찾지 못하면 ModuleNotFoundError
가 나온다.
먼저 built-in modules는 파이썬에서 제공하는 모듈이기 때문에 문제없이 잘 찾아진다. 또한 외부에서 import하는 모듈또한 site-packages
라는 디렉토리에 설치되는데 이 site-packages
라는 디렉토리 경로는 sys.path에 이미 포함되어 있기 때문에 찾는데 문제가 없다.
문제는 직접 개발한 local packages
이다. 직접 모듈을 작성하여 저장하면 파이썬에서는 개발자가 이 파일을 어디에 저장해 놓았는지 모르기 때문에(모든 디렉토리를 다 뒤져볼 수는 없으니) 따로 명시해 주는 것이 아니면 찾을 수 없다.
그래서 직접 개발한 모듈의 경우 경로를 import해주어 파이썬에게 알려주어야 하는데 이 때 경로를 절대 경로와 상대 경로로 지정해 줄 수있다.
다음과 같은 디렉토리들이 있다고 가정하고 예를 들어보면
└── my_app
├── main.py
├── package1
│ ├── module1.py
│ └── module2.py
└── package2
├── __init__.py
├── module3.py
├── module4.py
└── subpackage1
└── module5.py
절대 경로를 이용해서 package1 과 package2를 import하면
from package1 import module1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2
이런식으로 임포트할 수 있다.
전부 다 최상위 디렉토리인 my_app으로 부터 시작해서 원하는 모듈, 함수, 객체, 클래스, 변수 등으로 임포트하는 것을 볼 수 있다.
리눅스에서는 my_app/package2/subpackage1/module5.py
이런 식으로 /(slash)를 이용해서 경로를 표기하는데 파이썬에서는 my_app.package2.subpackage1.module5.py
이런 식으로 .(dot)을 이용해서 경로를 표기한다.
이제 my_app.package2.subpackage1.module5.py
에 있는 function2 라는 함수를 이용하고 싶을 때 위 경로에서 from package2.subpackage1.module5 import function2
라고 모듈을 이용할 파일에 작성해 주면 파이썬에서 찾아갈 수 있다.
my_app 프로젝트 안에서 어느 파일, 어느 위치에 import하던지 저 function2 함수를 쓰려면 저렇게 경로를 설정해 주기 때문에 absolute path라고 하는 것이다.
다만 절대 경로를 이용하면 경로가 길어질 수 있고 귀찮아진다. 따라서 relative path(상대 경로)를 이용해서 더 짧게 경로를 만들 수 있다. 상대 경로는 최상위 디렉토리부터 시작하는 절대 경로와 다르게 현재 import하는 위치를 기준으로 경로를 정의한다.
예를 들어, package2의 모듈에서
두가지를 import하려면 다음과 같이 하면 된다.
# package2/module3.py
from . import class1 #현재 파일에서 class1을 import한다.
from .subpackage1.module5 import function2 #현재 패키지에서 하위패키지인 subpackage1으로 들어가고 그 패키지에서 module5로 들어가서 그 파일에 있는 function2를 import한다.
.
(dot)은 import가 선언되는 파일의 현재 위치를 뜻한다.
..
은 현재 위치에서 상위 디렉토리로 간다는 뜻이다.
sys.modules
와 sys.path
의 차이점
sys.modules
- dictionary형태로 저장된다.
- 한번 import한 모듈과 패키지를 저장해 놓기 때문에 나중에 또 다른 파일이나 다른 위치에서 import를 할 경우 제일 먼저 모듈의 위치를 여기서 찾는다.
sys.modules
를 출력한 결과
sys.path
- string(문자열)형태의 리스트로 저장
- 파이썬에서 import된 모듈이나 패키지를 마지막으로 확인하는 곳
sys.path
를 출력한 결과
['/home/runner/57-How-Import-Statement-Finds-Modules-and-Packages-hrpp1300', '/opt/virtualenvs/python3/lib/python3.8/site-packages', '/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload']
파이썬이 모듈을 탐색하는 순서는 1.
sys.modules
2.built-in modules
3.sys.path
였다. 먼저 제일 처음sys
를 임포트하면 1번에는 존재하지 않을 것이다. 그 다음 2번built-in modules
, 사실 여기에 있다.
파이썬 sys모듈의 메소드로 빌트인 모듈을 볼 수 있는sys.builtin_module_names
이 있다. 이걸 출력해 보면import sys print(sys.builtin_module_names) #result ''' ('_abc', '_ast', '_bisect', '_blake2', '_codecs', '_collections', '_csv', '_datetime', '_elementtree', '_functools', '_heapq', '_imp', '_io', '_locale', '_md5', '_operator', '_pickle', '_posixsubprocess', '_random', '_sha1', '_sha256', '_sha3', '_sha512', '_signal', '_socket', '_sre', '_stat', '_statistics', '_string', '_struct', '_symtable', '_thread', '_tracemalloc', '_warnings', '_weakref', 'array', 'atexit', 'binascii', 'builtins', 'cmath', 'errno', 'faulthandler', 'fcntl', 'gc', 'grp', 'itertools', 'marshal', 'math', 'posix', 'pwd', 'pyexpat', 'select', 'spwd', 'sys', 'syslog', 'time', 'unicodedata', 'xxsubtype', 'zlib' ) '''
마지막 줄에 sys모듈이 들어있는 것을 확인할 수 있다.
- 절대 경로
임포트하는 파일의 위치에 상관없이 프로젝트의 최상위 디렉토리부터 경로를 탐색한다.
- 상대 경로
임포트하는 파일을 기준으로 경로를 탐색한다.
파일의 위치가 바뀌는 경우가 있을 수도 있고 헷갈리기도 하므로 절대 경로로 설정하는 것을 권장한다.
calculator
패키지 만들기└── . ├── calculator │ ├── __init__.py │ ├── add_and_multiply.py │ └── multiplication.py └────── main.py
main.py
# absoulte path # from calculator.add_and_multiply import add_and_multiply # relative path from .calculator.add_and_multiply import add_and_multiply if __name__ == '__main__': print(add_and_multiply(1,2))
add_and_multiply.py
from .multiplication import multiply # from calculator.multiplication import multiply def add_and_multiply(a,b): return multiply(a,b) + (a+b)
multiplication.py
def multiply(a,b): return(a*b)
main.py
에서 상대 경로로 add_and_multiply
를 임포트 했을 때 에러가 발생하는 이유와 해결책은?해당 파일에서 스크립트를 실행할 경우
__name__
이__main__
이 된다. 따라서main.py
에서 파이썬 인터프리터를 실행하면 if문인if __name__ == '__miain__'
을 만족한다.
그런데__name__ = '__main__'
일 경우 파이썬에서 최상위 디렉터리로 인식하기 때문에 절대 경로로 설정하여 최상위 디렉터리가 어딘지 알려주어야 한다.
add_and_multiply.py
에서 상대 경로와 절대 경로로 multiply
를 임포트 해보기
상대 경로(
from .multipication import multiply
)일 경우
- 결과 :
ImportError: attempted relative import with no known parent package
- 이유 : 5번과 마찬가지 이유로 에러가 난다.
절대 경로(
from calculator.multiplication import multiply
)일 경우
- 결과 :
ModuleNotFoundError: No module named 'calculator'
- 이유 :
add_and_multiply
파일은 calculator디렉터리 안에 존재하므로 그냥multiplication import multiply
로 해주면 된다.
__init__
의 역할디렉터리안에
__init__.py
파일을 생성해주지 않으면 파이썬에서는 그 디렉터리를 package로 인식하지 못한다. python3.3버전부터는__init.py__
가 없어도 패키지로 인식하는데 하위 버전과의 호환성을 위해 작성해 주는 것이 좋다.
마지막 파이썬 과제인 module부분을 공부하면서 절대 경로와 상대 경로를 각각 설정해 보며 어떤 오류가 있는지 확인하였다.
경로를 상대 경로로 할 때와 절대 경로로 할 때, 어떤 파일을 실행하는지에 따라 계속 바뀌기 때문에 어려웠다. 웬만하면 절대 경로로 설정하는 것이 좋을 것 같다.