상세 컨텐츠

본문 제목

[ Python 기초 문법 ] - 패키지

Language/Python

by bing_su 2023. 9. 25. 11:52

본문

반응형
SMALL

[ 패키지(Packages)란? ]

파이썬에서 패키지(Packages)는 도트(.)를 사용하여 파이썬 모듈을 계층적으로(디렉터리 구조) 관리할 수 있게 해 준다. 예를 들면 모듈 이름이 A.B인 경우에 A는 패키지 이름, B는 A 패키지의 모듈이 된다. 아래의 예시는 파이썬 패키지의 구조를 나타낸 것이다.

game/  # 디렉터리 명: game -> root directory
    __init__.py  # 파이썬 __init__ 모듈
    sound/  # 디렉터리 명: sound -> subdirectory
        __init__.py  # 파이썬 __init__ 모듈
        echo.py  # 파이썬 echo 모듈
        wav.py  # 파이썬 wav 모듈
    graphic/  # 디렉터리 명: graphic -> subdirectory
        __init__.py  # 파이썬 __init__ 모듈
        screen.py  # 파이썬 screen 모듈
        render.py  # 파이썬 render 모듈
    play/  # 디렉터리 명: play -> subdirectory
        __init__.py  # 파이썬 __init__ 모듈
        run.py  # 파이썬 run 모듈
        test.py  # 파이썬 test 모듈

간단한 파이썬 프로그램이 아니라면 패키지 구조로 파이썬 프로그램을 만드는 것이 공동 작업이나 유지 보수 등 여러 면에서 유용하다. 또한 패키지 구조로 모듈을 만들면, 다른 모듈과 서로 이름이 겹치더라도 안전하게 사용할 수 있다.

[ 패키지 만들기 ]

위에서 예시로 든 game 패키지를 직접 만들어 보며 패키지에 대해서 알아보자. 필자는 주피터 노트북 환경에서 실습을 진행했다. (아나콘다 아직도 설치 안 하신 분들 빨리 설치해 주세요.)

<< 패키지 기본 구성 요소 준비하기 >>

1. 주피터 노트북에서 'New' 버튼을 누른 다음 'Folder' 버튼을 누르면 새로운 폴더를 생성할 수 있다. 

라이브러리로 사용할 폴더 만들기
폴더 이름 변경

새롭게 생성된 폴더는 기본적으로 'Untitled Folder'로 생성된다. 'Rename' 버튼을 누르면 폴더나 파일의 이름을 변경할 수 있다. 해당 폴더의 이름을 루트 디렉터리인 'game'으로 바꾸자.

서브 디렉터리 생성(graphic, sound)

game 디렉터리 아래에 'graphic'과 'sound'라는 서브 디렉터리를 생성하자. 이후 아래와 같이 각 폴더에 해당하는 모듈(파이썬 파일)을 만들어 보자.

/game/__init__.py
/game/sound/__init__.py
/game/sound/echo.py
/game/graphic/__init__.py
/game/graphic/render.py

 

2. 각 디렉터리가 패키지로 판단되기 위해서는 반드시 디렉터리 내에 __init__.py 파일이 존재해야 한다. __init__.py 파일은 일단 만들어 놓고 내용은 비워 둔다.

Jupyter Notebook에서 Python 파일 생성

주피터 노트북에서 'New' 버튼을 누른 다음 'Python 3 (ipykernel)' 버튼을 누르면 새로운 파이썬 파일을 생성할 수 있다. 단, 주피터 노트북의 기본 확장명은 .ipynb이므로 확장자를 .py로 바꾸는 작업이 필요하다.

 

2-1) 새로운 파이썬 파일 생성 후 파일 이름 변경하기.

새로운 파이썬 파일 생성

위 사진과 같이 'New' > 'Python 3 (ipykernel)' 순으로 버튼을 누르면 새로운 파이썬 파일을 생성할 수 있다.

표시된 부분을 클릭하여 파일 이름 변경

표시된 부분을 클릭하면 파일 이름을 변경할 수 있다. 'Untitled'에서 우리가 원하는 파일 이름으로 변경해 주면 된다. (해당 파일은 game 디렉터리 안에 만들었으니 해당 파일은 '__init__'으로 변경했음.)

 

2-2) 주피터 노트북에서 터미널(Terminal) 실행하여 nbconvert 설치

주피터 노트북에서 확장자 변경을 위해 Terminal 실행

확장자 변경을 위해서는 터미널을 실행해야 한다. 'New' > 'Terminal' 순으로 누르면 터미널을 실행할 수 있다.

pip install nbconvert

윈도우 커맨드가 실행되면 위의 명령어를 입력하여 nbconvert를 설치한다.

 

2-3) 터미널(Terminal)에서 .ipynb → .py로 확장자 변경

game 디렉터리로 이동

