How Import Statement Finds Modules & Packages

choonghee-lee·2020년 7월 23일
1

WeCode

목록 보기
5/20

서론

파이썬을 공부하면서 모듈은 많이 만들어봤지만 패키지를 만들어 본 경험을 거의 없다. 패키지 다운로드는 많이 받아봤다 ㅎㅎㅎ. 사전스터디때 파이썬 문법을 공부해서 제법 잘 안다고 생각했지만 막상 읽어보니 머리가 약간 어질하다. 열심히 해보겠습니다 🙇.

1. sys.modules vs. sys.path

"둘 다 sys 모듈을 import 해야함다!!"라고 외치며 공통점이 먼저 눈에 들어오지만 여기서는 차이점을 알아봐야한다.

sys.modules

전체적으로 sys 모듈에 대해 정리해 놓은 페이지가 있다. 텐션이 떨어져있기 때문에 생각과 요약을 잠시 접어두고 적혀있는 그대로 번역을 해보겠습니다 (사실 짧다ㅋㅋㅋ).

sys.modules의 값은 이미 로드된 모듈이 그 이름과 쌍을 이루는 딕셔너리입니다.

로드된 모듈을 다시 불러오는 일이 없도록 관리하기 위한 딕셔너리라고 생각한다.

sys.path

sys.path는 파이썬 라이브러리들이 설치되어 있는 디렉터리들을 보여준다. 만약 파이썬 모듈이 위의 디렉터리에 들어 있다면 모듈이 저장된 디렉터리로 이동할 필요없이 바로 불러서 사용할 수가 있다. 아래는 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/choonghee/Library/Python/3.7/lib/python/site-packages', 
'/usr/local/lib/python3.7/site-packages']

리스트 형태이다. 그렇다! 필요한 모듈이 있는 경로를 append() 함수로 추가할 수 있다!!

결론

파이썬이 모듈을 확인할 때,
sys.modules는 파이썬이 제일 먼저 확인하는 장소이며 Dictionary이고
sys.path는 파이썬이 제일 마지막에 확인하는 장소이고 List이다.

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

PyMOTW-3 페이지를 보고 영감을 얻었다. 참고로 PyMOTW란 Python Module of the Week이다.

파이썬 인터프리터는 일부 내장된 C 모듈과 함께 컴파일 된다. 그래서 해당 모듈들은 따로 분리된 라이브러리일 필요가 없다. 이러한 내장 모듈들은 엄밀히 말하면 import 되는게 아니기 때문에 sys.modules에 나타나지 않는다. 내장 모듈들의 이름을 보고싶다면 다음과 같은 코드를 사용한다.

import sys

for name in sys.builtin_module_names:
    print(name)

그럼 다음과 같은 결과가 나타난다.

_abc
_ast
_codecs
_collections
_functools
..
중간생략
..
posix
pwd
sys
time
xxsubtype
zipimport

결론은 인터프리터에 이미 내장되어 있으므로, 인터프리터를 실행하면 그냥 로드된 것이다!

3. absolute path vs. relative path

위의 타이틀만 읽어보면, 리눅스에서 명령어에 경로를 써줘야할 때가 생각난다. 파이썬에서는 무엇을 의미하는지 궁금하다!

absolute imports

Absolute imports는 프로젝트의 루트 폴더에서 부터 full-경로를 사용하여 import될 리소스를 명시하는 것이다.

예를 들어볼까요?

당신은 다음과 같은 디렉토리 구조를 가지게 됩니다 후후... 😈

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

당신은 아래의 것들을 코드로 표현해야 합니다 후후... 😈

  1. package1/module2.py의 function1이 필요합니다.
  2. package2/__init__.py에 class1이 있다고 가정하고 class1이 필요합니다.
  3. package2/subpackage1/module5.py의 function2도 필요합니다.

코드로 표현해보자면

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

