모듈과 패키지

변현섭·2023년 7월 27일
0

파이썬 기초 다지기

목록 보기
10/16
post-thumbnail

Ⅴ. 모듈

1. 모듈

1) 모듈 생성하기

모듈은 함수나 변수, 클래스를 모아 놓은 파이썬 파일로, 다른 파이썬 프로그램에서 불러와 사용하기 위한 용도로 사용된다. 다른 사람이 만든 모듈을 사용할 수도 있고, 자신이 만든 모듈을 사용할 수도 있다. 아래와 같이 mod1.py라는 모듈이 있다고 하자.

# mod1.py
def add(a, b):
    return a + b

def sub(a, b): 
    return a-b

모듈이라고 해서 특별한 문법이 있는 것이 아니라, 평범한 파일일 뿐이다. 즉, 모든 파이썬 파일은 모듈로 사용될 수 있다.

2) 모듈 불러오기

VS Code를 열어 아래와 같이 mod1.py파일을 작성해보자.

def add(a, b):
    return a + b

def sub(a, b): 
    return a-b

cmd 창을 열어 mod1.py를 저장한 디렉토리로 이동하자. 참고로 디렉토리 이동은 cd 명령어로 가능하며, dir 명령어로 디렉토리 안의 파일을 확인할 수 있다.

명령 프롬프트에 python을 입력하면 파이썬 대화형 인터프리터가 실행된다. 만약 다운로드가 안 되어있다면 다운로드 후 실행하면 된다.

모듈을 불러오기 위해선 Java와 마찬가지로 import 키워드를 사용한다. 여기서 주의해야 할 점은 모듈 이름에는 확장자를 포함해선 안 된다는 것이다. 따라서 import mod1.py라고 입력하지 않도록 한다.

하지만, 모듈의 함수를 사용하기 위해 항상 모듈에 닷 오퍼레이터로 함수에 접근해야 하는 것은 다소 비효율적이다. 이 때, from 키워드를 함께 사용하면 모듈 이름 없이 바로 함수를 사용할 수 있다.

모듈 네임을 없애는 작업은 마치 C/C++에서 namespace를 없애는 것과 비슷하다. 사용은 편리해지지만, 변수 이름 간의 충돌이 일어나지 않게 신경써주어야 한다. C/C++에서 namespace를 없앨 때, using std::cin과 같이 개별적으로 없앨 수도 있고, using namespace std와 같이 한번에 없앨 수도 있었듯이, 파이썬의 모듈 네임도 개별적으로 없애는 방법과 한 번에 없애는 두 가지 방법이 존재한다.

① 개별적으로 없애기

from mod1 import add, sub

② 한 번에 없애기

from mod1 import *

3) if __name__ == "__main__":

mod1.py 파일을 아래와 같이 수정한다고 해보자.

def add(a, b): 
    return a+b

def sub(a, b): 
    return a-b

print(add(1, 4))
print(sub(4, 2))

그리고 이 파일을 import해보자.

>>> import mod1
5
2

분명 import만 해주었을 뿐인데, mod1.py 파일의 실행 결과가 출력된다. 일반적으로, 모듈을 포함하는 작업에서 파일의 실행결과가 같이 출력되는 일은 원치 않을 것이다. 이러한 문제를 방지하기 위해 사용하는 것이 if __name__ == "__main__": 구문이다. 이 조건문은 실제로 모듈 파일을 실행했을 때에만 true가 되어 아래의 명령을 실행한다. 반대로, 모듈을 불러 사용하는 경우에는 false가 되어 명령이 실행되지 않는다.

def add(a, b): 
    return a+b

def sub(a, b): 
    return a-b

if __name__ == "__main__":
    print(add(1, 4))
    print(sub(4, 2))

여기서, __name__은 파이썬이 내부적으로 사용하는 변수명이다. 파일이 실행될 때 실행 파일의 __name__ 변수에 __main__ 값이 저장된다. 하지만, 파일을 모듈로 import하는 경우에는 __name__변수에 모듈의 이름이 저장된다. 위 예시에서는 mod1이 __name__에 저장될 것이다.

4) 모듈에 클래스와 변수 포함하기

위에서 설명한대로, 모듈에는 함수 뿐만 아니라 클래스나 변수도 포함할 수 있다.

PI = 3.141592

class Math: 
    def solv(self, r): 
        return PI * (r ** 2) 

def add(a, b): 
    return a+b 

이제 해당 모듈을 불러와 변수를 사용하거나 메서드를 호출할 수 있다.

