7  모듈·패키지·터미널: 도구를 도구답게 (6주차 보충)

7.1 이번 장에서 배우는 것

6주차는 수요와 공급 모형을 함수로 완성한 주였습니다. 이번 보충 장은 새 경제 이론을 얹는 대신, 그 함수들이 노트북 한 칸을 벗어나 오래 살아남게 만드는 방법을 다룹니다. 코드를 새로 짜는 시간보다 이미 짠 코드를 정리하고 꺼내 쓰는 시간이 실무에서는 훨씬 길다는 사실을, 이 장에서 처음 실감하게 됩니다.

파이썬 쪽에서 다루는 것

  • .py 파일에 함수를 담아 모듈로 만들고, import의 세 가지 방식을 구분해 쓴다.
  • 여러 모듈을 폴더로 묶어 패키지로 정리하고, __init__.py의 역할을 이해한다.
  • if __name__ == "__main__":으로 한 파일에 부품과 프로그램의 두 얼굴을 담는다.
  • 터미널을 열어 cd, dir/ls, python으로 스크립트를 실행하고, argparse로 명령줄 인자를 받는다.

경제학 쪽에서 이어 가는 것

  • 6장의 수요·공급 계산을 market_models 모듈과 econo_analyzer 패키지로 재편한다.
  • 곡선 이동의 비교정학을 함수 호출 한 번으로 재현하고, 그 결과를 그림으로 확인한다.
  • 파이썬을 모르는 사람도 쓸 수 있는 균형 계산기를 만들어 배포의 첫 장면을 경험한다.

7.2 여는 질문: 이 함수, 다음 주에도 쓸 수 있을까

6장을 끝낸 여러분의 노트북에는 제법 쓸 만한 물건이 들어 있습니다. 가격을 넣으면 수요량을 돌려주는 quantity_demanded, 공급량을 돌려주는 quantity_supplied, 두 곡선이 만나는 자리를 찾아 주는 find_equilibrium. 이콘 카페 아메리카노 시장 하나를 통째로 계산하는 부품 세트입니다.

문제는 이 부품들이 특정 노트북 파일의 특정 셀 안에 갇혀 있다는 것입니다. 다음 주 노트북에서 균형을 다시 구하려면 어떻게 해야 할까요. 지금 방법으로는 그 셀을 찾아 스크롤하고, 블록을 잡아 복사하고, 새 노트북에 붙여 넣는 것뿐입니다. 옆자리 친구가 같은 함수를 쓰고 싶다고 하면 메신저로 코드 뭉치를 통째로 보내 줘야 하고요. 그렇게 흩뿌린 사본 하나에서 오타를 고치면, 나머지 사본들은 그대로 낡은 채 남습니다.

복사·붙여넣기는 두 번째 사용까지는 편하지만 세 번째부터 빚이 됩니다. 이 장은 그 빚을 지지 않는 방법에서 시작합니다. 함수를 노트북에서 꺼내 파일 하나에 모아 두면, 그 파일을 부르는 한 줄만으로 어느 노트북에서든 같은 함수를 쓸 수 있습니다. 파일이 늘어나면 폴더로 묶어 정돈하고, 마지막에는 파이썬을 켜지 않고 터미널에서 바로 실행되는 프로그램으로까지 키웁니다. 오늘 하는 일은 새 계산을 배우는 것이 아니라, 이미 가진 계산을 오래 쓰는 살림법을 배우는 것입니다.

7.3 그래프 준비

이 장 후반부에서 패키지를 만들 때, 수요·공급 곡선을 그려 주는 함수를 직접 구현해 씁니다. 그 그림의 축과 제목에 한글이 들어가므로, 라벨이 네모 상자로 깨지지 않도록 폰트 설정을 먼저 실행해 둡니다. 이 셀은 이 책의 그래프가 등장하는 모든 장에서 똑같이 나오니, 눈에 익혀 두면 좋습니다.

import platform
import matplotlib.pyplot as plt
if platform.system() == "Windows":
    plt.rc("font", family="Malgun Gothic")
elif platform.system() == "Darwin":
    plt.rc("font", family="AppleGothic")
else:
    plt.rc("font", family="NanumBarunGothic")
plt.rcParams["axes.unicode_minus"] = False
!pip install koreanize-matplotlib
import koreanize_matplotlib

내 컴퓨터가 Windows나 macOS라면 위의 폰트 셀 하나로 충분합니다. 구글 코랩에서 실습 중이라면 한글 폰트가 깔려 있지 않아 그래프의 한글이 깨질 수 있는데, 그때만 바로 위 koreanize-matplotlib 설치 셀을 실행하세요. 로컬에서 실습 중이라면 그 설치 셀은 건너뜁니다.

7.4 로컬 터미널 첫걸음: 검은 화면과 친해지기

함수를 파일로 꺼내기 전에, 그 파일이 살아갈 동네를 먼저 둘러봅시다. 프로그래머들이 터미널(terminal)이라 부르는 곳입니다. 처음 여는 사람에게는 커서만 깜빡이는 검은 화면이 조금 위압적으로 보이지만, 하는 일은 의외로 단순합니다.

터미널은 마우스 대신 글자로 컴퓨터에 일을 시키는 창입니다. 폴더를 더블클릭하는 대신 폴더 이름을 적어 이동하고, 아이콘을 누르는 대신 프로그램 이름을 적어 실행합니다. 평소 탐색기에서 클릭으로 하던 일을, 손가락으로 길을 불러 주며 하는 셈입니다. 익숙해지면 클릭보다 빠르고, 무엇보다 지금 무슨 일이 일어나는지가 글자로 남아 눈에 보입니다.

터미널 여는 법

Windows에서는 시작 버튼을 누르고 “PowerShell”을 검색해 실행합니다. Windows 11이라면 “터미널”이라는 앱을 열어도 같은 화면이 나옵니다. macOS에서는 Cmd+Space로 Spotlight을 열고 “터미널”(Terminal)을 검색합니다. 창이 열리면 대략 이런 한 줄이 여러분을 기다립니다.

Windows PowerShell:

PS C:\Users\hana>

macOS 터미널:

hana@MacBook ~ %

앞부분은 지금 서 있는 폴더의 주소이고, 맨 끝의 >%는 명령을 기다리는 중이라는 표시입니다. 이 기호를 프롬프트(prompt)라고 부릅니다. 프롬프트 뒤에 명령을 적고 Enter를 누르면, 컴퓨터가 그 줄을 읽어 실행합니다.

처음 배우는 세 명령

터미널에서 첫발을 떼는 데는 명령 셋이면 충분합니다. cd는 폴더를 옮겨 다니는 명령(change directory), dir는 지금 폴더에 무엇이 있는지 보여 주는 명령입니다. macOS와 리눅스에서는 dir 대신 ls를 씁니다. python --version은 파이썬이 깔려 있는지, 몇 버전인지 확인하는 명령입니다. Windows PowerShell에서 이 셋을 차례로 써 본 실제 세션은 이렇게 생겼습니다.

PS C:\Users\hana> cd econ_python
PS C:\Users\hana\econ_python> dir

    디렉터리: C:\Users\hana\econ_python

Mode      LastWriteTime        Length Name
----      -------------        ------ ----
-a---   2026-06-30   오후 2:11    612 market_models.py
-a---   2026-06-30   오후 2:15    980 sd_analyzer.py

PS C:\Users\hana\econ_python> python --version
Python 3.12.4

