TIL | Python - Module

송치헌·2021년 8월 9일
0
post-thumbnail

무엇을 배웠는가?

파이썬은 모듈과 패키지를 어떻게 찾을까?

파이썬은 다음 3가지 장소를 순서대로 확인하며 모듈, 패키지가 존재하는지 체크한다.

  1. sys.modules
  2. built-in modules
  3. sys.path

1. sys.modules

sys.modules
이것은 모듈 이름을 이미 로드된 모듈로 매핑하는 딕셔너리입니다. 모듈의 재로딩과 기타 트릭을 강제하기 위해 조작할 수 있습니다. 그러나 딕셔너리를 교체하는 것은 예상대로 작동하지는 않으며 딕셔너리에서 필수 항목을 삭제하면 파이썬이 실패할 수 있습니다.
참고자료 : https://docs.python.org/ko/3/library/sys.html

sys.modules는 딕셔너리 구조로 이미 import된 모듈과 패키지를 저장하고 있다. 즉, 한번 import된 모듈과 패키지는 또 다시 찾지 않아도 된다.

2. built-in modules

파이썬 자체에서 제공하는 공식 모듈이다.파이썬에서 제공하는 라이브러리가 들어있다.

3. sys.path

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가 나온다.

절대 경로(absolute path)와 상대 경로(relative path)

먼저 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의 모듈에서

  • package2의 class1
  • package2의 하위 package인 subpackage1의 module5에서 function2

두가지를 import하려면 다음과 같이 하면 된다.

# package2/module3.py
 
from . import class1 #현재 파일에서 class1을 import한다.
from .subpackage1.module5 import function2 #현재 패키지에서 하위패키지인 subpackage1으로 들어가고 그 패키지에서 module5로 들어가서 그 파일에 있는 function2를 import한다.

.(dot)은 import가 선언되는 파일의 현재 위치를 뜻한다.
..은 현재 위치에서 상위 디렉토리로 간다는 뜻이다.

Assignment


1. sys.modulessys.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']

2. 파이썬은 sys모듈을 어떻게 찾을까?

파이썬이 모듈을 탐색하는 순서는 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모듈이 들어있는 것을 확인할 수 있다.


3. 절대 경로(absolute path)와 상대 경로(relative path)의 차이

  • 절대 경로

임포트하는 파일의 위치에 상관없이 프로젝트의 최상위 디렉토리부터 경로를 탐색한다.

  • 상대 경로

임포트하는 파일을 기준으로 경로를 탐색한다.

파일의 위치가 바뀌는 경우가 있을 수도 있고 헷갈리기도 하므로 절대 경로로 설정하는 것을 권장한다.


4. 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)

5. main.py에서 상대 경로로 add_and_multiply 를 임포트 했을 때 에러가 발생하는 이유와 해결책은?

해당 파일에서 스크립트를 실행할 경우 __name____main__이 된다. 따라서 main.py에서 파이썬 인터프리터를 실행하면 if문인 if __name__ == '__miain__'을 만족한다.
그런데 __name__ = '__main__'일 경우 파이썬에서 최상위 디렉터리로 인식하기 때문에 절대 경로로 설정하여 최상위 디렉터리가 어딘지 알려주어야 한다.


6. 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로 해주면 된다.

7. __init__의 역할

디렉터리안에 __init__.py파일을 생성해주지 않으면 파이썬에서는 그 디렉터리를 package로 인식하지 못한다. python3.3버전부터는 __init.py__가 없어도 패키지로 인식하는데 하위 버전과의 호환성을 위해 작성해 주는 것이 좋다.


어디에 적용했는가

마지막 파이썬 과제인 module부분을 공부하면서 절대 경로와 상대 경로를 각각 설정해 보며 어떤 오류가 있는지 확인하였다.

어려웠던 점은 무엇인가

경로를 상대 경로로 할 때와 절대 경로로 할 때, 어떤 파일을 실행하는지에 따라 계속 바뀌기 때문에 어려웠다. 웬만하면 절대 경로로 설정하는 것이 좋을 것 같다.

profile
https://oraange.tistory.com/ 여기에도 많이 놀러와 주세요

0개의 댓글