TIL-025 | Python_Modules and Packages

Lee, Chankyu·2021년 10월 9일
0
post-thumbnail
post-custom-banner

1. module

  • 파이썬에서는 모듈을 import할 때 아래의 세 곳의 장소를 순서대로 search하여 찾는다.

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

sys.modules

  • 이미 import된 모듈이나 package들을 dictionary 형태로 저장한 다음 사용자가 확인할 수 있는 모듈이다.
  • 한번 import된 모듈과 package들은 파이썬이 또 다시 찾지 않아도 된다.
import sys

print(sys.modules) # 불러왔던 모든 모듈들을 확인할 수 있다. 

built-in modules

  • 파이썬이 제공하는 공식 라이브러리들이다.
  • 이미 파이썬에 포함되어 있으므로 쉽게 찾을 수 있다.

sys.path

  • 파이썬 라이브러리들이 설치되어 있는 directory들을 string 요소를 가지고 있는 list의 형태로 보여준다.
  • list 형태기 때문에 append() 함수로 경로를 추가해 줄 수도 있다.
import sys

print(sys.path) # 리스트 형태로 지정되어 있는 경로를 확인할 수 있다. 

위의 내용들을 바탕으로 sys.modules와 sys.path의 차이점을 살펴보면,

  • sys.modules은 파이썬에서 모듈을 import할 때 가장 먼저 확인하는 곳이고 dictionary의 형태이다. 이미 import 된 모듈과 패키지들이 있으며, 새로 import하는 모듈은 이 곳에서 찾을 수 없다.
  • sys.path는 모듈을 import할 때 가장 마지막에 확인하는 곳이고 list의 형태이다. list의 string요소들은 모듈과 패키지들의 경로를 제공한다.

위에서 sys.modules와 sys.path를 호출할 때 sys 모듈을 import 해야 출력이 가능했었다. sys 또한 모듈이기 때문에 import 해야하기 때문이다. 그렇다면 sys 모듈의 위치는 어떻게 찾을 수 있을까 ?

  • sys 모듈은 이미 파이썬에 builit-in 되어 있는 모듈이기 때문에 built-in modules에서 찾을 수 있다.
import sys

for name in sys.builtin_module_names:
    print(name)

2. Absolute path & Relative path

📝 파이썬 정상적으로 설치가 되었다면 Built-in 모듈의 import는 문제가 없다. 그리고 pip 로 설치한 외부 모듈은 자동으로 site-packages 라는 디렉토리에 설치가 되고, 이 site-packagessys.path에 이미 포함되어 있기 때문에 import할 때 문제가 없다.
그러나 사용자가 직접 개발한 local package를 import 할때는 해당 package의 위치에 맞게 import 경로를 적절하게 지정해줘야 한다.
Local package를 import 하는 경로에는 absolute pathrelative path 가 있다.


Absolute path (절대경로)

  • 모듈 및 패키지의 시작부터 끝까지의 명확한 경로를 모두 표기한 것을 뜻한다.
  • 경로가 길어지는 단점이 있으나, 모듈 및 패키지의 위치를 변경하지 않는 한 수정할 필요가 없기 때문에 헷갈리지 않는다.
  • 경로의 시작점이 최상위 디렉토리이며 동일한 프로젝트 내에서는 어디에서 사용하던 동일하게 적용된다. current directory(현재의 프로젝트 디렉토리, 절대경로의 시작점)는 default로 sys.path 에 등록되어 있기 때문이다.
# 경로의 시작점이 모두 최상단 디렉토리이다. 
from package1 import module1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2

Relative path (상대경로)

  • 최상위 디렉토리가 아닌 현재의 위치를 기준으로 경로를 정의한다.
  • absolute path에 비해 경로가 간결해지지만 파일의 위치가 변동되면 경로를 재설정 해주어야 한다. 따라서 프로젝트의 규모가 커질수록 혼동되기 쉽다.
# package2/module3.py

from . import class1
from .subpackage1.module5 import function2
  • dot(.)은 현재의 위치를 의미한다. 현재의 위치(package2/module3.py)에서 원하는 모듈의 경로를 선언한다.

Absolute path와 relative path의 차이점을 정리해보면,

  • Absolute path는 경로 지정시 최상위 디렉토리에서부터 순차적으로 지정해주는 것이다. 따라서 경로가 길어진다는 단점이 있다.
    대신 현재 파일의 위치가 변경되어도 경로를 새로 지정해주지 않아도 된다는 장점이 있다.
  • Relative path는 현재 위치의 디렉토리를 기준으로 경로를 지정해주는 것이다. 현재 파일의 위치가 변경되면 경로를 수정해줘야 하므로, 경로 지정시 혼동이 올 수 있다.

👉 각각의 장단점이 있으나, Absolute path의 사용이 권장되어 진다.


3. Practice_calculator 패키지

👉 아래와 같은 디렉토리 구조를 가지고 있는 패키지를 만들어보았다.