첫 줄에서 econ_python 폴더로 들어가자 프롬프트의 주소가 한 칸 깊어졌습니다. 둘째 명령 dir은 그 폴더 안의 파일 목록을 보여 주고, 마지막 명령은 파이썬 3.12가 준비되어 있음을 알려 줍니다. 파일을 만들어 실행하기 직전에 늘 이 세 가지를 확인하는 습관을 들이면, “파일이 어디 갔지” 하는 사고의 절반은 미리 막을 수 있습니다.

import os
print(os.getcwd())
C:\Users\voidm\Master_project_hub\ECONO_000\.claude\worktrees\economics-python-curriculum-3aebeb

지금 이 노트북에도 서 있는 폴더가 있습니다. os는 운영체제(operating system)와 대화하는 표준 모듈이고, os.getcwd()는 현재 작업 디렉터리(current working directory)를 알려 줍니다. 방금 터미널 프롬프트 앞머리에 찍히던 그 주소와 정확히 같은 개념입니다. 노트북이 파일을 읽거나 쓸 때 기준으로 삼는 폴더가 바로 여기고, 잠시 뒤 우리가 만들 .py 파일도 이 폴더에 생깁니다. 그래서 이 주소는 이 장 내내 조용히 중요한 역할을 합니다. 터미널을 더 깊이 다루는 명령들은 부록 A에 사전처럼 정리해 두었으니, 막히는 명령이 있으면 거기서 찾아보면 됩니다.

7.5 함수를 파일로: 모듈

이제 6장의 함수들을 노트북 밖으로 꺼냅니다. 목적지는 확장자가 .py인 평범한 텍스트 파일입니다. 파이썬에서는 함수와 변수를 담아 둔 .py 파일 하나를 모듈(module)이라고 부릅니다. 거창한 개념이 아닙니다. 코드가 든 파일 한 개, 그것이 곧 모듈입니다.

노트북 안에서 파일을 만드는 가장 간편한 방법은 %%writefile이라는 셀 매직(cell magic)입니다. 셀 맨 윗줄에 %%writefile 파일이름을 적으면, 그 아래에 쓴 내용이 실행되는 대신 그 이름의 파일로 저장됩니다. 6장에서 만든 세 함수를 market_models.py라는 파일에 담아 보겠습니다.

%%writefile market_models.py
"""이콘 카페에서 출발한 선형 수요·공급 모형 모듈.

Qd = a - bP, Qs = c + dP 형태의 선형 시장 모형에서
수요량, 공급량, 균형점을 계산하는 함수를 담고 있습니다.

사용 예:
    import market_models
    p, q = market_models.find_equilibrium(a=100, b=2, c=10, d=3)
"""


def quantity_demanded(price, a, b):
    """선형 수요 함수 Qd = a - bP 의 수요량을 계산합니다. (음수면 0)"""
    if b <= 0:
        raise ValueError("수요 곡선 기울기 b는 양수여야 합니다.")
    return max(0, a - b * price)


def quantity_supplied(price, c, d):
    """선형 공급 함수 Qs = c + dP 의 공급량을 계산합니다. (음수면 0)"""
    if d <= 0:
        raise ValueError("공급 곡선 기울기 d는 양수여야 합니다.")
    return max(0, c + d * price)


def find_equilibrium(a, b, c, d):
    """균형 가격과 균형 거래량을 (P*, Q*) 튜플로 돌려줍니다.

    양(+)의 균형 가격이 존재하지 않으면 (None, None)을 돌려줍니다.
    """
    if b <= 0 or d <= 0:
        raise ValueError("기울기 b, d는 모두 양수여야 합니다.")
    p_star = (a - c) / (b + d)
    if p_star < 0:
        return (None, None)
    q_star = quantity_demanded(p_star, a, b)
    return (p_star, q_star)


if __name__ == "__main__":
    # 이 블록은 파일을 직접 실행할 때만 돕니다. import 할 때는 조용합니다.
    print("market_models 자체 테스트를 시작합니다.")
    p, q = find_equilibrium(a=100, b=2, c=10, d=3)
    print(f"기본 시장 균형: P* = {p:.2f}, Q* = {q:.2f}")
Overwriting market_models.py

한 줄씩 뜯어보기

맨 윗줄 %%writefile market_models.py는 파이썬 문법이 아니라 노트북에게 건네는 지시입니다. “이 셀을 실행하지 말고, 아래 내용을 market_models.py라는 파일로 저장하라”는 뜻이지요. 처음 저장하면 노트북은 Writing market_models.py라고 답하고, 같은 이름의 파일이 이미 있으면 Overwriting market_models.py라고 답합니다. 지금 이 셀을 실행하면 6장에서 쓰던 그 파일을 새로 덮어쓰게 되는데, 내용이 같으니 걱정하지 않아도 됩니다.

그 아래는 순수한 파이썬입니다. 파일 첫머리의 삼중따옴표 문단은 모듈 전체를 설명하는 문서화 문자열(docstring)로, 나중에 help(market_models)를 부르면 이 글이 그대로 나옵니다. 이어지는 세 함수는 6장에서 쓰던 것과 한 글자도 다르지 않고, 각 함수 안의 raise ValueError는 기울기로 0이나 음수가 들어오는 잘못된 호출을 미리 막아 줍니다. 파일 맨 끝의 if __name__ == "__main__": 블록은 잠시 뒤 정체를 밝힐 예정이니, 지금은 직접 실행할 때만 도는 자기 테스트라고만 알아 두면 됩니다.

import의 세 가지 방식

파일이 생겼으니 불러 쓸 차례입니다. 방식이 셋인데, 셋 다 여러분이 이미 본 문법입니다. 첫째는 모듈 전체를 이름 그대로 불러오는 방식입니다.

import market_models

p_star, q_star = market_models.find_equilibrium(a=100, b=2, c=10, d=3)
print(f"이콘 카페 아메리카노 시장의 균형: P* = {p_star:.1f}, Q* = {q_star:.1f}")
이콘 카페 아메리카노 시장의 균형: P* = 18.0, Q* = 64.0

import market_models가 실행되는 순간, 파이썬은 작업 디렉터리에서 market_models.py를 찾아 위에서 아래로 한 번 읽고, 그 안의 함수들을 market_models라는 이름표 아래 모아 둡니다. 그래서 함수를 부를 때 market_models.find_equilibrium처럼 소속을 먼저 밝혀야 합니다. 성과 이름을 함께 부르는 방식이라 손이 조금 더 가지만, 그 대신 이 함수가 어느 모듈에서 왔는지가 코드에 또렷이 남습니다. 여러 모듈을 섞어 쓸 때 이 또렷함은 값을 합니다.

from market_models import find_equilibrium, quantity_demanded

print(find_equilibrium(a=100, b=2, c=10, d=3))
print(quantity_demanded(price=18, a=100, b=2))
(18.0, 64.0)
64

from 모듈 import 이름 꼴로 꺼내 온 함수는 소속 없이 이름만으로 부를 수 있습니다. 코드가 짧아지는 대신 대가가 하나 있습니다. 서로 다른 모듈에서 같은 이름을 꺼내 오면, 나중에 실행된 import가 앞의 것을 소리 없이 덮어씁니다. 예를 들어 두 모듈이 모두 plot이라는 함수를 갖고 있다면, 이 방식으로는 둘 중 하나만 살아남습니다. 그래서 이 방식은 이름이 겹칠 걱정이 없는 한두 개를 콕 집어 가져올 때 어울립니다. 세 번째 방식은 이미 여러분 손에 붙어 있습니다.

import market_models as mm

print(mm.find_equilibrium(a=70, b=2, c=10, d=3))   # 방학이라 수요가 줄어든 시장
(12.0, 46.0)

