Python 관련 글들을 보다 보면 스크립트를 실행시킬 때 어떤 때는 python3 script.py처럼 실행시키고, 어떤 때는 python3 -m script처럼 -m 옵션을 사용해서 실행시킬 때가 있습니다. 차이는 무엇일까요? 전자는 script.py를 실행시키는 것이고, 후자는 script라는 이름의 모듈을 sys.path에서 찾아서 실행시키는 것입니다. sys.path에 대한 설명은 알쏭달쏭 Python import - sys.path을 참고하세요.
오늘도 간략한 예시를 들어서 알아보겠습니다.
1. 간단한 함수부터
아래와 같은 Python 코드를 만들고 있다고 가정해보겠습니다. 주어진 문자열 주위를 아름답고 화려한 *으로 감싸주는 코드입니다.
import sys
def print_with_border(s):
print('*' * (len(s) + 4))
print('* ' + s + ' *')
print('*' * (len(s) + 4))
print_with_border(sys.argv[1])
➜ python3 add_border.py 'Beautiful world!'
********************
* Beautiful world! *
********************
잘 되는군요. 불현듯 print_with_border를 여기에서만 쓰기는 아깝다는 생각이 듭니다. 그래서 여러곳에서 사용하기로 합니다.
2. 문제의 시작
add_border.print_with_border 함수를 사용하는 main.py 를 아래와 같이 만듭니다.
import add_border
add_border.print_with_border('Wonderful day!')
➜ python3 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
import add_border
File "/Users/ceongjeein/Workspace/pydev/add_border.py", line 8, in <module>
print_with_border(sys.argv[1])
IndexError: list index out of range
에러가 발생합니다. 그것도 심지어 첫번째 줄인 import add_border에서 에러가 발생합니다. 조금 더 들여다보니 add_border.py에서 에러가 발생했군요. 원인은 import 문이 작동하는 방식에 있습니다. import 문은 느낌상 코드를 가져온다는 느낌적인 느낌이 있는데, 실제로는 이 과정에서 해당 코드 전체를 실행합니다. 즉, add_border.py의 8번째 줄인 print_with_border(sys.argv[1])를 실행하려는데, sys.argv[1]이 없기 때문에 에러가 발생한 거죠.
3. 모듈화의 시작
언젠가 소프트웨어의 모듈화는 좋은 것이라는 이야기를 들었던 것이 불현듯 떠오릅니다. 그래서 코드를 아래와 같이 나눕니다.
* add_border.py
import sys
def print_with_border(s):
print('*' * (len(s) + 4))
print('* ' + s + ' *')
print('*' * (len(s) + 4))
* add_border_exec.py
import add_border
import sys
add_border.print_with_border(sys.argv[1])
* main.py
import add_border
add_border.print_with_border('Wonderful day!')
그리고 아래와 같이 실행해보니 기대대로 아주 잘 작동합니다.
➜ python3 add_border_exec.py 'Beautiful world!'
********************
* Beautiful world! *
********************
➜ python3 main.py
******************
* Wonderful day! *
******************
4. 파편화?
하지만 어딘가에서 같은 기능은 같이 있어야 한다는 이야기를 들은 것이 떠오릅니다. add_border_exec.py의 내용을 분리하는 것도 나쁘지 않지만, 앞으로 유지보수를 위해서 print_with_border 함수와 같이 있는 것이 좋을 것 같은 생각이 듭니다.
이때 __name__이 등장합니다. __name__에 대한 자세한 설명은 알쏭달쏭 Python import - sys.path에 있습니다. 요약하자면 현재 파일이 직접 실행됐다면 __name__의 값은 __main__이 되고, 다른 파일에 의해서 import 됐다면 모듈의 이름이 됩니다. 예를 들어 add_border.py의 경우,
- python3 add_border.py라고 실행됐다면 __name__ == '__main__'이 되고,
- 어딘가에서 import add_border.py를 만나서 실행됐다면, __name__ == 'add_border'가 됩니다.
바로 이 __name__을 이용해서 현재 파일이 직접 실행된 것인지, import 된 것인지를 판단해서 동작을 나눌 수 있습니다.
앞에 코드 중 add_border.py를 아래와 같이 수정합니다.
import sys
def print_with_border(s):
print('*' * (len(s) + 4))
print('* ' + s + ' *')
print('*' * (len(s) + 4))
if __name__ == '__main__':
print_with_border(sys.argv[1])
➜ python3 add_border.py 'Beautiful world!'
********************
* Beautiful world! *
********************
➜ python3 main.py
******************
* Wonderful day! *
******************
이제 파일 하나가 스크립트처럼도 작동하고 모듈처럼도 작동합니다.
5. 드디어 -m 옵션
-m 옵션이 하는 일은 처음에 설명한 대로, 실행할 Python 스크립트를 찾을 때 sys.path에서 찾는다는 점이 다릅니다. 나머지 동작은 앞에서 설명한 것과 같습니다. sys.path에서 찾는다는 것은 import 때의 규칙을 그대로 따른다는 말이고요. 알쏭달쏭 Python import - sys.path에서 설명한대로 스크립트가 위치한 디렉터리가 sys.path에 추가되기 때문에 아래와 같이 실행할 수도 있습니다.
➜ python3 -m add_border 'Beautiful world!'
********************
* Beautiful world! *
********************
자세히 보시면 -m 옵션을 주고 add_border.py가 아닌 그냥 add_border를 사용했습니다. -m이 원하는 값은 파일명이 아니고 모듈 이름이기 때문입니다.
갑자기 의문이 듭니다. python3 add_border.py 'Beautiful world!'라고 그냥 하며 되는데 왜 굳이 헷갈리게 이렇게 할까요.
-m 옵션은 모듈이 배포되었을 때 힘을 발휘합니다. 예를 들어, add_border가 인기가 엄청 많아져서 배포를 하기로 했고 많은 사람들이 pip를 통해서 설치했다고 가정해보겠습니다. 그때마다 사용자들이 add_border.py의 위치를 찾아서 python3 /usr/local/somewhere/anywhere/add_border.py 'Beautiful world!' 라고 실행할 수는 없습니다. 사용하는 환경마다 실제로 모듈이 설치되는 위치가 다르니까요. 대신 python3 -m add_border 'Beautiful world!'를 사용하면 Python이 add_border 모듈을 찾는 일을 해주기 때문에 문제를 해결할 수 있습니다.
실제로 많은 Python 모듈들이 __name__ == '__main__' 을 사용해서 모듈에 실행 가능한 기능을 추가해서 배포하고 있습니다. venv, site 등이 그렇습니다.
'Python' 카테고리의 다른 글
Python Reflection 맛보기 (0) | 2020.06.09 |
---|---|
pyc 파일에 대해서 (5) | 2020.06.03 |
venv는 내부적으로 어떻게 작동할까? (2) | 2020.05.19 |
bad magic number in 'application': b'\x03\xf3\r\n': ImportError (0) | 2020.05.19 |
알쏭달쏭 Python import - sys.path (8) | 2020.05.15 |