15. 모듈과 패키지

🎯 이 장의 목표
  • 코드를 여러 파일로 나누고 import로 불러온다.
  • import의 여러 형태(import, from import, as)를 구분한다.
  • 표준 라이브러리의 유용한 모듈을 맛본다.
  • 패키지(폴더 단위)와 if __name__ == "__main__"을 이해한다.

먼저: 모듈이 뭔가요?

지금까지 코드를 한 파일에 다 적었습니다. 하지만 프로그램이 커지면 한 파일이 수천 줄이 되어 관리가 불가능해집니다. 그래서 코드를 여러 파일로 나눕니다.

모듈(module)은 그냥 하나의 .py 파일입니다. 함수·변수·클래스를 담아두고, 다른 파일에서 import로 불러와 씁니다.

사실 우리는 이미 모듈을 써왔습니다. 초급편에서 decimal 모듈을 언급했고, random으로 난수를 만든다는 이야기도 나왔죠. 이들은 Python이 기본 제공하는 모듈입니다. 이제 직접 모듈을 만들고 불러옵니다.

mymath.py라는 파일을 만들어 봅시다.

PYTHON
# mymath.py
PI = 3.14159

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

def circle_area(r):
    return PI * r * r

같은 폴더의 다른 파일(main.py)에서 이를 불러옵니다.

PYTHON
# main.py
import mymath

print(mymath.add(3, 5))        # 8
print(mymath.PI)               # 3.14159
print(mymath.circle_area(2))   # 12.56636

import mymath로 파일을 통째로 가져오고, mymath.add()처럼 모듈이름.기능 형태로 씁니다.

import 할 때 무슨 일이 일어날까

import mymath를 만나면 Python은 세 가지를 합니다.

  1. mymath.py 파일을 찾아 한 번 처음부터 끝까지 실행합니다(그 안의 def·변수 정의가 메모리에 올라감).
  2. 그 결과를 mymath라는 이름표에 담습니다.
  3. 이후 mymath.add처럼 점으로 그 안의 것에 접근하게 해줍니다.

여기서 중요한 점 두 가지가 있습니다.

  • 한 번만 실행됩니다. 같은 모듈을 여러 곳에서 여러 번 import해도, 파일은 처음 한 번만 실행되고 그 결과가 재사용됩니다. 그래서 import는 빠르고, 모듈 안 코드가 매번 반복 실행될까 걱정할 필요가 없습니다.
  • 이름 공간(namespace)이 분리됩니다. mymath.PI와 내 파일의 PI는 서로 다른 것입니다. mymath. 라는 "성(姓)"이 붙어 있어, 이름이 겹쳐도 충돌하지 않습니다. 이것이 import 모듈 방식이 안전한 이유입니다.
💡 팁
"한 번만 실행된다"는 성질 덕분에, 모듈 맨 위에 print("로딩됨")을 넣어두면 그 모듈이 처음 import될 때 딱 한 번만 출력됩니다. (이 특성은 뒤에 나올 if __name__ == "__main__"과 연결됩니다.)
📌 핵심
핵심: 모듈은 함수·변수·클래스를 담은 .py 파일이다. import로 불러와 코드를 재사용하고 파일을 깔끔하게 나눈다. import된 모듈은 한 번만 실행되며, 모듈.이름으로 접근해 이름 충돌을 피한다.
flowchart LR
    Main["main.py"]:::proc -->|import| MyMath["mymath.py<br/>add, PI, circle_area"]:::data
    Main -->|import| Random["random<br/>(표준 라이브러리)"]:::data
    Main -->|import| Math["math<br/>(표준 라이브러리)"]:::data

    classDef proc fill:#7fd8d8,stroke:#2a9d8f,color:#14532d
    classDef data fill:#a8dadc,stroke:#457b9d,color:#1d3557

import의 여러 형태

불러오는 방법은 세 가지가 있습니다. 상황에 맞게 고릅니다.

1) import 모듈 — 통째로

PYTHON
import mymath
print(mymath.add(3, 5))     # 항상 mymath. 을 붙여 씀

가장 안전합니다. mymath.add처럼 어디서 왔는지 분명히 드러납니다.

2) from 모듈 import 이름 — 골라서

특정 기능만 콕 집어 가져오면, 모듈 이름 없이 바로 쓸 수 있습니다.

