[Python] Modules & Packages

rang-dev·2020년 5월 30일
0

1. sys.modules와 sys.path의 차이점

sys모듈은 파이썬에 내장되어있는 모듈로 파이썬 인터프리터와 관련된 정보와 기능을 제공한다.(공식문서)

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.modules: sys.modules는 이미 import된 모듈과 패키지를 딕셔너리의 형태로 저장하고 있다.

This is a dictionary that maps module names to modules which have already been loaded. This can be manipulated to force reloading of modules and other tricks.

이렇게 저장을 해둠으로써 한번 import된 모듈과 패키지들을 파이썬이 다시 찾지 않아도 된다.

👉 WHY?
파이썬은 모듈이나 패키지를 찾을때 sys.modules -> built-in modules -> sys.path순으로 찾기 때문에 sys.modules에 저장되어있으면 다른 곳을 일일히 찾을 필요가 없다.

새로운 파이썬 파일을 만들어서 sys.modules을 호출하면 다음과 같은 결과가 나타난다.(굉장히 길다)

{'builtins': <module 'builtins' (built-in)>, 'sys': <module 'sys' (built-in)>, '_frozen_importlib': <module 'importlib._bootstrap' (frozen)>, '_imp': <module '_imp' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_thread': <module '_thread' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_frozen_importlib_external': <module 'importlib._bootstrap_external' (frozen)>, '_io': <module 'io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'posix': <module 'posix' (built-in)>, 'zipimport': <module 'zipimport' (built-in)>, 'encodings': <module 'encodings' from '/usr/lib/python3.6/encodings/__init__.py'>, 'codecs': <module 'codecs' from '/usr/lib/python3.6/codecs.py'>, '_codecs': <module '_codecs' (built-in)>, 'encodings.aliases': <module 'encodings.aliases' from '/usr/lib/python3.6/encodings/aliases.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from '/usr/lib/python3.6/encodings/utf_8.py'>, '_signal': <module '_signal' (built-in)>, '__main__': <module '__main__' from '/home/hyunlang/backjoon/sys2.py'>, 'encodings.latin_1': <module 'encodings.latin_1' from '/usr/lib/python3.6/encodings/latin_1.py'>, 'io': <module 'io' from '/usr/lib/python3.6/io.py'>, 'abc': <module 'abc' from '/usr/lib/python3.6/abc.py'>, '_weakrefset': <module '_weakrefset' from '/usr/lib/python3.6/_weakrefset.py'>, 'site': <module 'site' from '/usr/lib/python3.6/site.py'>, 'os': <module 'os' from '/usr/lib/python3.6/os.py'>, 'errno': <module 'errno' (built-in)>, 'stat': <module 'stat' from '/usr/lib/python3.6/stat.py'>, '_stat': <module '_stat' (built-in)>, 'posixpath': <module 'posixpath' from '/usr/lib/python3.6/posixpath.py'>, 'genericpath': <module 'genericpath' from '/usr/lib/python3.6/genericpath.py'>, 'os.path': <module 'posixpath' from '/usr/lib/python3.6/posixpath.py'>, '_collections_abc': <module '_collections_abc' from '/usr/lib/python3.6/_collections_abc.py'>, '_sitebuiltins': <module '_sitebuiltins' from '/usr/lib/python3.6/_sitebuiltins.py'>, 'sysconfig': <module 'sysconfig' from '/usr/lib/python3.6/sysconfig.py'>, '_sysconfigdata_m_linux_x86_64-linux-gnu': <module '_sysconfigdata_m_linux_x86_64-linux-gnu' from '/usr/lib/python3.6/_sysconfigdata_m_linux_x86_64-linux-gnu.py'>, '_bootlocale': <module '_bootlocale' from '/usr/lib/python3.6/_bootlocale.py'>, '_locale': <module '_locale' (built-in)>, 'types': <module 'types' from '/usr/lib/python3.6/types.py'>, 'functools': <module 'functools' from '/usr/lib/python3.6/functools.py'>, '_functools': <module '_functools' (built-in)>, 'collections': <module 'collections' from '/usr/lib/python3.6/collections/__init__.py'>, 'operator': <module 'operator' from '/usr/lib/python3.6/operator.py'>, '_operator': <module '_operator' (built-in)>, 'keyword': <module 'keyword' from '/usr/lib/python3.6/keyword.py'>, 'heapq': <module 'heapq' from '/usr/lib/python3.6/heapq.py'>, '_heapq': <module '_heapq' (built-in)>, 'itertools': <module 'itertools' (built-in)>, 'reprlib': <module 'reprlib' from '/usr/lib/python3.6/reprlib.py'>, '_collections': <module '_collections' (built-in)>, 'weakref': <module 'weakref' from '/usr/lib/python3.6/weakref.py'>, 'collections.abc': <module 'collections.abc' from '/usr/lib/python3.6/collections/abc.py'>, 'importlib': <module 'importlib' from '/usr/lib/python3.6/importlib/__init__.py'>, 'importlib._bootstrap': <module 'importlib._bootstrap' (frozen)>, 'importlib._bootstrap_external': <module 'importlib._bootstrap_external' (frozen)>, 'warnings': <module 'warnings' from '/usr/lib/python3.6/warnings.py'>, 'importlib.util': <module 'importlib.util' from '/usr/lib/python3.6/importlib/util.py'>, 'importlib.abc': <module 'importlib.abc' from '/usr/lib/python3.6/importlib/abc.py'>, 'importlib.machinery': <module 'importlib.machinery' from '/usr/lib/python3.6/importlib/machinery.py'>, 'contextlib': <module 'contextlib' from '/usr/lib/python3.6/contextlib.py'>, 'zope': <module 'zope' from '/usr/lib/python3/dist-packages/zope/__init__.py'>, 'sitecustomize': <module 'sitecustomize' from '/usr/lib/python3.6/sitecustomize.py'>, 'apport_python_hook': <module 'apport_python_hook' from '/usr/lib/python3/dist-packages/apport_python_hook.py'>}