import ... as ...는 모듈에 별명을 붙입니다. 매주 import numpy as np라고 쓰던 바로 그 문법이지요. 소속을 밝히는 첫째 방식의 장점은 그대로 두면서 타이핑만 줄인 절충안이라, 자주 부르는 모듈일수록 이 방식이 선호됩니다. np, plt, pd 같은 별명은 파이썬 문법이 강제하는 것이 아니라 전 세계 사용자들이 맞춰 온 관례입니다. 관례를 따르면 남이 내 코드를 읽기 쉽고, 내가 남의 코드를 읽기도 쉽습니다. 우리 market_models에도 mm이라는 짧은 별명을 붙여 두었습니다.

import는 파일을 어디서 찾을까

한 가지 짚고 넘어갈 것이 있습니다. import numpy는 우리가 만든 적 없는 파일인데 어떻게 찾아질까요. 그리고 방금 우리가 만든 market_models는 또 어떻게 찾아졌을까요. 파이썬은 import를 만나면 정해진 폴더 목록을 순서대로 뒤집니다. 이 목록은 sys.path라는 리스트에 들어 있고, 맨 앞자리가 바로 지금 실행 중인 노트북의 작업 디렉터리입니다. 그다음이 파이썬과 함께 설치된 표준 라이브러리, 마지막이 pip install로 깔아 둔 외부 패키지들입니다.

import sys, os