PYTHON
from mymath import circle_area, PI
print(circle_area(2))       # mymath. 없이 바로
print(PI)                   # 3.14159

3) import 모듈 as 별명 — 별명 붙이기

긴 모듈 이름을 짧게 줄일 때 씁니다. 데이터 분석에서 import pandas as pd가 대표적입니다.

PYTHON
import mymath as mm
print(mm.add(10, 20))       # 30
형태사용언제
import 모듈모듈.기능()기본. 출처가 분명
from 모듈 import 기능기능()자주 쓰는 몇 개만
import 모듈 as 별명별명.기능()이름이 길 때
⚠️ 흔한 실수
흔한 함정 — from 모듈 import *: 별표로 모든 것을 가져오는 방식은 피하세요.
```python
from mymath import * # ❌ 비권장
```
무엇이 들어왔는지 알 수 없어 이름이 충돌하거나 코드를 읽기 어려워집니다. 필요한 것을 명시적으로 가져오는 게 파이썬답습니다.

표준 라이브러리 맛보기

Python에는 표준 라이브러리(standard library)라는 방대한 모듈 모음이 기본 포함돼 있습니다. 설치 없이 import만 하면 바로 씁니다. "건전지 포함(batteries included)"이라는 Python의 철학이죠.

자주 쓰는 몇 가지를 봅시다.

PYTHON
import math
print(math.sqrt(16))        # 4.0    (제곱근)
print(math.pi)              # 3.141592653589793
print(math.ceil(4.1))       # 5      (올림)

import random
print(random.randint(1, 6))         # 1~6 주사위
print(random.choice(["가위","바위","보"]))   # 무작위 선택

from datetime import date
print(date.today())                 # 오늘 날짜
print(date(2025, 12, 31))           # 2025-12-31

from collections import Counter
print(Counter("banana"))    # Counter({'a': 3, 'n': 2, 'b': 1})  빈도 세기
모듈무엇에 쓰나대표 기능
math수학 계산sqrt, pi, ceil, floor
random난수·무작위randint, choice, shuffle
datetime날짜·시간date.today(), datetime.now()
collections특수 자료구조Counter, defaultdict, deque
jsonJSON 처리load, dump (16장)
csvCSV 처리reader, writer (16장)
os·pathlib파일 경로·시스템경로 조작, 폴더 탐색
💡 팁
무언가 직접 만들기 전에 "표준 라이브러리에 이미 있지 않을까?"를 먼저 떠올리세요. 빈도 세기(Counter), 날짜 계산(datetime) 등은 직접 짜는 것보다 표준 모듈이 정확하고 빠릅니다. 전체 목록은 docs.python.org에서 볼 수 있습니다.

패키지: 모듈을 폴더로 묶기

모듈(파일)이 많아지면 패키지(package)로 묶습니다. 패키지는 관련 모듈들을 담은 폴더입니다. 라이브러리가 커지면 보통 이 구조를 띱니다.

TEXT
shop/                  ← 패키지(폴더)
├── __init__.py        ← "이 폴더는 패키지다"를 표시
├── cart.py            ← 모듈
└── discount.py        ← 모듈
PYTHON
# shop/cart.py
def total(prices):
    return sum(prices)

# shop/discount.py
def apply(price, rate):
    return price * (1 - rate)

다른 파일에서 점(.)으로 패키지 안 모듈에 접근합니다.

PYTHON
from shop.cart import total
from shop import discount

print(total([1000, 2000, 3000]))    # 6000
print(discount.apply(10000, 0.2))    # 8000.0
📎 __init__.py는 "이 폴더를 패키지로 취급하라"는 표시 파일입니다. 비어 있어도 됩니다. (최신 Python은 없어도 동작하는 경우가 있지만, 명시적으로 두는 것이 안전하고 표준적입니다.)

⭐ if __name__ == "__main__"

모듈을 만들다 보면 이상한 코드를 자주 봅니다.

PYTHON
if __name__ == "__main__":
    ...

이게 무슨 뜻일까요? __name__은 Python이 자동으로 설정하는 특별한 변수입니다.

  • 파일을 직접 실행하면 (python mymath.py) → __name__"__main__"
  • 파일을 다른 곳에서 import하면 → __name__은 모듈 이름("mymath")