여기서 눈여겨 봐야할 점은 각 패키지 또는 파일의 상세한 경로를 탑-레벨 패키지 폴더에서 부터 지정해줘야 한다는 점이다. 파일 경로와 비슷한 점이 있지만 슬래쉬(/)대신 점(.)을 사용해야한다.

Absolute Imports의 장단점

장점! 일단 명쾌하게 import된 리소스의 위치를 파악할 수 있다. 사실, PEP8은 absolute imports를 명시적으로 추천한다!

하지만.. 디렉토리의 구조가 복잡해지면 살벌해진다 🔪.

from package1.subpackage2.subpackage3.subpackage4.module5 import function6

이런 경우에는 relavtive imports를 사용하자!

relative imports

relative imports는 현재 위치에 상대적으로 import될 리소스를 명시하는 것이다. implicit한 방법과 explicit한 방법이 있지만 파이썬 3에서 implicit relative imports는 deprecated되었으므로 다루지 않는다.

예시를 봅시다

relative imports 구문은 import될 모듈, 패키지, 객체의 위치 뿐만 아니라 현재의 위치에 따라 달라진다. 위에서 봤던 디렉토리 구조를 다시 사용해 예시를 들어본다.

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

시작하기전 알아야하는 것은 하나의 점(.)은 현재 위치와 같은 디렉토리의 모듈이나 패키지를 가리킨다. 두 개의 점(..)은 현재 위치의 부모 디렉토리를 가리킨다는 것이다. 세 개의 점을 이용해서 부모의 부모 디렉토리도 가리킬 수 있고 계속 그렇게 점을 늘려가도 되지만, 개인적으로 본 기억이 없다.

# package1/module1.py
from .module2 import function1

module1에서 module2의 function1을 import 한 것이다. 시작하기 전에 말한 점-규칙을 잘 생각해보면 이해할 수 있을 것이다.

relative imports의 장단점

relative imports는 당연히 간결한 구문이 장점이다. 위에서 봤던 복잡한 코드는 다음과 같이 간단해질 수 있다.

from ..subpackage4.module5 import function6

하지만 absolute imports에 비해서 가독성이 떨어지고, 다른 사람과 협업시 디렉터리 구조가 바뀔 가능성이 높으므로 오류가 발생할 가능성도 높아진다.

4. 패키지 모듈을 main 모듈에서 어떻게 import 할까?

오류 확인!

일단 다음과 같은 구조의 프로젝트가 있다.

├── calculator
│   ├── __init__.py
│   ├── add_and_multiply.py
│   └── multiplication.py
└── main.py

그리고 main.py에는 다음과 같은 코드가 있다. 나머지 파일들이 어떤 코드를 가졌는지 생각하지 말고 "산은 산이요 물은 물이로다"하는 마음으로 보면된다.

# relative path
from .calculator.add_and_multiply import add_and_multiply

if __name__ == '__main__':
    print(add_and_multiply(1,2))

실행해보면 다음과 같은 오류가 발생한다.

ImportError: attempted relative import with no known parent package

해석해 보면 부모 패키지도 없는데 relative import를 시도했다는 뜻이다. 그럼 main 모듈에서 어떻게 import를 해야할까??

그래서 어떻게 하죠??

파이썬 문서를 보면 메인 모듈에서 어떻게 import 해야하는지 적혀있다.

Note that relative imports are based on the name of the current module. Since the name of the main module is always "__main__", modules intended for use as the main module of a Python application must always use absolute imports.

흠.. 해석을 해보도록 하겠습니다 Ⓐℬ𝘾!

relative imports는 현재 모듈의 이름을 기반으로 한다. 메인 모듈의 이름은 항상 "__main__"이기 때문에, 파이썬 어플리케이션의 메인 모듈로 사용하려는 모듈은 항상 absolute imports를 사용해야한다.

뭔 말..?