print("작업 디렉터리:", os.getcwd())
print("찾는 순서 맨 앞 세 곳:", sys.path[:3])
print("market_models.py가 여기 있는가:", os.path.exists("market_models.py"))
작업 디렉터리: C:\Users\voidm\Master_project_hub\ECONO_000\.claude\worktrees\economics-python-curriculum-3aebeb
찾는 순서 맨 앞 세 곳: ['C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.13_3.13.3824.0_x64__qbz5n2kfra8p0\\python313.zip', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.13_3.13.3824.0_x64__qbz5n2kfra8p0\\DLLs', 'C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.13_3.13.3824.0_x64__qbz5n2kfra8p0\\Lib']
market_models.py가 여기 있는가: True

sys.path의 첫 칸은 보통 빈 문자열이거나 작업 디렉터리 자체인데, 파이썬은 이 빈 문자열을 지금 서 있는 폴더로 해석합니다. os.path.exists는 주어진 이름의 파일이 그 폴더에 실제로 있으면 True를 돌려줍니다. %%writefile이 작업 디렉터리에 market_models.py를 만들었고, import가 바로 그 폴더를 가장 먼저 뒤지기 때문에, 지금까지의 모든 일이 맞아떨어진 것입니다.

이 전제는 이 장 내내 유효해야 합니다. 만약 노트북을 다른 폴더에서 열거나, 만든 .py 파일을 다른 곳으로 옮기면, import가 첫 칸에서 파일을 찾지 못해 곧이어 소개할 오류가 납니다. 지금은 “내 노트북과 내 .py 파일이 같은 폴더에 있어야 한다”는 규칙 하나만 손에 쥐고 가면 충분합니다.

모듈이면서 프로그램: __name__

이제 파일 끝에 미뤄 둔 그 블록의 정체를 밝힐 차례입니다. 파이썬은 파일을 읽을 때마다 __name__이라는 숨은 변수로 그 파일에 이름표를 하나 붙입니다. 규칙은 단순합니다. 파일이 다른 코드에 의해 import로 불려 온 손님이면 이름표는 그 모듈의 이름이 되고, 파일이 직접 실행되는 주인공이면 이름표는 "__main__"이라는 특별한 문자열이 됩니다. 지금 이 노트북의 이름표부터 확인해 봅시다.

print(__name__)                  # 지금 이 노트북의 이름표
print(market_models.__name__)    # import 되어 온 모듈의 이름표
__main__
market_models

노트북은 지금 실행의 주인공이므로 __main__, market_models는 불려 온 손님이므로 자기 이름이 나옵니다. 따라서 if __name__ == "__main__":은 지금 내가 주인공으로 실행되는 중일 때만 아래를 실행하라는 조건문입니다. 그 안에 넣어 둔 테스트 코드는 파일을 직접 실행할 때만 돌고, 다른 곳에서 import될 때는 얌전히 건너뛰어집니다. 앞서 import market_models를 실행했을 때 아무 출력도 없었던 이유가 여기 있습니다. 이번에는 이 파일을 주인공으로 세워 봅니다.

%run market_models.py
market_models 자체 테스트를 시작합니다.
기본 시장 균형: P* = 18.00, Q* = 64.00

테스트 블록이 깨어나 두 줄이 출력되었습니다. 같은 파일인데도 손님으로 불려 갈 때는 조용한 부품이었다가, 주인공으로 실행되니 자기 검증을 돌리는 프로그램으로 행동한 것입니다. 파일 하나에 두 얼굴을 담는 이 장치는 파이썬 생태계 전체의 관례입니다. 앞으로 만드는 모든 모듈 끝에 if __name__ == "__main__": 블록을 두고 그 안에 간단한 사용 예나 테스트를 적어 두면, 그 파일은 남이 가져다 쓰는 부품이면서 혼자서도 자기 상태를 점검하는 프로그램이 됩니다.

실수 노트: ModuleNotFoundError, 파일이 거기 없을 때

모듈을 처음 다룰 때 가장 자주 만나는 오류입니다. import market_models를 실행했는데 이렇게 나온다면,

ModuleNotFoundError: No module named 'market_models'

파이썬은 “그런 이름의 파일을 찾는 순서대로 다 뒤졌는데 없더라”라고 말하는 것입니다. 원인은 대개 둘 중 하나입니다. 첫째, %%writefile 셀을 아직 실행하지 않아 파일이 만들어지지 않았을 때. 둘째, 노트북과 .py 파일이 서로 다른 폴더에 있을 때입니다. 앞에서 배운 os.getcwd()로 지금 폴더를 확인하고, os.path.exists("market_models.py")로 그 폴더에 파일이 실제로 있는지 확인하면 원인이 금방 드러납니다. 오류 메시지의 마지막 줄, 특히 작은따옴표 안의 이름을 눈여겨보는 습관을 들이세요. 그 이름이 여러분이 부른 것과 철자까지 같은지가 첫 번째 점검 대상입니다.

# [직접 해보기] 나의 첫 모듈
# TODO 1) 새 코드 셀을 만들어 %%writefile my_utils.py 로 시작하게 하고,
#         금액 숫자를 "1,800원" 꼴의 문자열로 바꿔 주는 format_won(amount) 함수를 정의해 저장하세요.
#         힌트: f"{amount:,.0f}원"
# TODO 2) import my_utils 로 불러온 뒤, my_utils.format_won(1800) 을 출력해 결과를 확인하세요.
# TODO 3) 파일 끝에 if __name__ == "__main__": 블록을 두고 그 안에서 format_won 을 한 번 시험해 보세요.
#         그런 다음 import my_utils 를 다시 실행하면 그 시험 출력이 나오지 않는 것을 확인하세요.

7.6 모듈에서 패키지로: 도구 정리함 짜기

모듈 맛을 들이면 파일이 금방 불어납니다. 모형을 정의하는 함수, 곡선 이동을 분석하는 함수, 그래프를 그리는 함수, 다음 학기에는 탄력성과 세금 계산까지. 이것을 전부 market_models.py 한 파일에 밀어 넣으면, 파일 하나가 수백 줄로 불어나 결국 함수 하나 찾으려고 한참 스크롤하던 6장 노트북의 처지로 되돌아갑니다. 서랍 하나에 옷과 양말과 필기구를 다 넣어 둔 것과 같지요.

해결책은 폴더입니다. 관련된 모듈끼리 폴더로 묶고, 그 폴더를 파이썬이 하나의 꾸러미로 인식하게 만든 것을 패키지(package)라고 부릅니다. 우리는 econo_analyzer라는 패키지를 만들어, 모형은 models에, 분석은 analysis에, 그림 도구는 utils에 나눠 담을 계획입니다. 먼저 폴더 뼈대부터 세웁니다.

import os

for folder in ["econo_analyzer/models", "econo_analyzer/analysis", "econo_analyzer/utils"]:
    os.makedirs(folder, exist_ok=True)

print(os.path.exists("econo_analyzer/utils"))
True

os.makedirs는 중간 단계 폴더까지 한 번에 만들어 줍니다. econo_analyzer가 없으면 그것부터 만들고, 그 안에 models를 만드는 식입니다. 짝꿍 옵션 exist_ok=True는 이미 있으면 그냥 넘어가라는 뜻입니다. 이 옵션 덕분에 셀을 몇 번을 다시 실행해도 폴더가 이미 있다는 오류가 나지 않습니다. 노트북처럼 같은 셀을 반복 실행하는 환경에서는, 이렇게 다시 실행해도 탈이 없는 코드를 짜는 습관이 특히 값집니다.

인터넷에서 파이썬 예제를 보다 보면 폴더를 만들 때 아래처럼 느낌표로 시작하는 명령을 자주 만납니다.

!mkdir -p econo_analyzer/models econo_analyzer/analysis econo_analyzer/utils
!ls econo_analyzer

느낌표로 시작하는 줄은 파이썬이 아니라 터미널 명령을 노트북 안에서 실행하라는 표시입니다. 구글 코랩에서 실행하면 mkdir -p가 폴더 세 개를 만들고, lsanalysis models utils를 출력합니다. 다만 이 방식이 통하는 것은 코랩이 리눅스 컴퓨터이기 때문입니다. 내 Windows 컴퓨터의 주피터에서는 mkdir -p-p를 알아듣지 못해 그대로 통하지 않습니다. 그래서 이 책의 실행 셀은 어느 운영체제에서나 똑같이 동작하는 파이썬 표준 os.makedirs를 씁니다. 운영체제마다 갈리는 이런 명령들의 대응표는 부록 A에 정리해 두었습니다.

%%writefile econo_analyzer/__init__.py
"""이콘 카페에서 출발한 시장 분석 패키지."""
Overwriting econo_analyzer/__init__.py

폴더만 만든다고 파이썬이 그것을 패키지로 인정해 주지는 않습니다. 폴더 안에 __init__.py라는 특별한 이름의 파일이 있어야, 파이썬은 이 폴더는 import해도 되는 꾸러미다라고 받아들입니다. 안이 비어 있어도 상관없지만, 우리는 한 줄짜리 docstring을 넣어 이 패키지가 무엇인지 적어 두었습니다. 방금 만든 세 하위 폴더에도 같은 요령으로 하나씩 세워 줍니다.

%%writefile econo_analyzer/models/__init__.py
"""모형 정의 모듈들이 사는 곳."""
Overwriting econo_analyzer/models/__init__.py
%%writefile econo_analyzer/analysis/__init__.py
"""분석 모듈들이 사는 곳."""
Overwriting econo_analyzer/analysis/__init__.py
%%writefile econo_analyzer/utils/__init__.py
"""그래프 등 보조 도구 모듈들이 사는 곳."""
Overwriting econo_analyzer/utils/__init__.py

뼈대가 섰으니 알맹이 모듈을 하나씩 채웁니다. 첫 번째는 패키지의 심장인 models/market.py입니다. 6장의 세 함수가 다시 등장합니다.

%%writefile econo_analyzer/models/market.py
"""선형 수요·공급 모형: Qd = a - bP, Qs = c + dP."""


def quantity_demanded(price, a, b):
    """선형 수요 함수 Qd = a - bP 의 수요량을 계산합니다. (음수면 0)"""
    if b <= 0:
        raise ValueError("수요 곡선 기울기 b는 양수여야 합니다.")
    return max(0, a - b * price)


def quantity_supplied(price, c, d):
    """선형 공급 함수 Qs = c + dP 의 공급량을 계산합니다. (음수면 0)"""
    if d <= 0:
        raise ValueError("공급 곡선 기울기 d는 양수여야 합니다.")
    return max(0, c + d * price)


def find_equilibrium(a, b, c, d):
    """균형 가격과 균형 거래량을 (P*, Q*) 튜플로 돌려줍니다.

    양(+)의 균형 가격이 존재하지 않으면 (None, None)을 돌려줍니다.
    """
    if b <= 0 or d <= 0:
        raise ValueError("기울기 b, d는 모두 양수여야 합니다.")
    p_star = (a - c) / (b + d)
    if p_star < 0:
        return (None, None)
    q_star = quantity_demanded(p_star, a, b)
    return (p_star, q_star)
Overwriting econo_analyzer/models/market.py

내용이 market_models.py와 거의 같다는 것을 눈치챘을 것입니다. 맞습니다. 모듈을 패키지로 옮기는 일의 본질은 코드를 고쳐 쓰는 것이 아니라 코드의 주소를 새로 정해 주는 것입니다. 달라진 점은 하나뿐입니다. 끝의 __main__ 테스트 블록을 뺐습니다. 패키지 깊숙이 들어간 모듈은 직접 실행되기보다 import되는 것이 본업이라, 자기 실행용 테스트가 굳이 필요하지 않기 때문입니다. 패키지 전체를 한꺼번에 시험하는 더 나은 방법은 10장에서 다룹니다.

%%writefile econo_analyzer/analysis/shifts.py
"""수요·공급 곡선 이동(shift) 전후의 균형을 비교하는 분석 모듈."""

from econo_analyzer.models import market


def compare_equilibria(params_old, params_new):
    """곡선 이동 전후의 파라미터를 받아 두 균형을 비교합니다.

    params_old, params_new: {"a": ..., "b": ..., "c": ..., "d": ...} 딕셔너리.
    돌려주는 값: 두 균형점과 가격·거래량의 변화 방향을 담은 딕셔너리.
    """
    p_old, q_old = market.find_equilibrium(
        params_old["a"], params_old["b"], params_old["c"], params_old["d"])
    p_new, q_new = market.find_equilibrium(
        params_new["a"], params_new["b"], params_new["c"], params_new["d"])

    result = {
        "old_equilibrium": (p_old, q_old),
        "new_equilibrium": (p_new, q_new),
        "price_change": direction(p_old, p_new, "상승", "하락"),
        "quantity_change": direction(q_old, q_new, "증가", "감소"),
    }
    return result


def direction(old, new, up_word, down_word):
    """두 값을 비교해 변화 방향을 우리말 단어로 돌려줍니다."""
    if old is None or new is None:
        return "비교 불가"
    if new > old:
        return up_word
    if new < old:
        return down_word
    return "변동 없음"
Overwriting econo_analyzer/analysis/shifts.py

한 줄씩 뜯어보기

이 파일에서 눈여겨볼 줄은 위쪽의 from econo_analyzer.models import market입니다. 패키지 안에서는 점(.)이 폴더 구분자 역할을 합니다. econo_analyzer 폴더 안의 models 폴더에서 market 모듈을 가져오라는 뜻이니, 점으로 이어 쓴 경로가 곧 폴더 트리를 그대로 읽은 것입니다. 같은 패키지 식구끼리도 이렇게 전체 주소로 서로를 부릅니다.

그 아래 compare_equilibria는 이동 전과 후의 파라미터 꾸러미 두 개를 받아, 각각의 균형을 구하고 가격과 거래량이 어느 쪽으로 움직였는지를 우리말 단어로 정리해 돌려줍니다. 방향을 판정하는 잔일은 direction이라는 작은 도우미 함수에게 맡겼습니다. 한 함수가 모든 것을 떠안는 대신, 작은 함수들이 서로를 부르며 일을 나누는 이 구조가 모듈을 오래 관리하기 좋게 만듭니다.

세 번째 모듈은 그림 담당 utils/plotting.py입니다. 6장에서 균형을 숫자로만 확인하고 넘어갔다면, 이제 두 곡선과 그 교점을 한 번에 그려 주는 함수를 아예 상비해 둘 차례입니다. 앞서 폰트 준비 셀에서 정한 한글 설정을 이 함수가 그대로 물려받도록, 폰트 설정은 함수 안에 넣지 않고 노트북 쪽에 맡겨 둡니다.

%%writefile econo_analyzer/utils/plotting.py
"""수요·공급 곡선을 그리는 보조 도구 모듈.

한글 폰트 설정은 여기서 하지 않습니다. 폰트는 이 모듈을 쓰는
노트북(또는 스크립트) 쪽에서 정해 두면 그 설정을 그대로 따릅니다.
"""

import numpy as np
import matplotlib.pyplot as plt

from econo_analyzer.models import market


def plot_market(a, b, c, d, p_max=None, title="수요와 공급"):
    """선형 수요·공급 곡선과 균형점을 그리고 (P*, Q*)를 돌려줍니다."""
    if p_max is None:
        p_max = a / b                      # 수요량이 0이 되는 가격까지 그립니다
    prices = np.linspace(0, p_max, 100)
    qd = [market.quantity_demanded(p, a, b) for p in prices]
    qs = [market.quantity_supplied(p, c, d) for p in prices]

    p_star, q_star = market.find_equilibrium(a, b, c, d)

    plt.figure(figsize=(7, 5))
    plt.plot(qd, prices, label="수요")
    plt.plot(qs, prices, label="공급")
    if p_star is not None:
        plt.scatter([q_star], [p_star], color="black", zorder=3)
        plt.annotate(f"균형 (Q*={q_star:.1f}, P*={p_star:.1f})",
                     xy=(q_star, p_star),
                     xytext=(q_star * 1.03, p_star * 1.05))
    plt.xlabel("수량 (Q)")
    plt.ylabel("가격 (P)")
    plt.title(title)
    plt.legend()
    plt.grid(True, alpha=0.3)
    return (p_star, q_star)


def plot_shift(params_old, params_new, title="비교정학: 이동 전과 후"):
    """이동 전(실선)과 후(점선)의 곡선을 겹쳐 그리고 두 균형점을 표시합니다."""
    a0, b0, c0, d0 = params_old["a"], params_old["b"], params_old["c"], params_old["d"]
    a1, b1, c1, d1 = params_new["a"], params_new["b"], params_new["c"], params_new["d"]

    p_max = max(a0 / b0, a1 / b1)
    prices = np.linspace(0, p_max, 100)

    plt.figure(figsize=(7, 5))
    plt.plot([market.quantity_demanded(p, a0, b0) for p in prices], prices,
             color="tab:blue", label="수요(전)")
    plt.plot([market.quantity_demanded(p, a1, b1) for p in prices], prices,
             color="tab:blue", linestyle="--", label="수요(후)")
    plt.plot([market.quantity_supplied(p, c0, d0) for p in prices], prices,
             color="tab:orange", label="공급(전)")
    plt.plot([market.quantity_supplied(p, c1, d1) for p in prices], prices,
             color="tab:orange", linestyle="--", label="공급(후)")

    p0, q0 = market.find_equilibrium(a0, b0, c0, d0)
    p1, q1 = market.find_equilibrium(a1, b1, c1, d1)
    if p0 is not None:
        plt.scatter([q0], [p0], color="black", zorder=3)
        plt.annotate("전", xy=(q0, p0), xytext=(q0 + 3, p0 + 1))
    if p1 is not None:
        plt.scatter([q1], [p1], color="crimson", zorder=3)
        plt.annotate("후", xy=(q1, p1), xytext=(q1 + 3, p1 + 1))
    plt.xlabel("수량 (Q)")
    plt.ylabel("가격 (P)")
    plt.title(title)
    plt.legend()
    plt.grid(True, alpha=0.3)
    return (p0, q0), (p1, q1)
Overwriting econo_analyzer/utils/plotting.py

파일 맨 위의 import 두 줄은 이 모듈이 기대는 도구의 선언입니다. numpy는 0부터 최고 가격까지 촘촘한 가격 격자를 만드는 np.linspace에, matplotlib는 그리기에 씁니다. 그리고 여기서도 from econo_analyzer.models import market로 우리 패키지의 모형 함수를 불러다 씁니다. 그림 모듈이 수요량 계산법을 따로 갖지 않고 모형 모듈에 물어보는 이 구조 덕분에, 나중에 모형 공식을 손보면 그림도 저절로 새 공식을 따릅니다. 계산은 한 곳에만 두는 것, 이것이 모듈을 나누는 큰 이유입니다.

from econo_analyzer.models import market

p_star, q_star = market.find_equilibrium(a=100, b=2, c=10, d=3)
print(f"패키지판 균형: P* = {p_star:.1f}, Q* = {q_star:.1f}")
패키지판 균형: P* = 18.0, Q* = 64.0

import 경로가 곧 폴더 경로입니다. econo_analyzer 폴더가 작업 디렉터리 바로 아래에 있어서 파이썬이 찾을 수 있다는 전제는, 모듈 때와 똑같이 여기서도 유효합니다. 다른 프로젝트에서 이 패키지를 그대로 쓰고 싶다면 지금으로서는 폴더째 복사해 가는 것이 방법입니다. 복사 없이 어느 폴더에서든 import되게 만드는 설치라는 개념은 10장에서 만납니다. 이제 분석 모듈을 실제로 일 시켜 봅니다. 이콘 카페의 학기 중과 방학을 비교하는 일입니다.

from econo_analyzer.analysis import shifts

semester = {"a": 100, "b": 2, "c": 10, "d": 3}   # 학기 중
vacation = {"a": 70, "b": 2, "c": 10, "d": 3}    # 방학: 수요만 감소

report = shifts.compare_equilibria(semester, vacation)
for key, value in report.items():
    print(f"{key}: {value}")
old_equilibrium: (18.0, 64.0)
new_equilibrium: (12.0, 46.0)
price_change: 하락
quantity_change: 감소

수요가 줄자 균형 가격은 18에서 12로, 균형 거래량은 64에서 46으로 내려앉았습니다. 결과 딕셔너리의 마지막 두 줄은 그 방향을 하락과 감소라는 단어로 요약해 줍니다. 수요 감소가 가격과 거래량을 함께 끌어내린다는 원론의 결론이, 함수 호출 한 번으로 재현됩니다. 6장에서 같은 비교를 하려면 균형 공식을 손으로 두 번 돌리고 결과를 눈으로 견줘야 했는데, 이제는 파라미터 꾸러미 두 개를 건네는 것이 일의 전부입니다. 숫자로 확인했으니 그림으로도 봅니다.

from econo_analyzer.utils import plotting

eq = plotting.plot_market(a=100, b=2, c=10, d=3, title="이콘 카페 아메리카노 시장 (학기 중)")
plt.show()
print(f"함수가 돌려준 균형: {eq}")

함수가 돌려준 균형: (18.0, 64.0)

plot_market은 그림을 그리는 동시에 균형점 튜플을 돌려줍니다. 화면에 보여 주는 일과 값을 돌려주는 일을 한 함수가 겸할 수도 있다는 예입니다. 이 그래프가 말하는 경제학 문장은 한 줄로 이렇습니다. 우하향하는 수요와 우상향하는 공급이 (64, 18)에서 만나고, 그 교점이 이 시장의 균형이다. 같은 그림 도구로 이번에는 방학의 수요 감소를 겹쳐 그려 봅니다.

plotting.plot_shift(semester, vacation, title="방학이 오면: 수요 감소의 비교정학")
plt.show()

파란 수요 곡선만 점선(방학)으로 안쪽으로 옮겨 갔고, 공급은 변한 것이 없어 실선과 점선이 정확히 포개져 있습니다. 균형점은 검은 점(전)에서 붉은 점(후)으로, 왼쪽 아래로 미끄러졌습니다. 여기서 곡선의 이동과 곡선 위에서의 이동을 구분해 읽는 것이 비교정학 그림의 핵심입니다. 이 그림에서는 수요 곡선이 통째로 이동했고, 균형은 그대로 있는 공급 곡선을 따라 아래로 미끄러져 내려간 것입니다. 도구 상자의 진짜 값은 다음 장면에서 드러납니다.

plotting.plot_market(a=80, b=4, c=20, d=2, title="학식 도시락 시장 (가상)")
plt.show()

코드를 한 줄도 새로 짜지 않고 완전히 다른 시장의 그림이 나왔습니다. 함수를 만들 때 들인 시간이 이렇게 회수됩니다. 모형은 models에, 분석은 analysis에, 그림은 utils에. 서랍이 정돈되어 있으면 꺼내 쓰는 손이 빨라지고, 실수도 줄어듭니다. 아메리카노든 도시락이든, 파라미터 네 개만 갈아 끼우면 같은 도구가 새 시장을 그려 냅니다.

# [직접 해보기] 원두 파동 시나리오
# 국제 원두 가격이 급등해 이콘 카페의 공급이 줄었습니다.
# 공급 절편 c가 10에서 0으로 내려간 상황을 학기 중(semester)과 비교해 봅시다.
# TODO 1) c만 0으로 바꾼 딕셔너리 bean_shock 을 만드세요. (a=100, b=2, d=3 은 그대로)
# TODO 2) shifts.compare_equilibria(semester, bean_shock) 로 두 균형을 비교하고 결과를 출력하세요.
# TODO 3) plotting.plot_shift(semester, bean_shock) 로 그림을 그리고,
#         이번에는 어느 곡선이 움직였는지 한 문장으로 적어 보세요.

실수 노트: 패키지 주소를 한 층 건너뛰었을 때

패키지를 쓰기 시작하면 이런 오류를 자주 만납니다.

from econo_analyzer import market
ImportError: cannot import name 'market' from 'econo_analyzer'

market 모듈은 econo_analyzer 바로 아래가 아니라 econo_analyzer/models 안에 있는데, 중간의 models 층을 건너뛰고 부른 것이 원인입니다. 폴더 트리를 그대로 읽어 from econo_analyzer.models import market이라고 층을 다 밟아 써야 합니다. ModuleNotFoundError와 비슷해 보이지만 성격이 조금 다릅니다. 이쪽은 그런 폴더(패키지)는 찾았는데 그 안에 그 이름이 없다는 뜻이라, 오류 이름도 ImportError입니다. 오류 메시지에서 from 뒤에 적힌 위치와 import 뒤에 적힌 이름을 나란히 놓고, 내가 부른 주소가 폴더 트리와 정말 일치하는지 한 층씩 짚어 보세요.

7.7 파이썬을 모르는 사람에게도: argparse 첫걸음

모듈과 패키지는 훌륭한 공유 방식이지만, 받는 쪽도 파이썬을 쓸 줄 알아야 한다는 전제가 깔려 있습니다. 그런데 이런 부탁을 받았다고 해 봅시다. 경제학원론 조교 선배가 “나는 파이썬을 몰라. 숫자 네 개만 넣으면 균형이 나오는 프로그램으로 만들어 줄 수 있을까?”라고 합니다. 선배에게 노트북을 열어 셀을 순서대로 실행하라고 안내할 수는 없습니다. 필요한 것은 터미널에 한 줄만 치면 답이 나오는 프로그램입니다.

그 한 줄은 대략 이런 모습입니다. python sd_analyzer.py --a 100 --b 2 --c 10 --d 3. 파일 이름 뒤에 --a, --b처럼 이름표를 단 숫자들이 따라옵니다. 이렇게 명령 뒤에 딸려 오는 값을 명령줄 인자(command-line argument)라고 부르고, 이 인자들을 받아 정리해 주는 파이썬 표준 도구가 argparse입니다.

%%writefile sd_analyzer.py
"""터미널에서 실행하는 수요·공급 균형 계산기.

사용 예:
    python sd_analyzer.py --a 100 --b 2 --c 10 --d 3
"""

import argparse

import market_models

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="선형 수요·공급 모형 Qd = a - bP, Qs = c + dP 의 균형을 계산합니다.")
    parser.add_argument("--a", type=float, required=True,
                        help="수요 절편 a: 가격이 0일 때의 수요량")
    parser.add_argument("--b", type=float, required=True,
                        help="수요 기울기 b: 가격 1단위당 수요 감소량 (양수)")
    parser.add_argument("--c", type=float, required=True,
                        help="공급 절편 c: 가격이 0일 때의 공급량")
    parser.add_argument("--d", type=float, required=True,
                        help="공급 기울기 d: 가격 1단위당 공급 증가량 (양수)")
    args = parser.parse_args()

    p_star, q_star = market_models.find_equilibrium(args.a, args.b, args.c, args.d)

    print(f"수요 함수: Qd = {args.a} - {args.b}P")
    print(f"공급 함수: Qs = {args.c} + {args.d}P")
    if p_star is None:
        print("이 파라미터로는 양(+)의 균형 가격이 없습니다.")
    else:
        print(f"균형 가격  P* = {p_star:.2f}")
        print(f"균형 거래량 Q* = {q_star:.2f}")
