본문 바로가기

Python

Python - Module & Project

안녕하세요 이번에는 본격적으로 파이썬 프로그램을 작성하기 위한 Module의 개념에 대해서 알아보도록 하겠습니다. 그럼 시작하겠습니다 :)

Module


먼저 Module 모듈의 개념은 단순히 소프트웨어에서만 사용되는 개념이 아니다. 간단히 아래와 같은 예시를 생각해보자. 우리는 어떤 커다란 자동차를 만들어야한다. 이 자동차의 어떤 부품은 A라는 기능을 어떤 부품은 B라는 기능을 해야한다. 이 자동차를 만들기 위해서 우리는 이러한 기능단위의 부품들로 나누어서 구성을 한다. 이러한 과정을 모듈화라고 한다. 즉, 소프트웨어를 모듈화한다는 것은 소프트웨어를 기능단위로 코드를 분리하고 추상화해서 재사용가능한 모듈이라는 단위들로 만드는 것이다.

https://www.modularmanagement.com/blog/all-you-need-to-know-about-modularization

결국 커다란 프로그램을 작은 조각들인 (기능단위) 모듈들로 나누고, 이 모듈들이 모여서 하나의 큰 프로그램이 되는 것이다. 그렇다면 이러한 모듈화를 하는 이유는 뭘까? 사실 이러한 비유를 들어보면 우리에게 모듈화는 너무 당연하게 느껴진다. 하지만, Jupyter Notebook에 익숙한 많은 사용자들을 보면 모듈화의 개념이 잘 안잡혀있다. 따라서 좀 더 소프트웨어적으로 모듈화를 왜 해야하는지에 대해서 언급하고 넘어가려한다. 내가 생각하는 모듈화의 장점은 아래와 같다.

  1. 프로그램을 기능단위로 쪼개기에 효율적인 관리가 가능하다.
  2. 전체적인 프로그램을 볼 때도 기능단위로 쪼개져 있어 이해하기에 용이하다.
  3. 기능단위로 쪼개져있기에 유지보수적인 측면에서 용이하다.
  4. 재사용하기에 용이하다.

이러한 이유들로 파이썬에서는 다른 사람들의 코드를 간단히 재사용할 수 있으며, 배포또한 쉽다. 결국 코드를 더 쉽게 이해하고 사용할 수 있으며, 논리적으로 조직화할 수 있다는 것이다. 그렇다면 이러한 모듈에 대한 개념은 Python에서는 어떻게 사용할까?

Module in Python

파이썬에서 모듈은 Python definitions와 statements를 포함하고 있는 py 파일이다. ( 확장자는 반드시 py여야한다. ) 모듈 즉 그 파일 내에서는 함수, 클래스, 변수등을 정의할 수 있다. 

# add.py

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

즉, 위와 같이 정의된 add.py가 있다면 이는 "덧셈"이라는 기능을 하는 모듈이 되는 것이고, 이러한 모듈을 다른 모듈 혹은 메인 모듈에서 사용하고 싶다면 해당 모듈을 불러오면 된다.

# main.py

import add

print(add.cal_add(1,2))
>>> 3

 

이 때 우리가 알아야할 것은 크게 두가지가 있다.
첫 번째는 모듈을 import한다는 것의 의미이고, 두 번째는 import시 path와 관련된 이야기이고

Import Meaning

먼저 모듈을 import한다는 것의 의미를 살펴보자. import를 직역하면 불러온다는 것이다. 그렇다면 뭘 불러온다는 것일까? Python에서 import가 하는 역할은 C/C++에서의 #include <header_file> 과 유사하다. 파이썬에서는 import를 통해서 다른 모듈의 코드에 접근할 수 있다. 즉, 우리가 불러온다는 것은 그 모듈의 코드인 것이다. 헷갈린다면, import module_name이 해당 모듈에 있는 코드 덩어리 전체와 동치라고 생각해도 좋다. 결국 import가 불러오는 것은 코드를 불러온다는 것이 핵심인 것이다.

# main.py

import add

print(add.cal_add(1,2))
>>> 3
# main.py

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

print(cal_add(1,2))
>>> 3

즉 위 두개의 코드 블럭은 동치인 것이다. 앞선 이유들로 인해 우리는 모듈화를 통해서 관리하고 있는 것이다. 이렇게 모듈을 불러오는 방법에는 다양한 방법들이 있다. 그러나 나는 그 중에서 as를 사용하는 것에 대해서만 살펴보겠다. 개인적으로 from을 사용하는 방식은 namespace에 혼란을 가져올 수 있어 선호하지 않는다.

import module_name as alias

