Python - How Import Statement Finds Modules & Packages

Seob·2020년 7월 22일
2

TIL

목록 보기
16/36

sys.modules & sys.path

파이썬에서는 모듈과 패키지를 다음과 같은 순서로 찾는다.
1. sys.modules
2. built-in modules
3. sys.path

built-in modules는 파이썬에서 제공하는 파이썬 공식 라이브러리들이다. 이미 파이썬에 포함되어 나오므로 파이썬이 찾기에 쉽다. sys는 파이썬에 포함되어 있는 모듈이다. 그러므로 다음과 같이 sys모듈을 import해서 sys.modulessys.path를 출력할 수도 있고 수정할 수도 있다.

import sys
print(sys.path)
print(sys.modules)

sys.modules

sys.modules는 파이썬이 모듈이나 패키지를 찾기 위해서 가장 먼저 확인하는 곳이다. 그리고 sys.modules는 단순한 딕셔너리구조이다. 이미 import 되어있는거나 한번 import한 모듈과 패키지를 저장하므로 파이썬이 또 다시 찾지 않아도 된다. 하지만 새로 import 하는 모듈은 sys.modules에서 찾을 수 없다.

정리하면 다음과 같다.

  • 파이썬이 제일 먼저 모듈이나 패키지를 찾는 곳
  • 딕셔너리 구조
  • import 되어있는 모듈과 패키지 저장
  • 새로 import 하는 모듈은 찾을 수 없음

sys.path

sys.path는 파이썬이 가장 나중에 보는 장소이다. sys.path는 string 요소를 갖고있는 list 이다. (list of strings)
각 string요소들의 경로는 다음과 같이 나타낸다.

['',
 '/Users/song-eun-u/anaconda3/bin',
 '/Users/song-eun-u/anaconda3/lib/python36.zip',
 '/Users/song-eun-u/anaconda3/lib/python3.6',
 '/Users/song-eun-u/anaconda3/lib/python3.6/lib-dynload',
 '/Users/song-eun-u/anaconda3/lib/python3.6/site-packages',
 '/Users/song-eun-u/anaconda3/lib/python3.6/site-packages/aeosa',
 '/Users/song-eun-u/anaconda3/lib/python3.6/site-packages/IPython/extensions',
 '/Users/song-eun-u/.ipython']

파이썬은 이 리스트의 각 경로를 처음부터 끝까지 확인하면서 해당 경로에 import 하려는 패키지나 모듈이 있는지 확인한다.

sys.path에서도 찾지 못한다면 ModuleNotFoundError에러를 리턴한다.

정리하면 다음과 같다.

  • sys.modules => built-in modules 순서로 경로를 찾고 못찾으면 sys.path에서 찾는다.
  • sys.path에서도 못찾으면 ModuleNotFoundError에러를 리턴한다.
  • string 요소들로 이루어진 list 이다.
  • 리스트의 처음 요소부터 마지막 요소까지 하나 하나 찾는다.

다른점

가장 큰 차이점은 sys.modules는 dictionary형태이고, sys.path는 list형태이다. 그리고 sys.modules는 파이썬이 가장 먼저 경로를 찾는 곳이고, sys.path는 가장 나중에 보는 곳이다.

sys 모듈의 위치

파이썬은 스크립트 실행시 PYTHONPATH, 환경설정, 변수, 위의 모듈들을 순차적으로 실행한다. 이러한 써드파티는 설치시 자동으로 절대경로가 설정이되어 찾기 쉽다.

Absolute path와 relative path의 차이점

Absolute path는 이름 그대로 절대경로이다. import 하는 파일이나 경로에 상과없이 항상 경로가 동일하기 때문이다.

위와 같은 my_app프로젝트로 예를 들면, package1package2를 절대경로로 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

와 같이 표시될 수 있는데 이미 my_app 안에 있기 때문에 my_app은 생략되고 다음과 같이 표현한다.

package2.subpackage1.module5.py

#from import 로 표현
from package2.subpackage1.module5 import function2

my_app프로젝트 내에서 어느 파일이나 어느 위치에서 import 하더라도 경로가 항상 같기때문에 absolute path라고 한다.

하지만 절대경로는 경로가 복잡해질수록 경로가 길어질 수 있다는 단점이 있다. 이 단점을 보완할 수 있는것이 relative path이다. Relative path는 프로젝트의 최상단 디렉토리가 기준이 아니라 import 하는 위치를 기준으로 경로를 정의하기때문에 일반적으로 local package 안에서 다른 locap package를 import 할 때 사용된다.