Overwriting sd_analyzer.py

한 줄씩 뜯어보기

먼저 import market_models를 보세요. 균형 공식을 이 파일에 다시 쓰지 않았습니다. 아침에 만든 모듈이 벌써 재사용되고 있는 것입니다. 단, 터미널에서 이 스크립트를 실행할 때도 market_models.py가 같은 폴더에 있어야 한다는 전제가 그대로 따라붙습니다.

parser = argparse.ArgumentParser(description=...)는 인자를 받아 줄 접수 창구를 하나 엽니다. description에 적은 문장은 잠시 뒤 --help 화면 맨 위에 소개문으로 나옵니다. 이어지는 parser.add_argument가 네 번 반복되는데, 이 한 줄 한 줄이 이런 이름의 값을 이런 방식으로 받겠다는 규격 선언입니다. "--a"는 인자의 이름표, type=float는 받은 글자를 실수로 바꿔 담으라는 지시, required=True는 이 값을 빠뜨리면 실행을 거부하라는 뜻, help="..."--help 화면에 나올 그 인자의 설명입니다. 규격을 다 선언한 뒤 args = parser.parse_args()가 실제로 들어온 값들을 읽어 args에 담고, 그때부터 args.a, args.b처럼 점을 찍어 꺼내 씁니다. 나머지 줄은 6장에서 하던 계산 그대로입니다.

