{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(ch:module-advanced)=\n", "# (부록) 모듈 고급 활용법" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "하나의 모듈이 독립적으로 제공되기도 하지만 다른 모듈과 함께 하나의 모음집으로 제공되기도 한다.\n", "모음집의 크기와 용도에 따라 패키지, 라이브러리, 프레임워크 등 다양한 이름으로 불린다. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 모듈, 패키지, 라이브러리, 프레임워크" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**패키지**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**패키지**package는 모듈을 모아놓은 디렉토리(폴더)이며, \n", "`__init__.py` 모듈을 포함한다.\n", "패키지 안에 하위 패키지가 포함될 수 있으며, 각 하위 패키지 모두 `__init__,py` 모듈을 \n", "포함한다. \n", "`__init__.py` 모듈은 해당 패키지가 사용될 때 필요한 기본 설정이 저장되어 있고 자동 실행된다.\n", "아래 그림이 전형적인 패키지 구조를 보여준다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

<그림 출처: 파이썬 패키지 구조>

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "아래 그림은 패키지와 단순히 모듈을 포함하는 디렉토리(폴더)의 차이점을 잘 보여준다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "

<그림 출처: 파이썬 패키지 구조>

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**라이브러리**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**라이브러리**library는\n", "모듈, 패키지 등 재사용이 가능한 코드의 모음집을 통칭헤서 부르는 이름이다.\n", "패키지가 하위 패키지를 포함하기에 라이브러리로 불리기도 하지만\n", "라이브러리는 여러 개의 패키지로 이루어진 모음집으로 하나의 패키지와 구분된다.\n", "[파이썬 표준 라이브러리](https://docs.python.org/3/library/)에서 \n", "기본으로 제공되는 패키지와 모듈을 확인할 수 있다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "또한 게임 프로그래밍에 사용되는 Pygame, 데이터 분석에 필수인 Numpy와 Pandas,\n", "웹에서 필요한 데이터 수집에 유용한 BeautifulSoup,\n", "머신러닝/딥러닝 분야의 Tensorflow, Keras, PyTorch 등이\n", "대표적인 제3자 파이썬 라이브러리다.\n", "제3자 라이브러린란 파이썬 공식 홈페이지가 아닌 다른 방식으로 제공되는 라이브러리를 가리킨다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**프레임워크**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**프레임워크**framework는 라이브러리 보다 포괄적인 개념이다.\n", "라이브러리가 도구 모음집만 제공하는 반면에\n", "프레임워크는 라이브러리와 함께 라이브러리를 쉽게 적용할 수 있는 \n", "틀frame과 아키텍처architecture를 \n", "함께 제공한다.\n", "\n", "예를 들어, **플라스크**Flask 프레임워크는 웹서버 개발에 적절한 틀과 구조를,\n", "**장고**Django 프레임워크는 웹 어플리케이션 구현에 최적인 틀과 구조를 제공한다.\n", "사용자는 프레임워크가 제공하는 틀과 구조에 맞춰 적절한 코드를 작성하면 원하는 결과를 \n", "쉽고 빠르게 구현할 수 있다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `pip` 파이썬 패키지 관리자" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "파이썬 프로그래밍에 사용되는 모든 모듈, 패키지, 라이브러리, 프레임워크 등은 \n", "일반적으로 `pip` 이라는 파이썬 패키지 관리자를 이용하여 설치하고 관리한다. \n", "예를 들어 넘파이 모듈을 설치하려면 다음 명령을 실행한다.\n", "달러 기호(`$`) 터미널의 프롬프트를 가리키며 실제로 입력하지는 않음에 주의하라.\n", "\n", "```\n", "$ pip install numpy\n", "```\n", "\n", "주피터 노트북에서도 동일한 방식으로 설치할 수 있다.\n", "단, 아래 처럼 느낌표로 시작해야 한다.\n", "\n", "```\n", ">>> !pip install numpy\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "모듈, 패키지, 라이브러리, 프레임워크 중에서 가장 작은 단위인 모듈만 \n", "불러와서import 모듈에 포함된 함수, 클래스 등을 사용할 수 있다.\n", "모듈을 불러오는 기본 방식은 다음과 같다.\n", "\n", "```python\n", ">>> import 모듈명\n", "```\n", "\n", "반면에 모듈이 아닌 패키지를 불러올 수도 있지만 그러려면 패키지 폴더에 있는 `__init__.py` 모듈에 \n", "해당 패키지를 불러올 때 기본적으로 함께 불러오는 모듈이 지정되어 있어야 한다.\n", "예를 들어 `numpy` 는 패키지이지만 아래 방식으로 불러올 수 있다.\n", "\n", "```python\n", ">>> import numpy\n", "```\n", "\n", "그러면 `__init__().py` 모듈에 지정된 방식으로 필요한 다른 모듈과 패키지가 함께 불려오게 된다.\n", "보다 자세한 사항은 \n", "[코딩도장: 패키지에서 from import 응용하기](https://dojang.io/mod/page/view.php?id=2450)를 참고한다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "모듈은 크게 세 종류로 나뉜다.\n", "\n", "* 내장 모듈: \n", " `math`, `urllib.request`, `random`, `turtle`, `os`, `sys` 등 \n", " 파이썬을 설치할 때 기본으로 제공되는 모듈\n", "* 제3자 라이브러리third-party library 모듈: \n", " `numpy.random`, `matplotlib.pyplot`, `pygame.mixer` 등 \n", " 제3자가 제공한 라이브러리에 포함된 모듈\n", "* 사용자 정의 모듈: \n", " 개인 프로젝트를 위해 직접 작성한 파이썬 코드 파일" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 사용자 정의 모듈" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**파이썬 파일 저장**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "아래 코드를 담고 있는 `wc.py` 파일을 모듈로 불러와서 활용해보자. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "def linecount(filename):\n", " count = 0\n", " with open(filename) as f:\n", " for line in f:\n", " count += 1\n", " return count\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`wc.py` 파일을 직접 생성하거나 아래 `myWget`파일을 이용하여 다운로드한다." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from urllib.request import urlretrieve\n", "from pathlib import Path\n", "\n", "# 서버 기본 주소\n", "base_url = \"https://raw.githubusercontent.com/codingalzi/pybook/master/jupyter-book/codes/\"\n", "# 파일 저장 위치\n", "code_path = Path() / \"codes\"\n", "\n", "def myWget(filename):\n", " file_url = base_url + filename\n", " code_path.mkdir(parents=True, exist_ok=True)\n", " target_path = code_path / filename\n", "\n", " return urlretrieve(file_url, target_path)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "아래 코드를 실행하면 현재 디렉토리에 `codes` 라는 하위 디렉토리가 생성되고\n", "그 안에 `wc.py` 파일이 저장된다." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(PosixPath('codes/wc.py'), )" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myWget('wc.py')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "다운로드된 파일의 내용을 확인하면 다음과 같이 앞서 언급한 내용과 동일하다." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def linecount(file_name):\n", " count = 0\n", " with open(file_name) as f:\n", " for line in f:\n", " count += 1\n", " return count\n", "\n" ] } ], "source": [ "with open(code_path / \"wc.py\") as f:\n", " print(f.read())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**사용자 정의 모듈 불러오기**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "만약에 `wc.py` 파일이 `codes` 폴더가 아닌 현재 작업 디렉토리에 저장되어 있다면\n", "단순히 아래 방식을 사용하면 된다.\n", "\n", "```python\n", ">>> import wc\n", "```\n", "\n", "하지만 여기서는 그렇지 않기에 좀 더 복잡하다. \n", "이유는 앞서 패키지를 설명했던 것처럼 `codes` 디렉토리를 패키지로 지정한 다음에\n", "파이썬 실행기interpreter에 해당 패키지의 존재를 알려야 하기 때문이다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 패키지 지정" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "특정 폴더를 파이썬 패키지로 지정하려면 해당 폴더에 `__init__.py` 파일명을 가진 파일이 존재하면 된다.\n", "`__init__.py` 파일은 해당 패키지를 불러올 때 사용할 설정을 담은 모듈인데\n", "아무 설정도 하지 않는다면 비어두어도 된다.\n", "여기서는 아무 설정도 하지 않는 빈 파일로 사용한다.\n", "\n", "비어 있는 `__init__.py` 파일을 생성해서 `codes` 디렉토리에 저장하거나 아래 명령문을 실행해서\n", "해당 파일을 다운로드 한다." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(PosixPath('codes/__init__.py'), )" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myWget('__init__.py')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`os` 모듈의 `listdir()` 함수를 이용하여\n", "`codes` 디렉토리에 두 개의 파일이 포함되어 있음을 확인한다." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['collision_detection',\n", " 'collision_detection.zip',\n", " 'lc.py',\n", " 'wc.py',\n", " '__init__.py',\n", " '__pycache__']" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import os\n", "\n", "os.listdir(code_path)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ":::{admonition} `__pycache__` 디렉토리\n", ":class: info\n", "\n", "파이썬 실행기interpreter가 실행에 필요한 \n", "파일들을 생성해서 보관한다.\n", ":::" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 라이브러리 경로" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "현재 작업 디렉토리가 아닌 디렉토리에 포함된 파이썬 파일을 모듈로 불러오는 기본적인\n", "방법은 **라이브러리 경로**library path에 해당 디렉토리를 \n", "임시로 추가하는 것이다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ":::{admonition} 영구적 라이브러리 경로 추가\n", ":class: warning\n", "\n", "특정 디렉토리를 라이브러리 경로에 영구적으로 추가하는 방법은 여기서는 다루지 않는다.\n", "필요하다면 [파이썬 경로 추가 방법](https://pybasall.tistory.com/201)를 참고할 수 있다.\n", ":::" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "파이썬이 기본적으로 지원하는 라이브러리들의 경로들의 리스트를 `sys.path` 변수가 가리킨다." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['/mnt/c/Users/gslee/Documents/GitHub/pybook/jupyter-book',\n", " '/home/gslee/miniconda3/lib/python311.zip',\n", " '/home/gslee/miniconda3/lib/python3.11',\n", " '/home/gslee/miniconda3/lib/python3.11/lib-dynload',\n", " '',\n", " '/home/gslee/miniconda3/lib/python3.11/site-packages']" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import sys\n", "\n", "sys.path" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "따라서 `sys.path` 가 가리키는 리스트에 원하는 디렉토리의 경로를 추가만 하면 된다.\n", "`Path` 객체의 `absolute()` 메서드가 지정된 경로의 절대경로를 반환한다." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PosixPath('/mnt/c/Users/gslee/Documents/GitHub/pybook/jupyter-book/codes')" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "code_path.absolute()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "위 경로를 문자열로 변환한 다음에 라이브러리 경로에 추가하지." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "sys.path.append(str(code_path.absolute()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이제 새로운 경로가 추가된 것을 확인할 수 있다." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['/mnt/c/Users/gslee/Documents/GitHub/pybook/jupyter-book',\n", " '/home/gslee/miniconda3/lib/python311.zip',\n", " '/home/gslee/miniconda3/lib/python3.11',\n", " '/home/gslee/miniconda3/lib/python3.11/lib-dynload',\n", " '',\n", " '/home/gslee/miniconda3/lib/python3.11/site-packages',\n", " '/mnt/c/Users/gslee/Documents/GitHub/pybook/jupyter-book/codes']" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sys.path" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "라이브러리 경로에 포함된 디렉토리의 파이썬 파일은 \n", "바로 불러올 수 있다." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "import wc" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "이제 `linecount()` 함수를 사용하면 된다.\n", "아래 코드는 `wc.py` 파일이 총 6개의 줄을 담고 있음을 확인해준다." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "6" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wc.linecount(code_path / \"wc.py\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 모듈 파일 실행" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "모듈엔 자주 사용될 수 있는 함수, 클래스, 상수 등을 저장한다.\n", "그런데 모듈을 불러올 때 모듈 파일에 포함된 모든 명령문이 실행된다.\n", "따라서 불필요한 코드를 모듈에 포함시키는 일은 삼가한다.\n", "\n", "반면에 파이썬 파일을 모듈로써의 기능과 함께 하나의 완성된 프로그램으로 작동하게 할 수도 있는데\n", "그럴 때는 보통 실행하고자 하는 코드를 파일의 맨 아래에 아래 형식으로 추가한다.\n", "\n", "```python\n", "if __name__ == '__main__':\n", " 명령문\n", "```\n", "\n", "`__name__` 은 모듈의 이름을 가리키는 파일 객체의 속성 변수이며,\n", "`__main__` 은 현재 코드를 실행하는 주체를 가리키는 값이다.\n", "그러면 이와 같이 작성된 코드의 실행여부는 위 코드를 포함한 파일이 사용되는 방식에 따라 결정된다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**모듈로 사용되는 경우**\n", "\n", "만약 파일을 직접실행하지 않고 다른 파일의 코드에서 모듈로 불러오면 \n", "`__name__ == '__main__'`는 거짓이 되어 `if` 문의 본체 명령문이 무시된다.\n", "이유는 `__name__`은 모듈의 이름을, `__main__`은 해당 모듈을 불러온 다른 파일을 가리키기에\n", "서로 다른 값을 가리키기 때문이다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**파일을 직접 실행하는 경우**\n", "\n", "예를 들어 아래와 같이 터미널에서 해당 파일을 실행하면 지정된 명령문이 실행된다.\n", "이유는 `__name__`과 `__main__` 모두 해당 모듈 파일을 가리키기 때문이다.\n", "\n", "```bash\n", "$ python 파일명.py\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**파일 실행에 인자가 필요한 경우**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "경우에 따라 아래와 같은 방식으로 파이썬 파일을 실행할 때 실행되는 코드에서 요구되는 인자를 지정해야 할 필요도 있다.\n", "\n", "```bash\n", "$ python 파일명.py 인자1 인자2 ... 인자n\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "예를 들어 `test.py` 라는 파이썬 파이썬 파일에 다음과 같은 코드가 포함되어 있다고 가정한다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "import sys\n", "print(\"파일 실행 인자:\", sys.argv)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "그리고 아래와 같이 터미널에서 아래와 같이 명령문을 실행해 보자." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```bash\n", "$ python test.py arg1 arg2 arg3\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "그러면 다음 문장이 터미널 화면에 출력된다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```bash\n", "파일명과 실행 인자: ['test.py', 'arg1', 'arg2', 'arg3']\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "즉, `python` 명령어의 인자로 사용되는 `test.py`, `arg1`, `arg2`, `arg3` \n", "모두 문자열로 갖는 리스트가 파일 내부에서 `sys.argv` 변수에 할당된다.\n", "예를 들어 `test.py` 파일에 아래 코드가 저장되어 있다고 가정하자." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "import sys\n", "\n", "int1 = int(sys.argv[1])\n", "int2 = int(sys.argv[2])\n", "\n", "print(f\"{int1}와 {int2}의 곱: {int1*int2}\")\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "터미널에서 위 파일을 2와 7 두 인자와 함께 실행하면 아래와 같이 실행된다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```bash\n", "$ python test.py 2 7\n", "2와 7의 곱: 14\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 필수 예제" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "참고: [(필수 예제) 모듈 고급 활용법](https://colab.research.google.com/github/codingalzi/pybook/blob/master/examples/examples-modules_advanced.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "vscode": { "interpreter": { "hash": "6c86b3592b6800d985c04531f2c445f0fa6967131b8dd6395a925f7622e55602" } } }, "nbformat": 4, "nbformat_minor": 1 }