지금 import한 sys모듈도 있는 것을 확인할 수 있고 built-in 모듈들과 다른 여러 모듈들이 보인다. 추측해볼 수 있는 것은 여기 들어있는 모듈들은 내가 파일(모듈이나 패키지)을 만들지 않더라도 그냥 import가 가능하다는 것이다.

sys.modules에 포함되어있는 warnings모듈과 포함되어있지 않은 modules_test를 import해보겠다.

import warnings
import module_test
Traceback (most recent call last):
  File "/home/hyunlang/test/sys2.py", line 3, in <module>
    import module_test
ModuleNotFoundError: No module named 'module_test'

module_test라는 모듈을 찾을 수 없다고 나온다. 두 모듈을 모두 직접 만든 적은 없지만 sys.moudles안에 있는 모듈은 그냥 import해서 사용할 수 있다.

또한 모듈을 직접 만들고 import를 하면 그 모듈은 sys.modules에 추가된다. mod1.py를 만들고 import를 하니 다음과 같이 맨 마지막에 mod1이 추가되었다.

{..., 'mod1': <module 'mod1' from '/home/hyunlang/test/mod1.py'>}

내가 사용가능한 모듈이 뭐가 있는지 확인하고자 할 때 sys.modules를 찍어보면 될 것 같다.

  • sys.path: sys.path는 모듈을 찾을때 참조하는 경로를 리스트의 형태로 나타낸다. 이 위치에 있는 파이썬 모듈은 경로에 상관없이 불러올 수 있다.

A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.

새 파이썬 파일에 sys.path를 출력해보았다.