이 스크립트의 무대는 터미널이지만, %run이 있어 노트북 안에서 미리 시험해 볼 수 있습니다. %run은 터미널에서 python sd_analyzer.py ...를 치는 일을 노트북 안에서 흉내 내는 라인 매직이라, 파일 이름 뒤에 붙인 인자까지 그대로 전달합니다. 학기 중 시장의 파라미터를 넣어 봅니다.

%run sd_analyzer.py --a 100 --b 2 --c 10 --d 3
수요 함수: Qd = 100.0 - 2.0P
공급 함수: Qs = 10.0 + 3.0P
균형 가격  P* = 18.00
균형 거래량 Q* = 64.00

학기 중 아메리카노 시장의 균형 (18, 64)가 명령 한 줄로 나왔습니다. 이제 파라미터 실험이 코드 수정이 아니라 명령 수정이 됩니다. 방학 시나리오는 숫자 하나만 바꿔 다시 실행하면 됩니다.

%run sd_analyzer.py --a 70 --b 2 --c 10 --d 3
수요 함수: Qd = 70.0 - 2.0P
공급 함수: Qs = 10.0 + 3.0P
균형 가격  P* = 12.00
균형 거래량 Q* = 46.00

파일은 한 글자도 건드리지 않았는데 다른 시장이 분석되었습니다. 코드는 고정하고 입력만 바꾼다, 이것이 스크립트의 사고방식입니다. 조교 선배가 자기 컴퓨터의 진짜 터미널에서 이 프로그램을 돌리면 화면은 이렇게 보입니다.

