이번에는 파이썬의 모듈에 관려하여 알아보도록 하겠습니다.
먼저 파이썬에서 자주 사용되는 라이브러리, 패키지, 모듈의 개념은 아래와 같습니다.
sys란?
sys는 파이썬에서 제공하는 모듈이며 os모듈안에 있습니다. 파이썬 인터프리터가 제공하는 변수와 함수를 직접 제어할 수 있게 해주는 모듈이고 C언어로 작성되어 있습니다. 또한 sys는 built-in 되어 있으므로 bulit-in modules에서 찾을 수 있습니다.
sys.module
파이썬이 모듈이나 package를 찾기위해 가장 먼저 확인하는 곳입니다.
sys.modules는 단순한 dictionary이고, 이미 import된 모듈과 package들을 저장하고 있습니다. 즉, 한번 import된 모듈과 package들은 파이썬이 또 다시 찾지 않아도 되도록 하는 기능을 가지고 있습니다. 한 번 이상 사용되었거나 이미 내장 되어 있는 module이나 패키지만 존재하므로 새롭게 import되는 것들은 없다고 생각하시면 됩니다.
-. 파이썬이 제일먼저 모듈이나 패키지를 찾는곳
-. Dictionary 구조
-. import 되있는 모듈과 패키지 저장 (다시 찾지 않아도 됨)
built-in modules
파이썬에서 공식으로 제공하는 라이브러리. 당연히 설치하자 마자 깔리는 것들이고 쉽게 찾을 수 있게 됩니다. 위에 sys.modules print() 출력 결과를 확인하게 되면 어떤 것이 built-in modules인지 쉽게 확인 할 수 있습니다.
sys.path
파이썬이 module이나 package를 찾을 때 가장 마지막으로 확인하는 부분입니다. pip로 새롭게 설치한 패키지도 이 곳을 통해 찾게 되며 새롭게 작성한 패키지나 모듈을 사용하고자 할 때 이 곳에 path를 등록해서 찾을 수 있도록 설정해 줍니다. 해당 변수는 list 형태로 구성되어 있어 쉽게 할당 및 삭제가 가능합니다
-. 모듈과 패키지를 1,2를 찾고 마지막으로 sys.path를 찾음
-. 리스트구조(string 요소)
-. 처음의 리스트 요소부터 마지막까지 찾음
-. 파이썬에 포함되어있는 built-in modules, sys.path에서도 모듈을 발견하지 못하면 ModuleNotFoundError 에러를 리턴
1 2 3 4 5 6 7 8 | import sys print(sys.modules) {'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_thread': <module '_thread' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>, 'nt': <module 'nt' (built-in)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'winreg': <module 'winreg' (built-in)>, 'time': <module 'time' (built-in)>, 'zipimport': <module 'zipimport' (frozen)>, '_codecs': <module '_codecs' (built-in)>, 'codecs': <module 'codecs' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\codecs.py'>, 'encodings.aliases': <module 'encodings.aliases' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\encodings\\aliases.py'>, 'encodings': <module 'encodings' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\encodings\\__init__.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\encodings\\utf_8.py'>, '_codecs_kr': <module '_codecs_kr' (built-in)>, '_multibytecodec': <module '_multibytecodec' (built-in)>, 'encodings.cp949': <module 'encodings.cp949' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\encodings\\cp949.py'>, '_signal': <module '_signal' (built-in)>, 'encodings.latin_1': <module 'encodings.latin_1' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\encodings\\latin_1.py'>, '_abc': <module '_abc' (built-in)>, 'abc': <module 'abc' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\abc.py'>, 'io': <module 'io' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\io.py'>, '__main__': <module '__main__' from 'c:\\Users\\samsung\\OneDrive\\바탕 화면\\python\\practice.py'>, '_stat': <module '_stat' (built-in)>, 'stat': <module 'stat' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\stat.py'>, '_collections_abc': <module '_collections_abc' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\_collections_abc.py'>, 'genericpath': <module 'genericpath' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\genericpath.py'>, 'ntpath': <module 'ntpath' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\ntpath.py'>, 'os.path': <module 'ntpath' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\ntpath.py'>, 'os': <module 'os' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\os.py'>, '_sitebuiltins': <module '_sitebuiltins' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\_sitebuiltins.py'>, 'site': <module 'site' from 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\site.py'>} print(sys.path) ['c:\\Users\\samsung\\OneDrive\\바탕 화면\\python', 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\python39.zip', 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\DLLs', 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib', 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39', 'C:\\Users\\samsung\\AppData\\Local\\Programs\\Python\\Python39\\lib\\site-packages'] | cs |
sys는 위의 출력 결과를 보면 아래와 같이 built-in modules 라는 것을 확인할 수 있습니다.
이미 built-in 되어 있기 때문에 built-in module들이 있는 부분에서 찾게 되며, 파이썬 설치시 기본적으로 내장 모듈에 대한 path 정보가 default 값으로 지정되어 있습니다.
'sys': <module 'sys' (built-in)>
Absolute path
프로젝트 폴더 최상위(root)기준으로 경로가 표시되며, 모듈 및 패키지의 시작부터 끝까지 생략이나 축약되지 않고 명확히 명시된 경로를 뜻합니다. 최상위 루트부터 경로를 표시해야해서 경로를 길게 표시해야하는 단점이 있지만 어디에서 쓰든 항상 같아서 사용하는데 헷갈리지 않다는 장점이 있습니다.
Relative path
최상위 경로가 아닌 현재 자기가 속한 경로를 기준으로 정의하며, 주로 local package안에서 다른 local package를 참조할 때 쓰입니다. absolute path에 비해 간결해 지는 장점이 있지만 프로젝트가 커지고 복잡도가 높아질수록 혼동이 될 수 있으며 만일 파일의 위치가 바뀌면 그에 맞춰 path도 재설정 해주어야 합니다. 따라서 아무리 복잡한 프로젝트라 하더라도 코드의 일관성 및 실수를 막기 위해 absolute path의 사용이 권장됩니다.
위의 구조와 같이 calculator 패키지를 만들었습니다.
main.py에서 상대경로로 add_and_mutiply를 임포트 했을 때 아래와 같이 Import 에러가 발생하였습니다.
ImportError: attempted relative import with no known parent package
에러 발생 원인
-. 상대경로를 이용하여 import 하기
1) __name__변수에 값으로 해당 모듈의 위치를 파악
2) 만약 __main__으로 설정될 경우(실행하는 주 스크립트 파일로 직접 실행할 경우) 패키지의 구조가 실제 파일 위치에 관계없이 최상위 모듈인 것처럼 된다.(즉, __main__이 최상위 파일인 것처럼)
3) 그러면 현재 main.py는 add_and_multiply.py가 있는 calculator패키지와 구분되어 실행을 시키면 내가 제일 최상위 모듈라는 의미로 "ImportError: attempted relative import with no known parent package"라는 에러가 발생
에러 해결 방법
절대경로로 표현하여 최상위 폴더가 calculator라는 것을 알려준다.
from calculator.add_and_multiply import add_and_multiply
5 #출력 확인
파이썬 인터프리터를 실행하는 __name__이 main인 스크립트에서는 모듈을 import할 때 절대경로를 이용해야하고 직관성과 명확성을 위해 이유가 있는 경우를 제외하고는 절대경로를 이용하는 것이 좋습니다.
- __name__ 변수란?
파이썬이 내부적으로 사용하는 특별한 변수 이름입니다. 모듈은 다른 모듈에서 호출되거나, 직접 인터프리터에서 실행이 되는데 인터프리터에서 모듈을 실행하는 경우 __name__ 변수에 __main__ 값이 저장됩니다.- '__main__'는 코드가 실행되는 최상위 스코프의 이름으로, 인터프리터에서 모듈을 실행 시, 모듈의 __name__ 변수는 '__main__'으로 설정이 됩니다.
이 경우 현재 스크립트 파일이 프로그램의 시작점이 맞는지 판단하는 작업으로 스크립트 파일이 메인 프로그램으로 사용될 때와 모듈로 사용될 때를 구분하기 위한 용도로 사용됩니다.
일반적으로 if __name__ == '__main__':와 같은 조건문을 이용하여 구분합니다.
상대경로
위에서와 같은 이유로 add_and_multiply.py가 __main__이 되므로 상대경로를 사용하면 에러가 발생하게 됩니다.
from .multiplication import multiply
ImportError: attempted relative import with no known parent package
절대경로
지금 import하려는 multiply는 같은 디렉토리내에 있으므로 아래와 같이 실행하면 됩니다.
from multiplication import multiply
main 모듈
파이썬 인터프리터를 실행한 main.py가 main이 되므로 main에서만 절대경로로 경로를 잘 설정해주면 main외의 모듈들은 절대경로, 상대경로 상관없이 올바르게 실행됩니다.
- __init__.py
__init__.py 파일은 해당 디렉토리가 패키지의 일부임을 알려주는 역할을 합니다.
이 파일이 없다면 패키지로 인식되지 않으며, 추가적으로 패키지내 모듈을 하나로 묶어주고, import하지 않을 모듈을 설정할 수 있습니다.
- __all__
다음과 같이 *을 이용해서 import할 때엔 해당 디렉토리의 __init__.py 파일안 __all__에 import 가능한 모듈을 따로 정의해주어야합니다.
__all__ 로 정의하지않으면 인식되지 않기때문에 사용할 수 없습니다.
참고자료:
https://velog.io/@sji7532/
https://avengersrhydon1121.tistory.com/132
https://velog.io/@hamsterhamin/Python-ImportError-attempted-relative-import-with-no-known-parent-package
https://velog.io/@anjaekk/python%EC%A0%88%EB%8C%80%EA%B2%BD%EB%A1%9C%EC%83%81%EB%8C%80%EA%B2%BD%EB%A1%9C-%EC%83%81%EB%8C%80%EA%B2%BD%EB%A1%9C-import-%EC%97%90%EB%9F%AC%EC%9D%B4%EC%9C%A0%EC%99%80-%ED%95%B4%EA%B2%B0