['/home/hyunlang/py_test', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/hyunlang/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']

맨 앞에 있는 주소는 현재 프로젝트이고 나머지 path는 PYTHONPATH 환경변수에 저장되어있거나 또는 설치시 기본 설정 되어 있던 것일 것이다.

만약 sys.modules안에서 module을 찾지 못하고 built-in module에도 없을때 이 path들을 하나하나 확인하면서 해당 모듈이나 패키지를 찾는다.

아까 module_test을 만들어서 처음 import했을때 sys.modules에도 없고, built-in도 아니므로 현재 위치에서 module_test.py를 찾아서 모듈을 import해왔을 것이다.

만약 해당 파일을 sys.path이외의 곳으로 이동하면 import에 실패할까? 한번 살펴보자.

Traceback (most recent call last):
  File "/home/hyunlang/py_test/test.py", line 1, in <module>
    import module_test
ModuleNotFoundError: No module named 'module_test'

모듈을 불러오는것에 실패했다. 따라서 모듈을 직접 추가할때는 현재 프로젝트 위치에서 추가하거나 sys.path를 잘 확인해서 추가해야한다.

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

sys 도 import 해야하는 모듈인데 파이썬은 sys 모듈의 위치를 어떻게 찾을 수 있을까?

위에서 살펴본 것처럼 파일을 만들지 않았어도 sys.modules에 기본적으로 포함되어있기 때문에 sys모듈을 찾을 수 있다.

3. Absolute path와 relative path의 차이점

  • Absolute path: import를 하는 파일이나 경로에 상관없이 항상 경로가 동일하다. 하지만 항상 프로젝트의 최상위 디렉토리부터 시작하기 때문에 경로가 길어질 수 있는 단점이 있다.
  • Relative path: 프로젝트의 최상단 디렉토리를 기준으로 경로를 잡는게 아니라 import 하는 위치를 기준으로 경로를 정의한다.(relative path는 주로 local package 안에서 다른 local package를 import 할때 사용된다고 한다.)

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

4. calculator 패키지 만들기 - main.py에서 발생하는 에러

  • __init__.py: __init__.py 파일에 아무코드도 없더라도 __init__(not init) 파일이 포함되어 있으면 해당 디렉토리가 패키지임을 알려주는 역할을 한다.
  • 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
  # 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))

main.py를 그대로 실행해보면 다음과 같은 오류가 발생했다.

Traceback (most recent call last):
  File "/home/hyunlang/replit/main.py", line 6, in <module>
    from .calculator.add_and_multiply import add_and_multiply
ModuleNotFoundError: No module named '__main__.calculator'; '__main__' is not a package

혹시 상대경로를 잘못적어서 오류가 나는건 아닐까?

A single dot means that the module or package referenced is in the same directory as the current location. Two dots mean that it is in the parent directory of the current location—that is, the directory above. Three dots mean that it is in the grandparent directory, and so on.

signle dot은 현재 동일 디렉토리내에서 참조하는 것을 나타내니까 .calculator.add_and_multiply는 맞는 경로이다. 동일 디렉토리 안에 있는 calculator 폴더에 있는 add_and_multiply를 나타낸다....(상대경로 너무 싫다 완전 헷갈린다ㅠㅠㅠㅠㅠ)

파이썬 공식 문서에는 main module 에서는 패키지의 모듈을 어떻게 임포트해야하는지에 대해 이렇게 설명한다.

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.

현재 모듈의 이름을 기반으로 상대적으로 임포트가 이루어지는데 main module의 이름은 항상 __main__이기 때문에 파이썬 어플리케이션에서 메인 모듈로 사용되는 모듈에서는 항상 절대경로로 임포트가 이루어져야한다.

그렇기때문에 다음과 같이 상대경로로 참조하던 것을 지우고 절대경로로 참조하면

값이 제대로 출력된다.

5. 절대경로와 상대경로

add_and_multiply.py에서 multiply함수를 절대경로와 상대경로 각각 임포트 해보기

starter code에 있던 절대경로인 from calculator.multiplication import multiply로 import를 했더니 다음과 같은 오류가 발생했다:

Traceback (most recent call last):
  File "/home/hyunlang/replit/calculator/add_and_multiply.py", line 6, in <module>
    from calculator.multiplication import multiply
ModuleNotFoundError: No module named 'calculator'

절대경로는 최상위 프로젝트 폴더를 제외하고 그 이후부터 쓰는거니까 맞지 않나? 라고 생각했는데 absolute path에 대해 잘못 이해했던 게 있었다. absolute path는 current directory로부터 경로를 시작하게되는데 이 current directory는 sys.path의 가장 앞에 나오는 path를 의미한다.

add_and_multiply.py에서 sys.path를 찍어보니

['/home/hyunlang/replit/calculator', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/hyunlang/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']

이렇게 나왔다. current dictionary는 프로젝트 폴더인 replit이 아니라 caculator였던 것이다. 그러니 from calculator.multiplication import multiplyfrom multiplication import multiply로 바꿔주어야한다.

이제 상대경로를 살펴보자.

같은 디렉토리에 있는 multiplication 모듈의 mutiply function을 import했지만 오류가 난다. 혼자 경로를 이리저리 바꿔보다가 도저히 뭐가 틀린지 모르겠어서 검색했더니 stack overflow에서 이유를 찾을 수 있었다.

그러니까 add_and_mutiply.py가 main module이기 때문에 상대경로가 아닌 절대경로로 import해야 된다는 것이었다. main module이 프로젝트에서 하나만 있는줄 알았는데 아니었나보다.

  • main module

    if name == "main"을 사용하면 C:\doit>python mod1.py처럼 직접 이 파일을 실행했을 때는 name == "main"이 참이 되어 if문 다음 문장이 수행된다. 반대로 대화형 인터프리터나 다른 파일에서 이 모듈을 불러서 사용할 때는 name == "main"이 거짓이 되어 if문 다음 문장이 수행되지 않는다.

그러니까 직접 실행되는 파일이 메인 모듈이 되는 것이다. 그래서 상대경로를 사용했을 때 직접 add_and_multiply.py를 실행하면 오류가 나고 main.py에서 실행하면 오류없이 잘 돌아가는 것이었다.

6. __init__.py 의 역할

디렉토리 안에 __init__.py가 있다면 해당 디렉터리는 패키지로 인식된다.(python3.3 버전부터는 __init__.py 파일이 없어도 패키지로 인식하지만 하위 버전 호환을 위해 __init__.py 파일을 생성하는 것이 안전한 방법이라고 한다.)

Package 안에 __init__.py 파일이 있으면 package가 import 될때 __init__.py 파일의 코드들이 자동으로 실행된다. 하지만 기본적으로 비워둘 수 있다.

1. Import 할때 경로의 총 길이 줄여주기

패키지로 특정 모듈의 함수를 사용할때는 그냥 모듈만 import할때보다 써야하는 경로의 길이가 길어지게 된다.

__init__.py 파일에 먼저 한번 import 함으로써 함수의 경로를 줄일 수 있다.

2. Package에서 import 할 수 있는 변수/함수/클래스 제한하기

특정 디렉터리의 모듈을 *를 사용하여 import할 때에는 import가 가능한 모듈을 __all__로 제한할 수 있다.

__all__ 변수는 string 값의 요소를 가지고 있는 list이며 import가 되어도 되는 요소들을 string으로 list에 선언하면 된다.

  • __all__을 할당하지 않았을때

  • __all__=['module1']

  • __all__=['module1', 'module2']

    all과 상관없이 무조건 import되는 경우는 from a.b.c import * 에서 from의 마지막 항목인 c가 모듈인 경우이다.

3. 그 외 package가 import될때 꼭 먼저 실행되어야 하는 코드들이 있을때

profile
지금 있는 곳에서, 내가 가진 것으로, 할 수 있는 일을 하기 🐢

0개의 댓글