TIL. 파이썬은 모듈과 패키지를 어떻게 찾아요?

Kim Chioh·2021년 1월 15일
0
post-thumbnail
  • 위에 그림 처럼 폭풍이 부는 곳에서 have fun 하기 싫다면, 모듈과 패키지를 찾는 방법에 대해 한번은 알아두는 것이 좋습니다. 하하;

근데 파이썬은 module과 package를 어떻게 찾을까요?
Import Search 순서
sys.modules
built-in modules
sys.path
Absolute Path & Relative Path
sys.modules 와 sys.path의 차이점
파이썬은 sys 모듈의 위치를 어떻게 찾을 수 있을까요?
Absolute path와 relative path의 차이점

파이썬은 module과 package를 어떻게 찾을까요?

예를 들어, abc 라는 package가 있다고 가정해 보겠습니다. 그리고 abc package를 사용하려면 다음처럼 import 해야 합니다.
/br
import abc
/br
여기서 abc는 단순한 파이썬 파일(모듈일 경우) 이거나 파이썬 파일들을 담고 있는 디렉토리(package의 경우) 입니다. 그러므로 해당 파일이나 디렉토리가 어디있는지 파이썬이 찾을 수 있어야 import가 가능할것입니다.

그럼 파이썬은 모듈/package를 어떻게 찾을까요?

파이썬은 다음 3가지 장소를 순서대로 보면서 찾습니다.

sys.modules->

built-in modules->

sys.path

sys.modules

파이썬이 모듈이나 package를 찾기위해 가장 먼저 확인하는 곳입니다.

sys.modules는 단순한 dictionary 입니다. 그리고 이미 import된 모듈과 package들을 저장하고 있습니다.

즉, 한번 import된 모듈과 package들은 파이썬이 또 다시 찾지 않아도 되도록 하는 기능을 가지고 있습니다.

그러므로 새로 import 하는 모듈은 sys.modules 에서 찾을 수 없습니다.

built-in modules

파이썬에서 제공하는 파이썬 공식 라이브러리들 입니다.

Built-in 모듈들은 이미 파이썬에 포함되어 나오므로 파이썬이 쉽게 찾을 수 있습니다.

sys.path

마지막으로 보는 장소가 바로 sys.path 입니다.

sys.path는 기본적으로 list이며 string 요소들을 가지고 있는 list 입니다.

각 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']

그러므로 파이썬은 list의 각 경로를 하나 하나 확인하면서 해당 경로에 import 하고자 하는 package가 위치해 있는지 확인합니다.

참고로 sys 는 파이썬에 포함되어 있는 모듈입니다. 그러므로 다음 처럼 sys 모듈을 import 해서 sys.modules와 sys.path 를 출력할수도 있고 수정 할 수 도 있습니다.

import sys

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

정리를 하자면, 파이썬은 import 하고자 하는 모듈과 package를 찾을때에 먼저 sys.modules를 보고, 없으면 파이썬 built-in 모듈들을 확인 하고 마지막으로 sys.path에 지정되어 있는 경로들을 확인해서 찾습니다.

sys.path 에서도 못찾으면 ModuleNotFoundError 에러를 리턴합니다.

Absolute Path & Relative Path

파이썬의 built-in 모듈과 pip 을 통해 설치한 외부 모듈 및 package는 일반적으로 import 하는데 큰 문제가 되지 않습니다. Built-in 모듈은 당연히 잘 찾아지고, pip 으로 설치한 외무 모듈도 자동으로 site-packages 라는 디렉토리에 설치가 되는데, 이 site-packages 는 sys.path에 이미 포함되어 있기때문에 찾는데 문제가 없습니다.

문제는 직접 개발한 local package 입니다. 직접 개발한 local package를 import 할때는 해당 package의 위치에 맞게 import 경로를 잘 선언해야 합니다. Local package를 import 하는 경로에는 absolute path 와 relative path 가 있습니다.

absolute path