PS C:\Users\hana\econ_python> python sd_analyzer.py --a 70 --b 2 --c 10 --d 3
수요 함수: Qd = 70.0 - 2.0P
공급 함수: Qs = 10.0 + 3.0P
균형 가격  P* = 12.00
균형 거래량 Q* = 46.00

노트북의 %run이 보여 준 출력과 한 글자도 다르지 않습니다. %run이 흉내 낸 것이 바로 이 터미널 실행이기 때문입니다. 그런데 선배가 옵션 이름을 잊어버리면 어떻게 할까요. 우리가 사용 설명서를 따로 써 줄 필요는 없습니다. argparse가 이미 만들어 두었으니까요.

%run sd_analyzer.py --help
usage: sd_analyzer.py [-h] --a A --b B --c C --d D

선형 수요·공급 모형 Qd = a - bP, Qs = c + dP 의 균형을 계산합니다.

options:
  -h, --help  show this help message and exit
  --a A       수요 절편 a: 가격이 0일 때의 수요량
  --b B       수요 기울기 b: 가격 1단위당 수요 감소량 (양수)
  --c C       공급 절편 c: 가격이 0일 때의 공급량
  --d D       공급 기울기 d: 가격 1단위당 공급 증가량 (양수)

--help 화면을 뜯어 보면 전부 우리가 쓴 조각들입니다. 맨 위 usage 줄은 add_argument 정의들로부터 자동으로 조립된 사용법 요약이고, 그 아래 소개문은 ArgumentParser(description=...)에 적은 문장, 옵션마다 붙은 설명은 각 add_argumenthelp=에 적은 문장입니다. 인자 규격을 성실히 선언한 것만으로 사용 설명서가 공짜로 딸려 오는 셈입니다. 이것이 argparse를 쓰는 큰 이유이고, 명령줄 프로그램이 갖춰야 할 최소한의 예의이기도 합니다.

실수 노트: 빠뜨린 필수 인자와 SystemExit

required=True로 선언한 인자를 빼먹으면 어떻게 될까요. 노트북에서 --d를 빠뜨려 %run sd_analyzer.py --a 100 --b 2 --c 10을 실행해 보면 이런 응답이 나옵니다.

usage: sd_analyzer.py [-h] --a A --b B --c C --d D
sd_analyzer.py: error: the following arguments are required: --d
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2

argparse는 규격에 맞지 않는 입력을 만나면 사용법을 짧게 보여 주고, 프로그램을 그 자리에서 멈춥니다. 이때 파이썬 내부에서 일어나는 일이 SystemExit인데, 노트북에서는 그것이 빨간 글씨로 보여 놀랄 수 있습니다. 하지만 이건 코드가 깨진 것이 아니라 필수 값이 빠졌으니 정상적으로 중단했다는 신호입니다. 마지막에서 두 번째 줄, error: 뒤 문장만 읽으면 무엇이 빠졌는지 바로 알 수 있습니다. 여기서는 --d가 빠졌다고 친절히 알려 주고 있습니다.

7.8 사이드바: 터미널이 한글을 깨뜨릴 때

프로그램의 출력에 한글을 넣으면, 노트북에서는 멀쩡하던 글자가 Windows 터미널에서 깨져 나오는 일이 있습니다. 원인은 인코딩(encoding), 곧 글자를 숫자로 바꿔 저장하는 방식이 노트북과 터미널에서 서로 다르기 때문입니다. 옛 Windows 터미널은 한글을 cp949라는 방식으로 다루는데, 요즘 파이썬 파일은 utf-8로 저장되는 것이 표준이라, 이 어긋남이 깨진 글자로 드러납니다.

임시 처방은 터미널에 chcp 65001을 한 번 쳐서 터미널을 utf-8 모드로 바꾸는 것입니다. 65001utf-8을 가리키는 번호입니다. 다만 이 설정은 창을 닫으면 풀리므로 근본 해결책은 아닙니다. 그래서 실무의 요령은 조금 다릅니다. 남에게 건네줄 명령줄 프로그램의 출력은 되도록 영문과 숫자 위주로 짜는 것입니다. 인자 이름(--a, --b)과 오류 메시지는 어차피 영문이 안전하고, 결과도 P* = 12.00처럼 기호와 숫자로 나오면 인코딩에 발목 잡힐 일이 없습니다. 화면을 꾸미는 한글 설명은 노트북이나 보고서에 맡기고, 터미널로 나가는 출력은 담백하게 두는 편이 뒤탈이 적습니다.

argparse에는 여기서 다루지 않은 기능이 더 있습니다. 값을 빠뜨렸을 때 대신 채워 주는 default=, 정해진 보기 중에서만 고르게 하는 choices=, 그리고 하나의 프로그램 아래 여러 하위 명령을 두는 서브커맨드가 그렇습니다. econo 명령 하나로 여러 분석을 골라 실행하는 이 서브커맨드 구조는 10장에서 완성합니다.

7.9 연습문제

손풀기는 본문을 따라왔다면 10분 안에 풀 수 있는 확인 문제이고, 응용부터는 파일을 직접 설계하게 됩니다. 이 장의 문제들은 .py 파일을 새로 만드는 것이 많으니, 파일 이름이 기존 파일과 겹치지 않게 주의하세요.

손풀기

1. 세 가지 import 다시 쓰기 market_models를 (1) import market_models, (2) from market_models import find_equilibrium, (3) import market_models as mm의 세 방식으로 불러오고, 각 방식으로 find_equilibrium(a=120, b=2, c=20, d=3)을 한 번씩 호출해 세 결과가 같은지 확인하세요. 호출하는 줄마다 함수 이름 앞에 무엇이 붙는지(모듈 이름, 없음, 별명)를 주석으로 달아 세 방식의 차이를 눈으로 정리하세요.

2. 이름표 예측하기 print(__name__) 한 줄만 담긴 파일 hello_name.py%%writefile로 만드세요. (1) %run hello_name.py를 실행할 때와 (2) import hello_name을 실행할 때 각각 무엇이 출력될지 먼저 글로 예측해 적고, 그런 다음 실제로 실행해 예측과 맞는지 견줘 보세요.

3. 터미널 세션 읽기 아래 PowerShell 세션의 네 명령이 각각 무슨 일을 하는지 한 줄씩 설명하세요. 특히 마지막 명령이 왜 두 줄을 출력하는지, 이 장에서 배운 __name__ 개념으로 설명하세요.