>>> import mod2
>>> print(mod2.PI)
3.141592
>>> a = mod2.Math()
>>> print(a.solv(2))
12.566368

5) 에디터에서 모듈 불러오기

VS Code에 mod2.py 파일을 추가한 후 mod1.py를 import하면, mod1에 정의된 add와 sub 메서드를 사용할 수 있다.

위의 경우는 모듈과 실행파일이 같은 디렉토리에 위치하는 경우를 가정한다. 그러나 사실, 모듈은 다른 디렉토리에 위치하는 경우가 더 많다. 다른 디렉토리의 모듈을 불러오는 방법은 무엇일까? 학습을 위해 mod1.py를 module_test라고 하는 디렉토리로 옮겨보자. 이제 mod2를 실행시키면, 아래와 같이 mod1을 찾을 수 없다는 에러가 발생한다.

먼저, 아래와 같이 sys를 import해주자. 여기서, sys 모듈은 파이썬 설치와 함께 설치되는 라이브러리 모듈로, 파이썬의 라이브러리가 설치되어 있는 디렉토리를 알려준다.

>>> import sys

sys.path를 입력하면, 파이썬 라이브러리가 설치되어 있는 디렉토리 목록을 보여주는데, 이 디렉토리 안에 저장된 모듈은 경로를 별도로 지정할 필요 없이 바로 불러 사용할 수 있다. 즉, sys.path안에 우리가 import하고 싶은 모듈의 경로를 추가하면 다른 디렉토리의 모듈을 불러올 수 있는 것이다. sys.path는 리스트이므로 append 메서드를 사용해 경로를 추가할 수 있다.

>>> sys.path
['', 'C:\\Windows\\SYSTEM32\\python311.zip', 'c:\\Python311\\DLLs', 
'c:\\Python311\\lib', 'c:\\Python311', 'c:\\Python311\\lib\\site-packages']
>>> sys.path.append("C:/doit/mymod")
>>> sys.path
['', 'C:\\Windows\\SYSTEM32\\python311.zip', 'c:\\Python311\\DLLs', 
'c:\\Python311\\lib', 'c:\\Python311', 'c:\\Python311\\lib\\site-packages', 
'C:/doit/mymod']


이제 기본적인 설명은 마쳤으니, VS Code로 돌아와서 아래와 같이 코드를 작성한다.

분명히 다른 디렉토리에 있는 파일임에도 module로 잘 import된 것을 확인할 수 있다.

2. 패키지

패키지는 모듈의 집합이다. 파이썬의 패키지는 Java의 패키지와 약간만 다를 뿐 거의 유사하다. game이라는 파이썬 패키지가 아래와 같다고 하자.

game/
    __init__.py
    sound/
        __init__.py
        echo.py
        wav.py
    graphic/
        __init__.py
        screen.py
        render.py
    play/
        __init__.py
        run.py
        test.py

game 디렉토리를 가리켜 루트 디렉토리라 하고, sound, graphic, play 디렉토리를 가리켜 서브 디렉토리라 한다. 디렉토리 안에 위치한 .py 파일은 모듈이라 부른다. echo.py 파일을 아래와 같이 작성되었다고 하자.

# echo.py
def echo_test():
    print("echo")

또한 render.py도 아래와 같이 작성되어있다.

# render.py
def render_test():
    print("render")

1) 패키지 안의 함수 실행하기

닷 오퍼레이터는 계층구조를 나타내는 용도로 사용될 수 있다. 즉, 패키지 안의 함수에 접근할 때에도 닷 오퍼레이터를 사용한다.

>>> import game.sound.echo
>>> game.sound.echo.echo_test()
echo

from 키워드를 사용하면 조금 더 간단하게 함수를 호출할 수 있게 된다.

>>> from game.sound.echo import echo_test
>>> echo_test()
echo

주의해야 할 점은 닷 오퍼레이터로 import할 때, 모듈 또는 디렉토리로 끝나야한다는 것이다. 메서드로 끝맺으면 에러가 발생한다.

>>> import game.sound.echo.echo_test // 에러

당연한 이야기일 수 있지만, 상위 디렉토리를 import한 후 하위 디렉토리의 메서드의 접근하는 것 역시 에러가 발생한다.

>>> import game
>>> game.sound.echo.echo_test() // 에러

2) __init__.py

