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.
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
를 찍어보면 될 것 같다.
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
를 잘 확인해서 추가해야한다.
sys 도 import 해야하는 모듈인데 파이썬은 sys 모듈의 위치를 어떻게 찾을 수 있을까?
위에서 살펴본 것처럼 파일을 만들지 않았어도 sys.modules
에 기본적으로 포함되어있기 때문에 sys
모듈을 찾을 수 있다.
Relative path는 선언해야 하는 경로의 길이를 줄여준다는 장점은 있지만 헷갈리기 쉽고 파일 위치가 변경되면 경로 위치도 변경되어야 하는 단점이 있다. 그러므로 웬만한 경우 absolute path를 사용하는게 권장된다.
__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__
이기 때문에 파이썬 어플리케이션에서 메인 모듈로 사용되는 모듈에서는 항상 절대경로로 임포트가 이루어져야한다.
그렇기때문에 다음과 같이 상대경로로 참조하던 것을 지우고 절대경로로 참조하면
값이 제대로 출력된다.
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 multiply
를 from multiplication import multiply
로 바꿔주어야한다.
이제 상대경로를 살펴보자.
같은 디렉토리에 있는 multiplication 모듈의 mutiply
function을 import했지만 오류가 난다. 혼자 경로를 이리저리 바꿔보다가 도저히 뭐가 틀린지 모르겠어서 검색했더니 stack overflow에서 이유를 찾을 수 있었다.
그러니까 add_and_mutiply.py
가 main module이기 때문에 상대경로가 아닌 절대경로로 import해야 된다는 것이었다. main module이 프로젝트에서 하나만 있는줄 알았는데 아니었나보다.
if name == "main"을 사용하면 C:\doit>python mod1.py처럼 직접 이 파일을 실행했을 때는 name == "main"이 참이 되어 if문 다음 문장이 수행된다. 반대로 대화형 인터프리터나 다른 파일에서 이 모듈을 불러서 사용할 때는 name == "main"이 거짓이 되어 if문 다음 문장이 수행되지 않는다.
그러니까 직접 실행되는 파일이 메인 모듈이 되는 것이다. 그래서 상대경로를 사용했을 때 직접 add_and_multiply.py
를 실행하면 오류가 나고 main.py
에서 실행하면 오류없이 잘 돌아가는 것이었다.
__init__.py
의 역할디렉토리 안에 __init__.py
가 있다면 해당 디렉터리는 패키지로 인식된다.(python3.3 버전부터는 __init__.py
파일이 없어도 패키지로 인식하지만 하위 버전 호환을 위해 __init__.py
파일을 생성하는 것이 안전한 방법이라고 한다.)
Package 안에 __init__.py
파일이 있으면 package가 import 될때 __init__.py
파일의 코드들이 자동으로 실행된다. 하지만 기본적으로 비워둘 수 있다.
패키지로 특정 모듈의 함수를 사용할때는 그냥 모듈만 import할때보다 써야하는 경로의 길이가 길어지게 된다.
__init__.py
파일에 먼저 한번 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가 모듈인 경우이다.