우리가 모듈을 불러올 때, 해당 모듈의 namespace에 있는 것을 가져옴을 확실히 하기 위해서 우리는 일종의 symbol table을 붙이게 된다. 그리고 파이썬에서는 그것이 해당 모듈의 이름이 된다. 즉 파일명이다. 그런데, 파일명에 기능이 두드러지게 적다보면, main flow내에서 name이 충돌할 수도 있고, 파일명이 너무 길어서 불편할 수 있다. 따라서 이 때 우리가 사용하게 되는 것이 as이다. as를 통해서 해당 symbol table을 우리가 붙인 alias 별명으로 사용할 수 있게 된다. 흔히 import numpy as np 와 같이 사용하는 것 처럼 말이다.

Import Path

그렇다면 이번에는 모듈을 불러올 때의 path와 관련된 이야기를 해보겠다. 우리가 어떤 모듈을 임포트할 때 인터프리터는 그 module name을 가지는 모듈을 sys.path로 주어지는 디렉토리들에서 찾게 된다. 그렇다면 sys.path에는 어떤 디렉토리 위치들이 들어가 있을까?

  • 현재 모듈을 import하는 script가 포함된 directory
  • PYHONPATH ( 환경변수로 설정되어 있는 PYTHONPATH에 들어있는 디렉토리 이름들의 목록 )
  • site-packages directory와 같은 site moudle에 의해서 다뤄지는 설치 의존적인 defualt 위치들

이러한 메커니즘이기에 우리는 현재 디렉토리에 없는 random같은 built-in module이나 numpy같은 python 설치시 default로 설치되는 module들을 바로 import할 수 있는 것이다. 

https://www.techbeamers.com/python-module/

그렇다면 이를 활용해서 우리는 sys.path를 수정한다면 즉, 추가적으로 우리가 원하는 모듈을 가져올 경로를 추가해두면 편하게 import할 수 있게 된다. 간단한 예를 통해 확인해보자.

import sys

print(sys.path)
>>> ['', 'C:\\Users\\dlgyt\\AppData\\Local\\Programs\\Python\\Python310\\python310.zip', 'C:\\Users\\dlgyt\\AppData\\Local\\Programs\\Python\\Python310\\DLLs', 'C:\\Users\\dlgyt\\AppData\\Local\\Programs\\Python\\Python310\\lib', 'C:\\Users\\dlgyt\\AppData\\Local\\Programs\\Python\\Python310', 'C:\\Users\\dlgyt\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages']

PATH = "D:\\Python"
sys.path.append(PATH)

print(sys.path[-1])
>>> 'D:\\Python'

우리는 PATH에 있는 경로를 추가적으로 defulat import 경로로 추가하고 싶은 것이다. 따라서 해당 경로를 sys.path에 추가해주면 해당 경로에 있는 모듈에 대해서도 자동적으로 import할 수 있게 된다. 즉, 앞서 작성했던 add.py가 해당 경로에 있다면 main.py가 어떤 디렉토리에 있건 상관없이 add module을 import할 수 있다는 것이다. 

물론 커맨드라인을 통해서, set PYTHONPATH= D:\\Python 과 같이 환경변수를 통해서 경로를 추가해주더라도 동일하게 동작하게 된다. 나는 개인적으로 다양한 프로젝트에서 정말 많이 쓰는 모듈들을 모은 위치는 환경변수를 통해 종종 특정 프로젝트에서 많이 사용하게 되는 모듈들의 위치는 sys.path를 통해 해당 런타임에서 추가해주는 것이 좋다고 생각한다.

Pycache

우리는 파이썬 프로그램을 작성하고 실행하다보면 종종 __pycache__라는 directory가 만든 적도 없는데 생성되는 것을 볼 수 있다. pycache란 도대체 뭘까?

우선 파이썬의 동작방식을 다시 한번 짚고 넘어갈 필요가 있다. 파이썬은 인터프리터 언어지만 전통적인 인터프리터와는 다르다. Python은 py파일을 실행시킬 때 내부적으로 PVM이라는 python virtual machine이 이해할 수 있는 Bytes codes 형태로 컴파일은 한다. 컴파일된 바이트 코드를 PVM이 단계별로 실행시키게 된다. 이 과정이 사용자에게 보이지 않기에, 소스코드가 직접 실행되는 것처럼 보이지만, 내부적으로는 Byte Code로 번역이 된다. 그런데 이런 방식은 무척 불편해보인다. 그래서 파이썬이 사용하는 것이 이 pycache이다. Bytes Code로 컴파일된 결과를 임시파일인 pyc의 확장자명을 가지는 파일로 pycache directory에 저장해두고, 다음 번에 로딩할 때 이런 컴파일 과정없이 빠르게 해주는 것이다.

https://www.python.org/dev/peps/pep-3147/