먼저 파일 확장자를 변경하기 위해 파일이 있는 디렉터리로 이동해야 한다. Jupyter Notebook에서 폴더를 생성하면, 기본적으로 'C:\Users\사용자명\폴더명' 경로에 저장된다. cd는 Change Directory의 줄임말로 현재 작업하고 있는 디렉터리의 위치를 이동하는 명령어다. 따라서 'cd C:\Users\사용자명\game'을 통해 __init__.ipynb 파일이 있는 game 디렉터리로 이동하자.

jupyter nbconvert --to python 파일명.ipynb

위의 명령어를 사용하면 해당 디렉터리에서 변환할 .ipynb 파일을 지정하고 .py 파일로 변환할 수 있다.

변환할 파일이 여러개일 경우

변환할 파일이 여러 개일 경우, 아래와 같은 명령어를 사용하여 ipynb 파일을 한번에 변환할 수도 있다.

jupyter nbconvert --to python 파일명1.ipynb 파일명2.ipynb 파일명3.ipynb

즉, 해당 경우에는 아래와 같이 명령어를 사용하면 된다.

※ 해당 디렉터리의 모든 파일을 변환하고자 할 경우, 아래와 같은 명령어를 사용하면 된다.

jupyter nbconvert *.ipynb --to python

파일을 변환한 뒤에는 .ipynb 파일을 삭제하자. 확장자명이 .py여야 라이브러리가 패키지로 인식되어 작동하기 때문이다.

 

3. echo.pyrender.py 파일에는 아래와 같은 코드를 작성한다.

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

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

<< 패키지 내부 함수 실행 >>

패키지를 사용하여 echo.py 파일의 echo_test() 함수를 실행해 보려고 한다. 패키지 안의 함수를 실행하는 방법에는 아래와 같이 3가지가 있다. 주의할 점은 하나의 예제를 실행 후에는 인터프리터를 종료하고 다시 실행해야 한다. (exit() 사용) 인터프리터를 다시 시작하지 않을 경우 이전에 import 한 것들이 메모리에 남아 있어 엉뚱한 결과가 나올 수 있다.

주피터 노트북 터미널을 이용하여 패키지 실행 표시한 부분만 명령어로 입력하면 됩니다.

빨간 박스로 표시한 부분이 패키지 안의 함수를 실행하는 코드다. 해당 코드만 따로 정리하여 나타내면 아래와 같다.

# echo 모듈을 import하여 실행
>>> import game.sound.echo
>>> game.sound.echo.echo_test()
echo

# echo 모듈이 있는 디렉터리까지를 from ... import하여 실행
>>> from game.sound import echo
>>> echo.echo_test()
echo

# echo 모듈의 echo_test 함수를 직접 import하여 실행
>>> from game.sound.echo import echo_test
>>> echo_test()
echo

주의해야 할 점은 import할 때 마지막 항목은 반드시 실행하려는 함수가 있는 디렉터리여야 한다는 점이다. 예를 들면 아래와 같은 코드는 오류가 발생한다.

import game을 수행하면 game 디렉터리의 모듈 또는 game 디렉터리의 __init__.py에 정의한 코드만 참조할 수 있다.

 

또한, 도트 연산자(.)를 사용하여 import를 할 때 가장 마지막 항목은 반드시 모듈 또는 패키지여야만 한다. 마찬가지로 예를 들면 아래와 같은 코드는 오류가 발생한다.

echo_test는 모듈 또는 패키지 이름이 아니므로 오류 발생

[ __init__.py의 용도 ]

__init.py__는 해당 디렉터리가 패키지의 일부임을 알려주는 역할을 한다. 만약 패키지에 포함된 디렉터리에 해당 파일이 없다면 패키지로 인식되지 않는다. 다만, python 3.3 버전부터는 __init__.py 파일이 없어도 패키지로 인식한다. 하지만 하위 버전과의 호환을 위하여 __init__.py 파일을 생성하는 것이 좋다.

 

또한, __init__.py 파일에는 패키지와 관련된 설정이나 초기화 코드를 포함할 수 있다. 단, __init__.py 파일을 수정한 후에 프로그램을 실행할 때는 반드시 파이썬 인터프리터를 종료하고 다시 실행해야 한다. 아래의 몇 가지 예시를 살펴보며 패키지를 활용할 수 있는 방법에 대해 알아보자.

1. 패키지 변수 및 함수의 정의

패키지 수준에서 공통으로 사용할 변수와 함수를 정의할 수 있다.

# C:\Users\User_Name\game\__init__.py
VERSION = 3.8

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

이렇게 패키지의 __init__.py 파일에 정의된 변수와 함수는 아래와 같이 사용할 수 있다.

>>> import game
>>> print(game.VERSION)
3.8
>>> game.print_version_info()
The version of this game is 3.8.

2. 패키지 내 모듈을 미리 import 하는 경우