예를 들어, package2의 module3에서 package2의 class1과 package2의 하위 package인 subpackage1의 module5의 function2 함수를 import하려고 하면 다음 처럼 할 수 있다.

# package2/module3.py

from . import class1
from .subpackage1.module5 import function2

여기서 .은 import가 선언되는 파일의 현재 위치를 의미한다. ..은 상위 디렉토리로 가는 경로로 다음과 같이 사용할 수 있다.

# subpackage1/module5.py
from ..module4 import class4

Relative path는 Absolute path의 경로를 보다 짧게 표현할 수 있지만, 헷갈리기가 쉽고 파일 위치가 변경되면 경로 위치도 변경되어야 하는 단점이 있다. 그래서 보통의 경우 Absolute path를 사용하는 것이 권장된다.

calculator package 만들기




위와 같은 상태일때 main.py를 실행하면 다음과 같은 오류가 발생한다.

이 오류가 발생하는 이유는 main.py가 최상위 경로에 있기 때문이다. 이런 경우에는 상대경로는 사용할 수 없고 절대경로만 사용해야 한다.

main.py에서 절대경로로 설정해주면 제대로 import 되는 것을 다음과 같이 확인할 수 있다.

add_and_multiply.py에서 multiply함수를 절대경로와 상대경로도 각각 임포트 해보고 main 모듈과 차이점을 생각해보고 결과를 출력해 보세요.

add_and_multiply.py에서는 상대경로나 절대경로나 둘 다 muliply함수를 import 했을 때 main.py에서 정상적으로 동작하였다. 위에서 말한 것처럼 main 모듈에서는 상대경로로 접근할 수 없지만 그렇지 않은 경우에는 상대경로로 접근해도 문제 없다.

__init__.py 파일의 역할

__init__.py 파일에는 아무코드도 없지만 init 파일은 해당 디렉토리가 패키지임을 알려주는 역할을 한다. __init__.py는 어떤 패키지나 모듈을 불러올 때 우선 실행되는 파일이다.

가끔은 package가 import 될때 초기 설정을 해줘야 할때가 있다. 파이썬은 __init__.py 파일을 통해 package 초기 설정을 가능하게 해준다.

__init__.py 파일이 있으면 package가 import 될때 __init__.py 파일의 코드들이 자동으로 실행된다.

일반적으로 다음과 같은 초기 설정을 할 수 있다.

  • Import 할때 경로의 총 길이 줄여주기
  • Package에서 import 할 수 있는 변수/함수/클래스 제한하기
  • 그 외 package가 import될때 꼭 먼저 실행되어야 하는 코드들

경로의 총 길이 줄여주기


현재 pkg에서 mod1의 func2 라는 함수를 import 하여 사용하기 위해서는 다음과 같이 해야한다.

import pkg.mod1

pkg.mod1.func2()

func2 함수를 호출 할때마다 매번 모든 경로를 다 타입해줘야 하기때문에 번거롭지만 __init__.py 파일을 통해 함수의 경로를 줄여줄 수 있다.

__init__.py 파일에 먼저 다음과 같이 import 해주면 된다.

# __init__.py
from .mod1 import func2

이렇게 import한 후 다음과 같이 함수를 짧게 호출할 수 있다.

# main.py
from pkg import func2

func2()

변수/함수/클래스 제한하기

__init__.py 파일을 사용해서 import 할 수 있는 변수/함수/클래스를 제한할 수 있다.

내부적으로만 사용되어야 하는 함수가 package 외부에서 import되어 사용되는 것을 막기 위해서는 __all__ 변수를 지정해 줄 수 있다.

  • package를 통해 import 될 수 있는 요소들은 모두 __all__ 변수를 통해 정의 된다.
  • __all__ 변수의 default 값은 모든 함수/변수/클래스 이다.
  • __all__ 변수를 따로 정의해줌으로 import 될 수 있는 요소들을 제한할 수 있다.
  • __all__ 변수는 string 값의 요소를 가지고 있는 list 이다. (list of strings).
  • import 되길 원하는 요소들을 string으로 list에 선언한다.
# __init__.py
from .mod1 import func2
from .mod2 import func3

__all__ = ['func2', 'func3']
# main.py
from pkg import *

func2()
func3()
func4() ## <== Error. func4 함수는 __all__ 에 정의되지 않았으므로 import 될 수 없음.
profile
Hello, world!

0개의 댓글