태현님이 "메인 모듈을 보통 프로젝트의 루트에 있기 때문에 찾을 부모 디렉터리가 없어서 relative imports를 하면 에러가 난다"고 말씀해주셔서 이해가 되었다 😭 Je vous remercie 🥳!!

5. __init__.py의 역할

사전스터디에서 장고로 앱을 생성할 때 항상 자동으로 추가되어있던 __init__.py... 그 때는 "이게 뭐고? 암것도 없는데 와만드는데" 하고 그냥 대수롭지 않게 넘어갔다. 이제 알아볼 시간이 온 것 같다 ㅋㅋ. 그것의 역할을 몇가지 정리해본다.

  • 이 파일이 존재하는 디렉터리가 패키지임을 명시한다.
  • 공통으로 적용가능한 기능이나 모듈을 포함하여 한 번의 선언으로 해당 경로에 위치한 모든 파일들이 그것을 이용하게 한다.
  • 필요에 따라 import 경로의 길이를 짧게할 수 있다.
  • __all__ 변수를 통해 import 할 수 있는 변수/함수/클래스를 제한 할 수 있다.

소감

파이썬 문법 공부할 때 언급자체가 없거나 소홀히하기 쉬운 내용들이라 블로그에 정리한 내용들이 대부분 모르는 내용이었다. 정리되는게 하나씩 생기니 좋다. 위코드에 오길 잘한 것 같다 ㅎㅎ 😂!

피드백을 받고 수정합니다!

아마도 소헌님께서 피드백을 주셨는데 4, 5, 6번 세개나 빠졌다고 하셨다 ㅋㅋㅋ. 나의 정신머리 수준.. 빨리 끝내고 싶은 마음의 소리를 따라갔나보다 🙄🙄.
? 세 개나 빠졌다구요? ㅜㅜ 다시 열심히 해보겠습니다 🙇🏻‍♂️

1. Calculator 패키지 만들기

로컬 환경에 파이썬을 설치하고 직접 패키지를 만들어보는 과정이다.

패키지 구조

main.py

multiplication.py

add_and_multiply.py

2. main.py에서 상대경로로 add_and_mutiply 를 임포트 했을 때 발생하는 에러를 확인하고 어떻게 임포트 해야하는지 정리하기

에러 확인

일단 에러를 확인해야하니 확인해본다.

Traceback (most recent call last):
  File "/Users/choonghee/workspace/wecode/projets-pour-blogging/calculator-package/main.py", line 5, in <module>
    from .calculator.add_and_multiply import add_and_multiply
ImportError: attempted relative import with no known parent package

부모 패키지가 정의되어있지 않은 상태에서 상대 경로로 import를 시도해서 발생한 ImportError다. 이럴 땐 어떻게 해야할까? 사실 위에 적어놨다 (2트).

에러 수정

위에서 적어놓은대로 absolute path를 사용하여 수정해 보았다 (사실 주석 없애기 ㅋㅋ).

결과는

5

성공!

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

진짜 1차원적으로 repl.it에 적힌대로 생각해보고 결과를 출력하고 "ㅇㅋㅇㅋ"이러고 넘어갔다. 포스팅할 때 정리를 안하다니 ㅜㅜ

절대 경로로 출력해보기

from calculator.multiplication import multiply
def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)
    
>>> python main.py
5

상대 경로로 출력해보기

from .multiplication import multiply
def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)
    
>>> python main.py
5

차이점 생각해보기

add_and_multiply.pycalculator 패키지에 있는 모듈이기 때문에 원하는 경로 스타일을 적어넣어 활용하면 된다. 절대 경로이던지 상대 경로이던지 상관없다!

메인 모듈에서 import하는 방법은 위에 적어놨다 🤧 (3트).

결론

포스팅은 피드백 받아서 재수정하는 일이 없도록 해야겠다. 심신이 미약해짐을 느낀다 ㅋㅋ 😪.

profile
뭐든지 열심히하는 타입 😎

0개의 댓글