이 때 좀더 정확히 하자면 현재 디렉토리 내에 __pycache__라는 서브 디렉토리가 생기고 안에는 우리가 현재 동작시킨 module혹은 코드에 포함되어 실행된 각 모듈의 컴파일된 버전들인 module.version.pyc 형태로 각각 caching되어 있게 된다. 즉, 앞선 예시에서 우리가 main.py에서 add.py를 import하는 과정을 거쳤고 이 떄 특정 버전(컴파일된 버전)을 version이라고 할 때 __pycache__라는 서브 디렉토리 안에 add.version.pyc , main.version.pyc가 존재하는 것이다. 

https://www.reddit.com/r/git/comments/bj4hjl/how_to_ignore_pycache_directories_in_a_main/

 

Package


그렇다면 패키지는 뭘까? 앞서 살펴봤던 이러한 기능을 가진 모듈들을 collect해서 디렉토리 형식으로 구조화한 것이다. 이 때 파이썬은 앞서 이야기한 것처럼 파일명이나 디렉토리명을 symbol로 사용하는 걸 좋아하기에, 패키지명은 이러한 모듈들이 들어간 디렉토리명이 된다. 결국 패키지는 모듈들의 collection인 것이다. 이러한 세부 기능 모듈들을 크게 물리를 위한 기능, 화학을 위한 기능, 생물학을 위한 기능으로 나눌 수 있는 것처럼, 패키지 내에서는 서브 패키지를 나눠 큰 단위로써 기능을 구분한다.

 

 

http://www.trytoprogram.com/python-programming/python-packages/

이 때 패키지와 모듈의 큰 차이점이 있다. 패키지로 만들기 위해서는 반드시 해당 패키지 디렉토리에 __init__.py 파일이 필요하다. 이 __init__.py는 해당 디렉토리가 패키지로 취급되게 만들으며, 패키지의 초기화 코드로 빈 파일일 수도 있다.

이러한 패키지적인 관점에서도 from ~ import ~ 문을 지양해야하는 부분이 드러나는데, 이렇게 from package import item을 사용하게 될 경우 item은 패키지의 서브모듈일 수도 있고, 함수, 클래스, 변수 등 패키지 namespace에 정의된 다른 name들일 수도 있다. 그래서 import statement는 item이 패키지에 정의되어있는지를 검사하고 그렇지 않다면 모듈이라고 가정하고 로드를 시도하게 된다. 그래서 의도치않게 name이 충돌하는 문제가 발생할 수 있다. 반면 앞서 이야기했듯 import문을 사용해  import item.subitem 과 같은 식의 문법을 사용하게 될 경우 이러한 문제가 발생할 일은 적게 된다. 

from ~ import ~

그렇다면 이런 from ~ import ~문은 언제 많이 쓰게 될까? 이러한 from ~ import ~ 문을 주로 사용하게 되는 경우는 상대성을 활용해 서로 다른 서브 패키지 혹은 모듈을 참조하려고 할 때이다. 커맨드에서 많이 사용해봤듯이 . 즉, dot을 사용해 상대성을 활용한 참조를 사용하는 것이 효율적이게 된다. 예시를 들어보자.

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...
              
 출처 : https://docs.python.org/ko/3/tutorial/modules.html

위 예시에서 현재 surround 모듈에 있다고 하자. 순서대로 같은 디렉토리에 있는 echo를 참조하고 싶다면? 한 계층 상위 디렉토리의 다른 서브패키지인 formats를 참조하고 싶다면? 한계층 상위 디렉토리 filters의 하위 모듈인 equalizer를 참조하고 싶다면? 아래와 같이 상대경로 표시법을 활용하면 되고, 이 때는 from ~ import ~을 사용하는 것이 효과적이다.

from . import echo
from .. import formats
from ..filters import equalizer

 

References

https://docs.python.org/ko/3/tutorial/modules.html

 

6. 모듈 — Python 3.10.2 문서

6. 모듈 파이썬 인터프리터를 종료한 후에 다시 들어가면, 여러분이 만들었던 정의들이 사라집니다 (함수나 변수들). 그래서, 좀 긴 프로그램을 쓰고자 한다면, 대신 인터프리터 입력을 편집기를

docs.python.org

https://yganalyst.github.io/data_handling/Py_study14/#5-%EB%AA%A8%EB%93%88%EC%9D%B4-%EC%9E%88%EB%8A%94-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC%EB%A1%9C-%EC%9D%B4%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EA%B3%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

 

[Python 기초] 파이썬 프로그래밍의 핵심 - 모듈(Module)

파이썬의 모듈(Module)을 기초부터 확실하게 알아보자

yganalyst.github.io

 

'Python' 카테고리의 다른 글

Python - Exception Handling  (0) 2022.02.16
Python - File Handling & With Statement  (0) 2022.02.16
Python OOP - Part2  (0) 2022.02.13
Python - Magic Method  (0) 2022.02.13
Python OOP - Part 1 ( Object Oriented Programming)  (0) 2022.02.11