Dev/Python

[Python 기초] Module & Package 이해하기 3

sincerely10 2020. 6. 27. 16:47
반응형

<Module & Package 이해하기 Series>
[Python 기초] Module & Package 이해하기 1(Module)
[Python 기초] Module & Package 이해하기 2(Package)
[Python 기초] Module & Package 이해하기 3(import 순서)
[Python 기초] Module & Package 이해하기 4(실습)

Python Module & Package 이해하기의 세 번째 포스트 입니다.
이번 포스트에서는 Python이 어떤 순서로 Package/Module을 찾는지와
import 할 때 Absolute Path(절대경로)와 Relative Path(상대경로)에 대한 비교를 해보겠습니다.

포스트 항목은 다음과 같습니다.
1. import의 순서
2. sys.modules와 sys.path 관찰하기(비교)
3. sys 패키지에 대한 이해
4. Absolute Path(절대경로)와 Relative Path(상대경로)

1. import의 search 순서

Python에서 Library(Package를 모아놓은 개념) 종류는 다음과 같이 세 가지가 있습니다.

  • 정의하거나 install 않아도 사용할 수 있는 Package(ex: time, sys, os ..) -> Python Standard Library

  • Install 해서 사용하는 Package(ex: Django, PyMySQL ..) -> Python Installing Packages

  • user가 직접 만든 Package 

 

이러한 Module/Package들은 어디서 어떤 과정으로 찾는걸까요?

Python에서는 다음 세 곳에서 순서대로 찾습니다.

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

하나씩 어떤 역할을 하는지 확인하겠습니다.

1) sys.modules

Python이 Module/Package를 찾기 위해 가장 먼저 확인하는 곳입니다.
sys.modules는 단순한 Direcotry 입니다.
그리고 이미 import된 module과 Package를 저장하고 있습니다.
따라서 새로 import 하는 Module/Package는 sys.modules에서 찾을 수 없습니다.

sys.modules는 dict 구조의 변수 입니다.
제 mac에서 별도의 설정 없이 sys.modules 변수를 출력 해보았습니다.

import sys
>>> print(sys.modules)
{'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, 'copyreg': <module 'copyreg' from '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/copyreg.py'>, ....중략..... '_datetime': <module '_datetime' from '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload/_datetime.cpython-37m-darwin.so'>}

너무 길어서 다 출력할 수 없었습니다.

2) built-in modules

Built-in(내장된) 라는 단어처럼 Python에서 제공하는 공식 Library 들 입니다.
위에서 언급한 Library 종류 중 첫 번째인 Python Standard Library(os,  sys, time 등)이 여기에 해당됩니다.
이 Library들은 별도의 설치 없이 사용할 수 있습니다.

3) sys.path

마지막으로 sys.path가 있습니다.
sys.path는 package의 __init__ 변수와 같이 String Value로된 list 입니다.
제 mac에서 별도의 설정 없이 sys.path 변수를 출력 해보았습니다.

>>> print(sys.path)
['', '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/gonholee/Library/Python/3.7/lib/python/site-packages', '/usr/local/lib/python3.7/site-packages']

sys.path의 element 중 하나인 '/usr/local/lib/python3.7/site-packages'를 보면 각각이 경로를 의미합니다.
즉, Python에서 각 element를 확인하며 해당 경로에 import 하고자 하는 Package가 위치해 있는지 확인 합니다.

그리고 sys.path와 sys.modules의 name station이 존재합니다.
그렇습니다. sys는 Python Standard Library 중 하나로 import 해서 사용할 수 있습니다.

즉, Python에서 module/package를 찾을 때는
sys.modules -> built-in modules -> sys.path 순서로 찾고 sys.path에서도 없다면, 모듈을 찾을 수 없다는 error를 반환합니다.

2. sys.modules와 sys.path 관찰하기(비교)

sys.modules와 sys.path에 대해서 조금만 더 깊게 확인해보겠습니다.

위의 출력화면에서 sys.modules 그리고 sys.path를 보면 명확하게 비교되는 point가 있습니다.
우선 둘의 자료형이 다릅니다. sys.modules는 dict형 자료구조이며 sys.path는 list형 자료구조를 갖습니다.

저의 mac에서 sys.modules에서 출력한 내용중에 마지막 key-value pair(짝)을 보시면 다음과 같습니다.

'_datetime': <module '_datetime' from '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload/_datetime.cpython-37m-darwin.so'>

날짜와 시간을 입력할 수 있게 해주는 datetime이라는 모듈에 대한 위치를 콕 찝어서 알려줍니다.
key는 module name 이고, value는 해당 모듈의 경로(path) 입니다.

그렇다면, sys.path는 어떨까요?
우선 출력결과가 sys.modules에 비해 상당히 간결 합니다.
sys.modules의 element의 수가 60인데 반해 sys.path는 6입니다.
또한, 직접적인 경로가 아닌 특정 패키지를 모아놓은 site-packages와 같은 directory까지의 경로임을 명확히 확인할 수 있습니다.

제가 설치했던 Django라는 패키지를 찾아 보고자 합니다.
먼저 sys.modules에는 Django를 key로 하는 element가 없었습니다.
따라서 sys.path의 element 중 하나이고 외부 패키지를 install 할 때 저장되는 위치인 /usr/local/lib/python3.7/site-packages/ 에서 찾아 보도록 하겠습니다.

ll /usr/local/lib/python3.7/site-packages/ 의 결과, 화면에 한 번에 안 보일 정도로 많은 패키지들이 나옵니다.
무려 77개가 나옵니다..