PS C:\Users\hana> cd econ_python
PS C:\Users\hana\econ_python> dir
(econ_python 폴더의 파일 목록이 출력됨)
PS C:\Users\hana\econ_python> python --version
Python 3.12.4
PS C:\Users\hana\econ_python> python market_models.py
market_models 자체 테스트를 시작합니다.
기본 시장 균형: P* = 18.00, Q* = 64.00

응용

4. my_utils 모듈 완성하기 본문 [직접 해보기]에서 시작한 my_utils.py를 확장하세요. 요구 사항은 셋입니다. (1) format_won(amount) 함수로 1234567을 "1,234,567원"으로 바꾸기, (2) 부가세를 더한 가격을 돌려주는 with_vat(price, rate=0.1) 함수, (3) 두 함수를 시험하는 if __name__ == "__main__": 테스트 블록. 완성한 뒤 import해서, 이콘 카페 아메리카노 1,800원의 부가세 포함 가격을 format_won으로 감싸 출력하세요.

5. 탄력성 모듈을 패키지에 입주시키기 econo_analyzer/analysis/elasticity.py를 새로 만들고, 5장에서 만든 중간점 공식 탄력성 함수 midpoint_elasticity(p1, q1, p2, q2)를 그 안에 넣으세요. from econo_analyzer.analysis import elasticity로 불러온 뒤, 본문 방학 시나리오의 두 균형점 (18, 64)와 (12, 46)을 넣어 탄력성을 계산하세요. 이 두 점은 이동하지 않은 공급 곡선 위의 점들입니다. 그렇다면 지금 계산한 값은 무엇의 탄력성인지 한 문장으로 답하세요.

6. –verbose 옵션 설계하기 sd_analyzer.py를 복사해 sd_analyzer2.py를 만들고 --verbose 옵션을 더하세요. 힌트: parser.add_argument("--verbose", action="store_true", help="계산 과정을 자세히 출력")은 옵션을 붙이면 args.verboseTrue, 안 붙이면 False가 되는 스위치를 만듭니다. args.verbose가 참일 때만 균형 공식 \(P^* = (a - c) / (b + d)\)에 숫자가 대입되는 과정을 단계별로 출력하게 하고, %run으로 옵션을 켠 경우와 끈 경우의 출력을 비교하세요.

도전

7. 수요 이동 CLI --a, --b, --c, --d에 더해 이동 후의 수요 절편을 받는 --new-a 옵션까지 다섯 인자를 받아, 이동 전후의 균형과 변화 방향을 출력하는 sd_shift.py를 만드세요. 균형 비교 논리는 직접 짜지 말고 econo_analyzer.analysis.shiftscompare_equilibria를 import해서 재사용해야 합니다. 한 가지 함정이 있습니다. 옵션 이름의 붙임표(--new-a)는 파이썬 변수 이름이 될 수 없어서, argparse가 밑줄로 바꿔 줍니다. 그래서 값은 args.new_a로 꺼냅니다. 완성하면 %run sd_shift.py --a 100 --b 2 --c 10 --d 3 --new-a 70의 출력이 본문의 방학 분석과 일치하는지 확인하세요.

풀이는 부록 C에 있습니다.

7.10 미니 프로젝트: 생각을 코드로, 코드를 글로

과제: 나만의 경제 함수 상자 my_econ.py (30~60분)

3장부터 6장까지 여러분이 만든 함수 가운데 다섯 개를 골라 my_econ.py 한 파일로 정리하고, 그 모듈을 import해서 쓰는 데모를 만드세요. 후보를 몇 개 떠올려 보면, 누진세 계산기(3장), 복리 원리금(4장), 중간점 탄력성(5장), 장바구니 물가지수(5장), 하루 매출과 총비용(5장), 균형 탐색(6장)이 있습니다. 다른 함수를 골라도 됩니다. 중요한 것은 “왜 하필 이 다섯인가”라는 질문에 답할 수 있어야 한다는 점입니다. 모듈에 무엇을 넣고 무엇을 뺄지 정하는 일은 코딩이라기보다 편집에 가깝고, 이번 과제의 진짜 주제는 그 편집 기준입니다.

요구 사항

  1. 다섯 함수 모두 docstring을 달고, 그중 하나 이상에는 잘못된 입력을 거절하는 raise ValueError 검증을 넣으세요.
  2. 파일 끝에 if __name__ == "__main__": 테스트 블록을 두고, %run my_econ.py로 테스트가 도는 것을 보이세요.
  3. 데모에서는 다섯 함수를 전부 한 번 이상 호출하고, 그 결과로 표(예: 소득 구간별 세액을 print로 정렬해 출력) 또는 그래프를 1개 이상 만드세요.

글쓰기: 세 문단

데모 아래에 마크다운 셀로 세 문단을 쓰세요.

  • 1문단(주장): 다섯 함수를 고른 기준을 한 문장의 주장으로 세우고, 그 기준을 설명합니다. 자주 재사용되어서인지, 여러 시장에 두루 통해서인지, 서로 조합되어 더 큰 분석을 만들어서인지.
  • 2문단(근거): 데모의 수치와 표·그래프를 직접 인용하며 그 기준이 실제로 드러난 장면을 보여 줍니다. “탄력성 함수는 아메리카노와 도시락 두 시장에 수정 없이 재사용되었다”처럼, 구체적인 숫자와 함께 쓰세요.
  • 3문단(한계와 확장): 함수가 스무 개로 늘면 my_econ.py 한 파일로 버틸 수 있을까요? 이 장에서 배운 패키지 구조를 빌려, 어떤 기준으로 모듈을 쪼갤지 계획을 적으세요.

자기 점검 체크리스트

7.11 요약과 다음 장 예고

이번 장의 주제는 새로운 계산이 아니라 계산을 보관하는 법이었습니다. 노트북 셀에 살던 함수들은 %%writefile을 거쳐 .py 파일이 되었고, 그 순간부터 import 한 줄로 어느 노트북에서든 불러 쓸 수 있는 모듈이 되었습니다. import에는 전체를 불러오는 방식, 일부만 꺼내는 방식, 별명을 붙이는 방식이 있으며, 어느 쪽이든 파이썬이 파일을 찾으려면 내 노트북과 .py 파일이 같은 작업 디렉터리에 있어야 한다는 전제가 맞아야 했습니다. 모듈이 여럿으로 불어나자 폴더마다 __init__.py를 세워 econo_analyzer 패키지로 묶었고, 모형과 분석과 그림을 각자의 서랍에 나눠 담았습니다. 파일 하나가 import될 때는 조용한 부품으로, 직접 실행될 때는 자기 테스트를 돌리는 프로그램으로 행동하게 만드는 스위치가 if __name__ == "__main__":이었습니다. 터미널은 그 파일들이 실행되는 작업대였고, cddirpython --version 세 명령이면 첫발을 딛기에 충분했습니다. 마지막으로 argparse는 이 도구 상자를 파이썬을 모르는 사람에게까지 열어 주었습니다. 숫자 네 개를 건네면 균형이 나오고, --help라는 사용 설명서까지 딸려 오는 프로그램으로 말입니다.

다음 장에서는 이 도구 상자에서 그림 도구를 꺼내 본격적으로 갈고닦습니다. 지금까지의 그래프가 곡선이 그려지는 데 만족하는 수준이었다면, 8장에서는 소비자·생산자 잉여를 색으로 칠하고, 균형점에 주석을 달고, 로렌츠 곡선으로 소득 불평등을 그리는 경제학자의 그래프를 만듭니다. 오늘 utils/plotting.py에 넣어 둔 함수들이 거기서 다시 자라날 것입니다.