__init__.py 파일은 해당 디렉토리가 패키지에 속해 있음을 알려주는 역할을 한다. 패키지는 하나 이상의 모듈을 포함하는 디렉토리를 가리키기 때문에, 해당 디렉토리에 __init__.py 파일이 있어야만 파이썬에서 패키지로 인식한다. 물론, python 3.3 버전부터는 __init__.py 파일이 없어도 패키지로 인식하지만, 하위 버전과의 호환을 위해 여전히 __init__.py 파일을 생성할 것을 권장한다. __init__.py 파일은 아래와 같은 역할을 수행한다.

① 패키지 변수 및 함수 정의

  • 패키지 레벨에서 변수와 함수를 정의할 수 있게 해준다.
  • 즉, 해당 패키지에서 사용될 공통 변수나 함수를 정의할 수 있다.
# C:/doit/game/__init__.py
VERSION = 3.5

def print_version_info():
    print(f"The version of this game is {VERSION}.")
  • 이제 game 패키지를 import하여 패키지 변수와 함수를 사용할 수 있다.
>>> import game
>>> print(game.VERSION)
3.5
>>> game.print_version_info()
The version of this game is 3.5.

② 패키지 내 모듈을 import

  • 아래와 같이 game 패키지의 __init__.py 파일에서 render.py 모듈을 import하면, game 패키지에서 바로 render_test 메서드를 사용할 수 있게 된다.
# C:/doit/game/__init__.py
from .graphic.render import render_test

VERSION = 3.5

def print_version_info():
    print(f"The version of this game is {VERSION}.")
    
>>> import game
>>> game.render_test()
render
  • 참고로 from 뒤에 나오는 .은 현재 디렉토리라는 뜻으로 상대 경로를 나타내는 표현이다.

③ 패키지 초기화

  • 패키지를 처음 불러올 때 실행되어야 하는 코드를 작성할 수 있다.
  • 일반적으로 데이터베이스 연결이나, 설정 파일 로드와 같은 작업을 수행한다.
from .graphic.render import render_test

VERSION = 3.5

def print_version_info():
    print(f"The version of this game is {VERSION}.")

// 여기에 패키지 초기화 코드를 작성한다.
print("Initializing game ...")
  • 초기화 코드는 import하는 시점에 실행된다.
>>> import game
Initializing game ...
>>>
  • 특이한 점은 초기화 코드가 정의된 패키지의 하위 모듈을 import할 때도 초기화 코드가 실행되며, 초기화 코드는 최초 한 번만 실행된다.
>>> from game.graphic.render import render_test
Initializing game ...
>>> import game
>>>

3) __all__

__all__은 패키지 내의 모든 모듈을 import하기 위해 사용한다. 우리는 이미 import 을 이용하면 모든 것을 다 import한다고 배운 적이 있다. 하지만, 이는 이 모듈 내의 변수나 함수를 의미하는 경우에만 적용된다. 만약 *이 모듈 그 자체를 의미할 경우, 먼저 __all__ 변수를 설정해주어야 한다.

# C:/doit/game/sound/__init__.py
__all__ = ['echo']

이제 sound 디렉토리에서 import *을 사용할 경우 echo 모듈만 import되고 wav 모듈은 import 되지 않는다.

# \__all\__ 변수를 설정 전
>>> from game.sound import *
Initializing game ...
>>> echo.echo_test() // 에러

# \__all\__ 변수를 설정 후
>>> from game.sound import *
Initializing game ...
>>> echo.echo_test()
echo

4) relative 패키지

graphic 디렉터리의 render.py 모듈에서 sound 디렉터리의 echo.py 모듈을 사용하려는 상황을 가정해보자. 아래와 같이 render.py 파일을 수정하면 될 것이다.

# render.py
from game.sound.echo import echo_test
def render_test():
    print("render")
    echo_test()

물론, 지금은 패키지 구조가 간단해서 절대경로로 작성해도 큰 불편은 없지만, 패키지가 복잡해지면 절대경로로 작성하는 일은 매우 불편해진다. 이러한 이유에서 절대경로 대신 상대경로로 import하는 경우가 많은데, 이를 relative import라고 한다. 시스템 프로그래밍을 배워본 사람이라면, 상대 경로에 대해서 이미 잘 알고 있을 것이다. 배우지 않았더라도 큰 상관은 없는게, 아래의 두 개만 알아도 상대경로를 표현하는 데에 부족함이 없을 것이다.

relative import 방식을 사용하여 위 코드를 수정해보자.

# render.py
from ..sound.echo import echo_test

def render_test():
    print("render")
    echo_test()
profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글