이 차이를 이용해, "직접 실행할 때만 돌릴 코드"를 구분합니다.

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

if __name__ == "__main__":      # 직접 실행할 때만 실행됨
    print("테스트:", add(2, 3))  # import 할 땐 실행 안 됨
PYTHON
# 직접 실행
$ python mymath.py
테스트: 5

# 다른 파일에서 import — 위 print는 실행되지 않음
import mymath               # 아무것도 출력 안 됨 (add 함수만 가져옴)
📌 핵심
핵심: if __name__ == "__main__": 안의 코드는 그 파일을 직접 실행할 때만 돌고, import될 때는 무시된다. 모듈에 테스트나 실행 코드를 넣되 import 시 방해하지 않게 하는 표준 관용구다.

이 패턴이 중요한 이유: 모듈을 import하는 쪽에서는 그 모듈의 함수만 쓰고 싶지, 그 안의 실행 코드가 멋대로 돌아가는 건 원치 않기 때문입니다.

나쁜 예 ❌ vs 좋은 예 ✅

PYTHON
# ❌ 나쁜 예: 한 파일에 모든 것 (수천 줄, 관리 불가)
# everything.py 에 함수 200개, 클래스 50개...

# ✅ 좋은 예: 역할별로 모듈 분리
# models.py      ← 데이터 클래스들
# utils.py       ← 보조 함수들
# main.py        ← from models import User; from utils import format_date
💡 팁
"한 파일은 한 가지 책임"을 기준으로 나누세요. 관련된 것끼리 모듈로 묶으면 어디에 무엇이 있는지 찾기 쉽고, 협업과 테스트가 쉬워집니다.

이 장에서 배운 것

  • 모듈은 함수·클래스를 담은 .py 파일이다. import로 불러와 재사용한다.
  • import 형태 셋: import 모듈(출처 분명), from 모듈 import 기능(골라서), import 모듈 as 별명(이름 단축). import *는 피한다.
  • 표준 라이브러리(math·random·datetime·collections 등)는 설치 없이 바로 쓴다. 직접 만들기 전에 먼저 찾아본다.
  • 패키지는 모듈을 담은 폴더이며 __init__.py로 표시한다. 점(.)으로 내부 모듈에 접근한다.
  • if __name__ == "__main__":은 파일을 직접 실행할 때만 도는 코드를 구분한다.

🧪 실습 문제

문제 1. greetings.py 모듈에 hello(name) 함수("안녕, {name}!" 반환)를 만들고, 다른 파일에서 from greetings import hello로 불러와 hello("민지")를 출력하는 코드를 쓰세요. (코드만 작성)

문제 2. import math를 이용해 144의 제곱근과, 7을 올림한 값(math.ceil)을 출력하세요.

문제 3. 다음 중 __name__"__main__"이 되는 경우는?

TEXT
(a) python mymodule.py 로 직접 실행할 때
(b) 다른 파일에서 import mymodule 할 때

문제 4. random 모듈로 1부터 45 사이의 서로 다른 로또 번호 6개를 뽑아 정렬해 출력하세요. (힌트: random.sample(범위, 개수))

문제 5. from collections import Counter로, 문자열 "mississippi"에서 각 글자의 빈도를 출력하세요.

<details>

<summary>✅ 정답·해설 보기</summary>

1.

PYTHON
# greetings.py
def hello(name):
    return f"안녕, {name}!"

# main.py
from greetings import hello
print(hello("민지"))   # 안녕, 민지!

2.

PYTHON
import math
print(math.sqrt(144))   # 12.0
print(math.ceil(7))     # 7  (이미 정수라 그대로; math.ceil(7.1)이면 8)

3. (a)만. 직접 실행하면 "__main__", import하면 모듈 이름("mymodule")이 됩니다.

4.

PYTHON
import random
numbers = sorted(random.sample(range(1, 46), 6))
print(numbers)   # 예: [3, 12, 19, 27, 33, 41] (실행마다 다름)

(random.sample은 중복 없이 뽑아줍니다.)

5.

PYTHON
from collections import Counter
print(Counter("mississippi"))
# Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})

</details>

◀️ 이전 장: 14. 이터레이터와 제너레이터 | ▶️ 다음 장: 16. 파일 입출력과 데이터 형식