# main.py

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 에서는 패키지의 모듈을 어떻게 임포트 해야하는가?

# 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
  • 상대경로로 지정하면 위와 같은 에러를 확인할 수 있다. 에러의 내용은 알 수 없는 상위 패키지를 사용하여 임포트를 시도했다는 내용이다.
#absolute path
from calculator.add_and_multiply import add_and_multiply 

if __name__ == '__main__':
    print(add_and_multiply(1,2)) # 5
  • 이와 같이 절대경로로 지정하여 실행하면 에러 없이 잘 실행됨을 확인 할 수 있다.
  • 파이썬 공식문서를 통해서도 메인 모듈은 절대 경로를 사용해야함을 확인할 수 있었다.

add_and_multiply.py에서 multiply함수를 절대경로와 상대경로도 각각 임포트 하였을때 main 모듈과 차이점은?

#상대경로
from .multiplication import multiply

def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)

print(add_and_multiply(1,2))
#ImportError: attempted relative import with no known parent package
  • 상대경로로 임포트 하였을때 위의 예시와 동일한 에러가 발생하였다.
#절대경로
from calculator.multiplication import multiply

def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)

print(add_and_multiply(1,2))
#ModuleNotFoundError: No module named 'calculator'
  • 당연히 실행 될 것이라 예상하였는데 'calculator' 라는 모듈이 없다는 에러가 발생하였다. 위에서 학습하였듯이 절대경로는 current directory가 시작점이고 현재의 폴더가 sys.path에 포함된다. 나의 경우 calculator 폴더가 current directory 이므로 이를 제외하고 작성해보았다.
#절대경로
from multiplication import multiply

def add_and_multiply(a,b):
    return multiply(a,b) + (a+b)

print(add_and_multiply(1,2)) # 5
  • 정상적으로 실행 됨을 확인하였다.

add_and_multiply.py 에서 상대경로와 절대경로로 임포트 하기 직전에는 둘 다 작동이 잘 될것이라 예상했었다. 절대경로는 바로 위의 코드로 해결했다지만 상대경로는 왜 실행이 되지 않은 것일까? 이를 알아내기 위해 __name__ 변수의 역할과 이를 활용해야 했다.

__name__ 변수의 역할은?

  • __name__은 import로 모듈을 가져왔을 때 모듈의 이름이 들어가는 변수이다. 위의 calculator 패키지의 main.py 에 작성되어 있는 코드를 보면 __name__ 변수에 __main__ 이 있으면 출력을 실행하라는 조건문이 있다. 절대경로 지정을 통해 정상적으로 실행이 됐었고 이는 main.py 가 메인모듈(__main__)이었다는 뜻이다.
    즉 패키지 각각의 py 파일에 __name__ 변수를 활용하면 임포트하는 모듈들의 __name__을 알 수 있다.

📝 내 의문을 해소하고자 아래와 같이 calculator 폴더에 test.py 라는 파일을 추가하고 약간의 코드 변경을 하고 실행해보았다.

#multiplication.py
def multiply(a,b):
    print(__name__)
    return(a*b)
#test.py
from add_and_multiply import add_and_multiply

def test():
    print(__name__)

print(__name__)

#출력값
"""
add_and_multiply
multiplication 👈
5
add_and_multiply.py __name__: add_and_multiply 👈
__main__ 👈
"""
#add_and_multiply.py
from multiplication import multiply

def add_and_multiply(a,b):
    print(__name__)
    return multiply(a,b) + (a+b)

print(add_and_multiply(1,2))
print('add_and_multiply.py __name__:', __name__)

#출력값
"""
__main__ 👈
multiplication 👈
5
add_and_multiply.py __name__: __main__ 👈
"""
  • 결과 값들이 다소 지저분하긴 하지만 여기서 핵심은 해당 파일에서 파일을 실행하였을때 그 파일의 __name____main__으로 설정된다는 것이다. 그러나 모듈을 임포트해서 사용하면 임포트 된 모듈의 name은 원래의 모듈 이름으로 설정됨을 확인하였다. (위의 예시에서 multiplication.py 모듈은 test, add_and_multiply 모듈에서 __name__이 모두 multiplication 으로 출력 되었다.)
    따라서 if name == 'main': 조건문을 사용하는 블록은 다른 파일에서 임포트하여 사용할때 실행되지 않는다.
    결론적으로 상대경로 임포트를 포함한 파이썬 모듈은 단독 실행이 불가능한 것으로 판단된다.

__init__.py 파일의 역할은?

  • 파일에 아무 코드가 작성되어 있지 않더라도 해당 디렉토리가 패키지임을 알려주는 역할을 한다.
  • package가 import 될때 __init__.py 파일이 자동으로 실행한다.
  • __all__ 변수를 사용하여 import 할 수 있는 변수/함수/클래스를 제한 할 수 있다.

📝 reference
https://docs.python.org/3/tutorial/modules.html#intra-package-references

profile
Backend Developer - "Growth itself contains the germ of happiness"
post-custom-banner

0개의 댓글