__init__.py 파일에 패키지 내의 다른 모듈을 import하여 패키지를 사용하는 코드에서 간편하게 접근할 수 있게 한다. 아래의 코드를 통해 해당 기능을 이해해 보자.

#C:\Users\User_Name\game\__init__.py
''' .graphic.render에서 사용한 맨 앞의 도트 연산자(.)는 현재 디렉터리를 의미한다.
이에 대해서는 추후에 자세히 다룰 예정이다. '''
from .graphic.render import render_test

VERSION = 3.8

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

이 기능을 활용하여 아래와 같이 간편하게 game 패키지를 통해 render_test 함수를 사용할 수 있다.

 

그리고 만약, 패키지에서 모든 것(*)을 import 한 후 하위 패키지의 모듈을 사용하고자 하는 경우. 해당 디렉터리의 __init__.py 파일에 __all__ 변수를 설정하여 import 할 수 있는 모듈을 정의해 줘야 한다. 즉, __all__ 변수를 설정하지 않고 아래의 코드를 실행하면 오류가 발생한다.

해당 오류를 고치기 위해서는 sound 디렉터리의 __init__.py 파일에 아래의 코드를 추가해야 한다. 여기서 __all__이 의미하는 것은 sound 디렉터리에서 *을 사용하여 import 할 경우, 이 변수에 정의된 echo 모듈만 import 된다는 의미다.

__all__=['echo']

위의 코드를 추가한 후 위의 예제를 다시 수행하면 이번에는 정상적으로 실행됨을 확인할 수 있다.

※ from game.sound.echo import * 과의 비교

아래와 같이 참조할 때는 __all__ 변수를 사용하지 않아도 된다.

앞서 살펴본 from game.sound import *은 sound 패키지의 모든 것(*)을 import 한다는 뜻이다.

그러나 위와 같이 from game.sound.echo import *은 echo 모듈모든 것(*)을 import한다는 뜻이다. 즉, from a.b.c import *에서 from의 마지막 항목인 c가 모듈일 경우에는 __all__ 변수와 상관없이 echo 모듈이 실행되므로, __all__ 변수를 사용할 필요가 없다.

3. 패키지 초기화

__init__.py 파일에 패키지를 처음 불러올 때 실행되어야 하는 코드를 작성할 수 있다. 예를 들면 데이터베이스 연결이나 설정 파일 로드와 같은 작업을 수행할 수 있다. 아래의 코드를  통해 실습을 진행하자.

# C:\Users\User_Name\game\__init__.py
VERSION = 3.8

def print_version_info():
    print(f"The version of this game is {VERSION}.")
    
# Package initialization code
print("Initializing game ...")

위와 같이 코드를 입력하면 패키지를 처음 import 할 때 초기화 코드가 실행된다.

또한 game 패키지의 초기화 코드는 해당 패키지의 하위 모듈의 함수를 import 할 경우에도 실행된다.

단, 초기화 코드는 한 번 실행된 후에는 다시 import를 수행하더라도 실행되지 않는다. 예를 들어 아래와 같이 game 패키지를 import 한 후에 하위 모듈을 다시 import를 해도 초기화 코드는 처음 한 번만 실행된다.

[ relative 패키지 ]

특정 디렉터리의 모듈이 다른 디렉터리의 모듈을 사용하고자 할 때가 있다. 즉, game 패키지에서 graphic 디렉터리의 render.py 모듈이 sound 디렉터리의 echo.py 모듈을 사용하고자 하는 경우다. 이러한 상황에 대한 실습을 아래와 같은 코드를 통해 진행해 보자.

# C:\Users\User_Name\game\graphic\render.py

# 추가된 코드: echo 모듈의 echo_test 함수를 import
from game.sound.echo import echo_test 
def render_test():
    print("render")
    echo_test()

위와 같이 수정한 후 프로그램을 실행하면 아래와 같이 이상 없이 잘 실행되는 것을 확인할 수 있다.

이와 같이 전체 경로를 사용하여 import를 할 수도 있다. 그러나 아래와 같이 relative 하게 import 할 수도 있다.

# C:\Users\User_Name\game\graphic\render.py

# 추가된 코드: echo 모듈의 echo_test 함수를 import
from ..sound.echo import echo_test 
def render_test():
    print("render")
    echo_test()

위의 소스코드에서는 경로 중 game이 사라지고 ..이 그 자리를 대신한다. 여기서 ..은 부모 디렉터리를 의미한다. 단, 사용하고자 하는 디렉터리의 깊이(depth)가 동일할 경우에만 부모 디렉터리(..)의 사용이 가능하다. 또한, ..와 같은 relative 한 접근자는 render.py와 같이 모듈 내에서만 사용해야 한다. 아래 실행 화면과 같이 파이썬 인터프리터 실행 화면에서 해당 접근자를 사용하면 오류가 발생한다.

 

반응형
LIST

관련글 더보기

댓글 영역