Absolute path는 이름 그대로 절대 경로 입니다. 왜 절대 경로인가 하니, import를 하는 파일이나 경로에 상관없이 항상 경로가 동일하기 때문입니다.
다음과 같은 프로젝트를 예를 들어 보겠습니다:

└── my_app
    ├── main.py
    ├── package1
    │   ├── module1.py
    │   └── module2.py
    └── package2
        ├── __init__.py
        ├── module3.py
        ├── module4.py
        └── subpackage1
            └── module5.py

my_app 이라는 프로젝트 이며 package1과 package2 라는 2개의 package를 가지고 있습니다.

그리고 package2는 subpackage2 라는 중첩 package를 가지고 있습니다.

Absolute path를 사용해 package1 과 package2를 import 하면 다음과 같습니다.

from package1 import module1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2

경로들의 시작점이 전부 "my_app" 프로젝트의 가장 최상위 디렉토리에서 시작하는것을 볼 수 있습니다.

예를 들어, subpackage1의 module5 모듈의 function2 함수를 import 하기 위해서는 다음 경로를 거치게 됩니다.

/br
my_app => package2 => subpackage1 => module5.py
/br

이걸 리눅스의 directory 경로 형식으로 바꾸면 다음처럼 표현 할 수 있습니다.

my_app/package2/subpackage1/module5.py
윈도우스 형식이라면 다음과 같습니다.

my_app\package2\subpackage1\module5.py

파이썬에서는 slash (/) 나 back slack() 대신에 dot (.) 을 사용해서 경로를 표현 합니다.

my_app.package2.subpackage1.module5.py

이미 my_app 프로젝트 안에 있으므로 my_app 은 생략됩니다. 그러므로 다음처럼 경로를 표현하게 되는 것입니다.

package2.subpackage1.module5.py

이걸 from import 키워드를 사용해 import 하게 되면 다음 처럼 되는 것입니다.

from package2.subpackage1.module5 import function2

my_app 프로젝트 내에서는 어느 파일, 어느 위치에서 import 하던지 경로가 항상 위와 같이 동일하게 되므로 absolute path 라고 하는 것입니다.

참고로 current directory 라고 하는 현재의 프로젝트 디렉토리는 default로 sys.path 에 포함되게 됩니다.

그러므로 absolute path는 current directory 로 부터 경로를 시작하게 되는것입니다.

일반적으로 local package를 import 할때는 absolute path를 사용하면 됩니다.

다만 absolute path를 사용하게 되면 한가지 단점이 있는데 바로 경로가 길어질 수 있다는 점입니다.

그래서 이러한 단점을 보완하기 위해서 relative path를 사용할 수 있습니다.

relative path

Relative path 는 absolute path와 다르게 프로젝트의 최상단 디렉토리를 기준으로 경로를 잡는게 아니라 import 하는 위치를 기준으로 경로를 정의합니다. 그래서 일반적으로 relative path는 local package 안에서 다른 local package를 import 할때 사용됩니다.

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

# package2/module3.py

from . import class1
from .subpackage1.module5 import function2

여기서 dot(.)은 import가 선언되는 파일의 현재 위치를 이야기 합니다. 현재위치는 package2/module3.py 이므로 현재 위치에서부터 원하는 모듈의 경로만 선언해주면 되는 것입니다.

또한 dot 2개를 사용할 수도 있습니다. dot 2개(..) 는 현재위치에서 상위 디렉토리로 가는 경로입니다.

# subpackage1/module5.py

from ..module4 import class4

Relative path는 선언해야 하는 경로의 길이를 줄여준다는 장점은 있지만 헷갈리기 쉽고 파일 위치가 변경되면 경로 위치도 변경되어야 하는 단점이 있습니다. 그러므로 웬만한 경우 absolute path를 사용하는게 권장 됩니다.

sys.modules와 sys.path의 차이점