ls /usr/local/lib/python3.7/site-packages/ |grep django 로 찾아보니
django라는 directory를 찾았습니다!

다시 정리를 하면,
sys.modules는 Dict 형태로 module/package와 각 module/package의 경로를,
sys.path는 list 형태로 module/package가 설치된 Directory를 담고 있습니다.

3. sys 패키지에 대한 이해

sys 패키지 덕분에 module을 쉽게 찾을 수 있다는 것을 알았습니다.
그런데 사실 sys 패키지가 정확히 어떤 일을 하는지 모르고 지나쳤습니다.
Python 공식 문서에서는 다음과 같이 정의 합니다.

This module provides access to some variables used or maintained by the interpreter and to functions that interact strongly with the interpreter. It is always available.

 -> 인터프리터에 의해 사용되거나 관리되는 변수와 인터프리터와 강력하게 상호작용하는 함수에 대한 접근을 제공 하는 모듈입니다.

위에서 제가 sys.path와 sys.modules를 출력하기 위해 import sys를 출력한 것을 보셨나요?
분명히 sys도 패키지였는데, 패키지의 경로를 입력해주지는 않았습니다.

다시 1.1의 sys.modules 출력결과를 보겠습니다.
가장 첫 번째 element가 다음과 같습니다.

'sys': <module 'sys' (built-in)>

built-in이라는 것을 볼 수 있습니다.
sys 모듈은 조금 전에 1. import의 search 순서 에서 보았던 패키지 확인에서 두 번째인 buillt-in modules 입니다.
Python에서는 built-in Module에서 경로를 가져와 복잡한 절대경로(바로 아래에서 학습하는)를 적지 않아도 되는 것 입니다.

4. Absolute Path(절대경로)와 Relative Path(상대경로)

바로 위 3. sys 패키지에 대한 이해에서 built-in 패키지는 별도의 패키지 경로 없이도 불러올 수 있다고 했습니다.

이는 pip로 설치한(조금 전 Django와 같은) 패키지도 마찬가지 입니다.
설치만 하면 바로 import 할 수 있습니다.
설치한 패키지들이 있는 site-packages에 저장되기 때문입니다.

그러나 직접 개발한 local package가 문제입니다.
이러한 패키지는 import 할 때, package의 위치에 맞게 import 경로를 정확하게 선언해야 하기 때문입니다.

먼저 다음 그림과 같은 경로를 가정하겠습니다.

my_app이라는 프로젝트 명을 가지고 있고 package1과 package2라는 두 개의 package를 갖고 있습니다.
package2는 subpackage1이라는 sub package도 갖고 있습니다.

여기서 기존에 선언한 방법으로 package1과 package2를 import 해보겠습니다.

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

모두 최상단 Directory인 package1과 package2로 부터 시작되는 것을 볼 수 있습니다.
특히, 코드 마지막줄의 module5를 import 하기 위해서는
my_app -> package2 -> subpackage1 -> module5 까지 가야 import 할 수 있습니다.

제 mac에서 본다면,
my_app/package2/subpackage1/module5.py 형태로 되어 있을 것입니다.

프로젝트를 만들 때, my_app 경로에서 시작하기 때문에 별도의 위치정보가 필요 없이 
package2 부터 시작하는 것입니다.
그리고, python은 이 경로 구분을 .(dot notation)으로 구분합니다.

최종적으로 module5를 import 하기 위해서

from package2.subpackage1.module5 import function2

위 코드와 같이 적어주어야 합니다.

이러한 경로는 my_app 프로젝트 내에서는 어느 파일이나 위치에서 import 하던지 동일한 경로입니다.
이러한 경로를 Absolute Path(절대경로) 라고 합니다.

local package에서 일반적인 경우는 대부분 이 absolute path를 사용하는 것을 권장합니다.
그렇지만 경로가 길어질 수 있다는 단점이 있습니다.

이런 단점을 보완하기 위해 Relative Path(상대경로)를 사용할 수 있습니다.

relativa path는 최상단을 기준으로 하는 것이 아닌, import 기능을 사용하는 현재 파일의 위치를 기준으로 합니다.
예를 들어 package2의 module3.py라는 파일에서
package2의 class1과 package2에서 subpackage1의 module5의 function2를 import 한다고 해보겠습니다.

# package2/module3.py

from . import class1
from .subpackage1.module5 import function2

from 뒤에 써진 .(dot notation)의 역할은 현재 directory를 가리키는 것입니다.
현재 위치가 package2/module3.py 이므로 .만 찍어서 class1을 import 했습니다.

또한 현재 directory의 이전 direcotory를 가리킬 때는 ..을 사용합니다.

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

위의 코드에서
현재 위치가 my_app/package2/subpackage1/module5.py 이므로
전 directory는 my_app/package2 입니다.
여기서 module4.py의 class4를 사용하기 때문에 ..으로만 접근이 가능합니다.

Linux File System과 동일한 것을 확인 하였습니다.

Relative Path를 사용한다면 편하긴 하겠지만, 사람이 작성하는 코드이기 때문에 다른 파일등과 혼동이 생겨 얼마든지 실수 할 수 있는 가능성이 있습니다.
따라서 조금전 언급한 내용과 같이 Absolute Path를 사용하는 것을 권장드립니다.

다음 포스트에서는 직접 패키지를 만드는 실습을 해보겠습니다.

 

반응형