1.형태
sys.modules는 딕셔너리형, 그리고 이미 import된 모듈과 패키지들을 저장.
sys.path 리스트형 리스트형, 스트링 요소들을 가지고 있음
2. 순서
sys.module을 가장 먼저 확인함.
sys.path를 가장 나중에 확인함.

2.sys 도 import 해야하는 모듈입니다. 파이썬은 sys 모듈의 위치를 어떻게 찾을 수 있을까요?

sys.modules은 파이썬에 이미 포함 된 module중 하나이다.
sys.modules 디렉토리나 sys.path 경로를 확인하여 module의 위치를 확인 할 수 있다.

Absolute path와 relative path의 차이점을 서술해 주세요.

  1. 용도
    absolute path는 최상위 디렉토리에서부터 시작해 모든 경로를 거침
    따라서 어느위치에서 import 하던지 경로가 항상 동일(current directory 시작)
    일반적으로 local package를 import 할때 사용
    /br
    relative path는 이처럼 복잡하고 긴 absolute pate의 단점을 해결
    최상단 디렉토리를 기준으로 경로를 잡는게아니라 import하는 위치를 기준으로 경로를 정의.
    local package 안에서 다른 local package 를 import할때 사용.
    단점: 간단하지만 그만큼 표현이 정확하지 않아서 헷갈리기 쉬움.

calculator 패키지 만들기

앞으로 개발하면서 많은 패키지를 사용할 것이고 만들어야 하는 경우도 있을 수 있기 때문에 이번과제는 직접 패키지를 만들어 보는 과제 입니다.

로컬 환경에서 파이썬을 설치하고 진행해 주세요.

디렉토리 구조는 다음과 같고 각 파일의 코드내용은 다음과 같습니다.

init.py 파일에는 아무코드도 없지만 init 파일은 해당 디렉토리가 패키지임을 알려주는 역할을 합니다.

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_mutiply 를 임포트 했을 때 발생하는 에러를 확인하고
다음의 파이썬 공식 문서를 참고해서 main module 에서는 패키지의 모듈을 어떻게 임포트 해야하는지 블로깅 해주세요.

다음과 같은 에러가 나타난다.

ImportError: attempted relative import with no known parent package

해당 에러는 상대 경로로 import 했을 때 main.py 와 add_and_multiply와 상대적으로 경로를 받고 있지 않다는 이야기다.

[main.py]

[output]

따라서 main.py를 절대경로(absolute path)로 수정하면 결과는 5가 나온다.

현재 디렉토리를 기준으로 찾아갈 파일의 상대적인 경로를 찾아야 하는게 realative path 이다.
그렇기 때문에 main.module이 import 할 다른 package안에 속해 있지 않을 때는 절대경로를 통해 모듈이나 패키지를 import 해줘야 한다.

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

실행이 가능하다.

상대 경로 import:

실행 오류

ImportError: attempted relative import with no known parent package

add_and_multiply.py는 calculator 패키지의 모듈이다.

main.py는 add_and_multiply.py 모듈에 있는 함수를 받아 실행하는 모듈이다.

init.py 파일의 역할에 대해서도 정리해서 블로깅 해주세요.
package가 import 될때 초기 설정으로 init.py 파일을 만든다.

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

  1. Import 할때 경로의 총 길이를 줄여준다.
    import pkg.mod1

pkg.mod1.func2()
매번 모든 경로를 다 타입해줘야 함.
init.py 파일을 통해 함수의 경로를 줄여줄 수 있다.
init.py 파일에 먼저 한번 import 해주면 된다.
#init.py
from .mod1 import func2

#main.py
from pkg import func2

func2()
2. Package에서 import 할 수 있는 변수/함수/클래스 제한하기
init.py 파일을 사용해서 import 할 수 있는 변수/함수/클래스를 제한할 수 있다.
all 변수를 지정: 외부에 노출되면 안 되는 함수가 package 외부에서 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 될 수 없음.
3. 그 외 package가 import될때 꼭 먼저 실행되어야 하는 코드들을 실행해준다.

profile
Just do what to do

0개의 댓글