C-3: 모듈과 패키지 — 코드를 나누고 가져다 쓰기
목차 29
안녕하세요, 홍순구 튜터입니다. 지난 시간엔 함수를 값처럼 다루는 함수형 도구들을 익혔어요. 람다로 한 줄 함수를 만들고, map·filter·sorted(key=) 로 데이터를 변환하고 거르고 정렬했죠. 카테고리 C 의 두 번째 산을 멋지게 넘었습니다.
그런데 지난 시간 마지막에 from functools import reduce 라고 썼던 거, 기억나세요? functools 라는 어딘가에서 reduce 를 가져온다는 뜻이었어요. 오늘은 바로 그 가져오는 문법, import 를 제대로 배웁니다.
지금까지 우리는 코드를 파일 하나에 다 몰아 썼어요. 짧을 땐 괜찮지만, 코드가 길어지면 한 파일이 수백 줄로 불어나 찾기도 고치기도 힘들어져요. 그래서 코드를 주제별로 여러 파일(모듈)에 나눠 담고, 필요할 때 import 로 꺼내 씁니다. 오늘 배울 "잘 나누고 가져다 쓰는" 정리정돈의 기술이에요.
지난 시간 (C-2) 오늘 배울 것 (C-3)
────────────── ──────────────────
함수를 값처럼 다뤘다 ──> 코드를 여러 파일로 나누기
│
├─ import / from ... import
├─ 표준 라이브러리 둘러보기
├─ if __name__ == "__main__"
├─ 패키지 — __init__.py
└─ uv run -m 으로 실행
낯선 기호(__)가 좀 보여도 괜찮아요. 하나씩 직접 실행해 보면서 "아, 이렇게 나뉘는구나" 를 눈으로 확인하며 갈게요. 출발해 봅시다!
💡 오늘 수업의 핵심 — "코드는 모듈로 나누고, import 로 가져다 쓴다"
🎯 학습 목표
import와from ... import로 다른 파일(모듈)의 함수를 가져다 쓸 수 있어요- 파이썬이 기본 제공하는 표준 라이브러리를 둘러보고,
if __name__ == "__main__"으로 "직접 실행" 과 "import" 를 구분해요 - 여러 모듈을 패키지로 묶고,
uv run으로 패키지를 실행할 수 있어요
Step 1: "코드를 여러 파일로 — 첫 import"
먼저 가장 기본부터 잡고 갈게요. 다른 파일에 있는 함수를 어떻게 가져다 쓸까요? 파이썬에서는 .py 파일 하나하나가 곧 모듈(module) 이에요. 모듈은 "함수와 값을 담아둔 도구 상자" 라고 생각하면 편해요.
먼저 가져다 쓸 도구 상자부터 하나 만들어 봅시다. 인사말과 해시태그를 만드는 함수 두 개를 담은 파일이에요.
# src/c_functions/c3_greetings.py
"""인사·해시태그 만들기 함수를 모아둔 모듈."""
def make_hashtag(word):
"""단어 하나를 해시태그로 바꾼다. 예: "여행" -> "#여행"."""
return "#" + word.strip().replace(" ", "")
def greet(name):
"""사용자에게 보낼 인사말을 만든다."""
return f"안녕하세요 @{name}님!"
이제 이 도구 상자를 다른 파일에서 꺼내 써볼게요. import 모듈이름 이라고 적으면 그 모듈을 통째로 가져와요. 단, 가져온 함수를 쓸 땐 모듈이름.함수이름() 처럼 앞에 모듈 이름을 점(.)과 함께 붙여요.
# src/c_functions/c3_use_import.py
import c3_greetings
print(c3_greetings.greet("민지"))
# 안녕하세요 @민지님!
print(c3_greetings.make_hashtag(" 제주 여행 "))
# #제주여행
import c3_greetings 한 줄로 옆 파일(c3_greetings.py)을 통째로 가져왔어요. .py 는 빼고 파일 이름만 적는다는 점에 주의하세요. 그리고 그 안의 함수는 c3_greetings.greet(...) 처럼 "어느 상자에서 꺼낸 도구인지" 를 점으로 밝혀줘요.
c3_use_import.py c3_greetings.py
──────────────── ───────────────
import c3_greetings ──────> def make_hashtag(...)
c3_greetings.greet(...) def greet(...)
직접 실행해 볼까요? 터미널에서 이렇게 칩니다.
uv run python src/c_functions/c3_use_import.py
@민지님! 인사말과 #제주여행 해시태그가 차례로 찍히면 성공이에요. 옆 파일의 함수를 그대로 빌려 쓴 거죠.
💡 한 줄 정리
.py 파일 하나가 모듈이고, import 모듈 로 통째로 가져온 뒤 모듈.함수() 로 꺼내 쓴다.
🙋 학생 질문 — "튜터님, 왜 굳이 코드를 여러 파일로 나눠요? 한 파일에 다 쓰면 편하잖아요?"
지금처럼 함수가 두세 개일 땐 한 파일이 편해요. 그런데 인사·해시태그·정렬·통계… 기능이 수십 개로 늘면 한 파일이 수백 줄이 돼요. 그 안에서 고치고 싶은 함수 하나 찾는 것도 일이 되죠.
주제별로 파일을 나눠두면, "해시태그 관련은 formatting.py 에 있겠지" 하고 바로 찾아가요. 여러 명이 함께 일할 때도 서로 다른 파일을 건드려서 충돌이 줄어들어요. 코드가 커질수록 "잘 나눠둔 것" 의 가치가 커집니다.
Step 2: "from ... import 와 별칭 as"
Step 1 처럼 import c3_greetings 로 가져오면, 함수를 쓸 때마다 c3_greetings.greet(...) 처럼 모듈 이름을 매번 붙여야 했어요. 좀 길죠? 자주 쓰는 함수라면 이름만 콕 집어 가져올 수 있어요. 바로 from ... import 예요.
# src/c_functions/c3_from_import.py
from c3_greetings import make_hashtag, greet
import c3_greetings as g
print(make_hashtag("맛집 탐방"))
# #맛집탐방
print(greet("승우"))
# 안녕하세요 @승우님!
print(g.make_hashtag("일상"))
# #일상
첫 줄 from c3_greetings import make_hashtag, greet 는 그 모듈에서 두 함수만 콕 집어 가져와요. 이렇게 하면 모듈 이름 없이 바로 make_hashtag(...) 라고 쓸 수 있어요. 쉼표로 여러 개를 한 번에 가져올 수도 있죠.
둘째 줄 import c3_greetings as g 는 모듈에 g 라는 짧은 별명을 붙여요. 이제 g.make_hashtag(...) 처럼 짧게 쓸 수 있죠. 이름이 길거나 다른 이름과 겹칠 때 as 로 별칭을 줘요.
두 방식을 한자리에 놓고 비교해 볼게요. 둘 다 직접 써봤으니 이제 차이가 눈에 들어올 거예요.
import 모듈 from 모듈 import 이름
────────── ────────────────────
모듈.함수() 로 호출 함수() 로 바로 호출
어디서 왔는지 명확 짧고 간결
이름 충돌 걱정 적음 같은 이름이 있으면 덮어쓸 위험
⚠️ from 모듈 import * 라고 별표를 쓰면 그 모듈의 모든 이름을 한꺼번에 가져와요. 편해 보이지만, 무엇이 들어왔는지 알 수 없어서 이름이 겹쳐도 눈치채기 어려워요. 입문 단계에선 쓰지 않는 걸 권해요. 필요한 것만 이름으로 콕 집는 게 안전합니다.
💡 한 줄 정리
from 모듈 import 이름 은 함수만 콕 집어 바로 쓰고, import 모듈 as 별칭 은 짧은 별명을 붙인다. import * 는 피한다.
🙋 학생 질문 — "튜터님, 그럼 항상 from import 로 짧게 쓰는 게 좋은 거 아니에요?"
꼭 그렇진 않아요. from 모듈 import 이름 은 짧아서 좋지만, 코드만 봐서는 그 함수가 어느 모듈에서 왔는지 한눈에 안 보여요. 반대로 모듈.함수() 는 길어도 출처가 분명하죠.
그래서 보통은 이렇게 나눠 써요. 자주 쓰는 함수 몇 개는 from ... import 로 가져오고, 출처를 분명히 하고 싶거나 이름이 겹칠 위험이 있으면 import 모듈 로 점을 붙여 써요. 정답은 없고, 읽는 사람이 헷갈리지 않는 쪽을 고르면 됩니다.
Step 3: "배터리 포함 — 표준 라이브러리 둘러보기"
지금까지는 우리가 만든 모듈을 가져왔어요. 그런데 파이썬에는 설치도 필요 없이 바로 import 해서 쓰는 도구 상자가 아주 많이 딸려 와요. 이걸 표준 라이브러리(standard library) 라고 해요. 파이썬을 두고 "배터리 포함(batteries included)" 이라고 부르는 이유예요. 사자마자 건전지가 들어 있는 장난감처럼요.
몇 개만 맛보기로 꺼내 볼게요.
# src/c_functions/c3_stdlib_tour.py
import math
import random
print(math.sqrt(144))
# 12.0
print(round(math.pi, 2))
# 3.14
random.seed(42)
candidates = ["민지", "재훈", "승우"]
print(random.choice(candidates))
# 승우 (시드 42 를 고정해서 매번 같은 결과가 나와요)
import math 로 수학 도구 상자를 가져와 제곱근(sqrt)과 원주율(pi)을 썼어요. import random 으로는 무작위 도구 상자를 가져와 리스트에서 하나를 골랐죠(choice). 우리가 만든 모듈을 가져올 때와 문법이 똑같아요. import 이름 하고 이름.함수() 로 쓰면 끝이에요.
여기서 random.seed(42) 가 눈에 띄죠? 무작위는 원래 매번 다른 결과가 나오는데, 씨앗(seed)을 같은 숫자로 고정하면 실행할 때마다 같은 결과가 나와요. 그래서 예제에서 결과를 똑같이 재현할 수 있어요.
중요한 건, 표준 라이브러리는 uv add 같은 설치가 전혀 필요 없다는 점이에요. 파이썬에 이미 들어 있으니 그냥 import 만 하면 돼요. 오늘은 "이런 창고가 있다" 정도만 맛보고, 자주 쓰는 표준 라이브러리(날짜·빈도 세기 등)는 이 과목 뒤쪽에서 더 깊이 다룰게요.
💡 한 줄 정리
파이썬엔 설치 없이 import 만으로 쓰는 표준 라이브러리가 잔뜩 딸려 온다. math·random 처럼.
🙋 학생 질문 — "튜터님, 표준 라이브러리는 외부에서 받는 패키지랑 뭐가 달라요?"
표준 라이브러리는 파이썬을 설치하면 이미 함께 들어 있는 도구예요. math·random 처럼 import 만 하면 바로 써요. 따로 받을 필요가 없죠.
반대로 세상에는 파이썬에 기본 포함되지 않은 도구도 아주 많아요. 그런 건 지난 시간에 잠깐 본 uv add 같은 명령으로 인터넷에서 받아 와야 써요. 지금 단계에선 "기본 제공되는 표준 라이브러리만으로도 할 수 있는 게 많다" 만 기억해 두면 충분해요.
Step 4: "import 하면 무슨 일이 일어날까"
여기서 살짝 깊이 들어가 볼게요. import 를 하면 파이썬은 그 모듈 파일을 위에서 아래로 한 번 통째로 실행 해요. 함수 정의뿐 아니라, 파일에 그냥 적혀 있는 print 같은 문장도 그때 실행돼요. 눈으로 확인해 봅시다.
# src/c_functions/c3_loading.py
print("c3_loading 모듈이 로딩됐어요!")
WELCOME = "인스타그램에 오신 걸 환영해요"
def hello():
return WELCOME
이 모듈의 맨 위엔 print 문장이 하나 있어요. 그럼 이걸 두 번 import 하면 "로딩됐어요!" 가 두 번 찍힐까요? 직접 해봅시다.
# src/c_functions/c3_import_twice.py
import c3_loading
import c3_loading # 두 번째 import — 모듈은 다시 로딩되지 않아요
print(c3_loading.hello())
c3_loading 모듈이 로딩됐어요!
인스타그램에 오신 걸 환영해요
import c3_loading 을 두 번 적었는데도 "로딩됐어요!" 는 딱 한 번만 찍혔어요. 파이썬은 한 번 가져온 모듈을 기억해 뒀다가, 다음에 또 import 하면 실행하지 않고 기억해 둔 걸 그대로 줘요. 책을 책장에 한 번 꽂아두면, 다음부터는 다시 사 오지 않고 꽂힌 책을 펴보는 것과 같아요.
그런데 여기서 한 가지 신경 쓰이는 게 있어요. 모듈을 import 하면 맨 위의 print 같은 문장이 무조건 실행된다는 거예요. 만약 모듈에 테스트용으로 적어둔 실행 코드가 있다면, 그걸 가져다 쓸 때마다 원치 않게 같이 돌아버려요. 이 문제를 다음 Step 에서 깔끔하게 해결합니다.
💡 한 줄 정리
import 하면 그 모듈이 한 번 통째로 실행되고, 두 번째부터는 기억해 둔 걸 그대로 쓴다(다시 실행 안 함).
🙋 학생 질문 — "튜터님, 모듈이 한 번만 실행되는 게 왜 좋은 거예요?"
만약 import 할 때마다 모듈이 매번 처음부터 다시 실행된다고 생각해 보세요. 여러 파일이 같은 모듈을 가져다 쓰면, 그 무거운 준비 작업이 가져올 때마다 반복돼서 느려지겠죠.
파이썬은 한 번 실행한 모듈을 기억해 두기 때문에, 몇 군데서 가져다 써도 준비는 딱 한 번만 일어나요. 빠르고, 모듈 안의 값도 모두가 같은 하나를 공유하게 돼서 일관성도 지켜집니다.
Step 5: "if __name__ == \"__main__\" — 직접 실행과 import 구분"
Step 4 에서 본 문제를 풀어 봅시다. 모듈을 import 하면 맨 위 문장들이 다 실행된다고 했죠. 그럼 "이 파일을 직접 실행할 때만 돌리고, 남이 import 할 땐 안 돌릴 코드" 는 어떻게 구분할까요?
파이썬은 모든 파일에 __name__ 이라는 특별한 값을 자동으로 넣어줘요. 이 값을 한번 찍어 봅시다.
# src/c_functions/c3_name_demo.py
print("이 파일의 __name__ 은:", __name__)
이 파일을 직접 실행 하면 이렇게 나와요.
uv run python src/c_functions/c3_name_demo.py
이 파일의 __name__ 은: __main__
그런데 이 파일을 다른 파일에서 import 하면 __name__ 이 모듈 이름(c3_name_demo)으로 바뀌어요. 즉 __name__ 만 보면 "내가 지금 직접 실행된 건지, 남에게 import 된 건지" 를 알 수 있어요. 직접 실행이면 "__main__", import 면 그 모듈의 이름이에요.
이걸 이용한 게 그 유명한 if __name__ == "__main__": 가드예요.
# src/c_functions/c3_guarded.py
def make_caption(text):
return text.strip() + " #인스타"
# 이 파일을 직접 실행할 때만 아래가 돌아가요.
if __name__ == "__main__":
print("직접 실행 테스트:")
print(make_caption(" 오늘의 사진 "))
이 파일을 직접 실행하면 가드 아래 코드가 돌아가요.
직접 실행 테스트:
오늘의 사진 #인스타
하지만 다른 파일이 이 모듈에서 make_caption 만 import 하면, 가드 아래 print 들은 실행되지 않아요. __name__ 이 "__main__" 이 아니니까요. 덕분에 함수는 깔끔하게 빌려주면서, 테스트용 실행 코드는 직접 켰을 때만 돌릴 수 있어요. "이 파일을 직접 켰을 때만 작동하는 스위치" 라고 생각하면 돼요.
💡 한 줄 정리
__name__ 은 직접 실행이면 "__main__", import 면 모듈 이름이다. if __name__ == "__main__": 아래엔 직접 실행할 때만 돌릴 코드를 둔다.
🙋 학생 질문 — "튜터님, __main__ 앞뒤의 밑줄 두 개(__)는 뭐예요?"
밑줄 두 개로 감싼 이름(__name__·__main__)은 파이썬이 특별한 용도로 미리 정해둔 약속이에요. 영어로 "던더(dunder, double underscore)" 라고 부르기도 해요.
우리가 직접 만드는 변수에는 이런 모양을 쓰지 않아요. "아, 이건 파이썬이 알아서 채워주는 특별한 값이구나" 정도로 받아들이면 돼요. 지금은 if __name__ == "__main__": 라는 패턴 자체를 익히는 게 더 중요합니다.
Step 6: "여러 모듈을 패키지로 — __init__.py"
모듈(파일)이 한두 개일 땐 폴더 없이 쭉 둬도 돼요. 그런데 모듈이 많아지면 주제별로 폴더에 묶고 싶어지죠. 이렇게 모듈들을 담은 폴더를 패키지(package) 라고 해요. 모듈이 파일이라면, 패키지는 그 파일들을 담은 서랍장이에요.
폴더를 패키지로 만들려면 그 안에 __init__.py 라는 파일을 하나 둬요. 이름이 좀 특이하지만, "이 폴더는 패키지예요" 라고 파이썬에게 알려주는 표시라고 보면 돼요. 비어 있어도 괜찮아요.
인스타 도메인 함수를 담을 작은 패키지를 만들어 봅시다. 이런 구조예요.
src/c_functions/
└── insta/ ← 패키지 폴더
├── __init__.py ← 이 폴더가 패키지라는 표시
├── formatting.py ← 캡션·해시태그 포맷 함수
└── ranking.py ← 정렬·통계 함수
__init__.py 엔 패키지 설명만 한 줄 적어 둘게요.
# src/c_functions/insta/__init__.py
"""인스타 도메인 함수를 모아둔 패키지 (C-3 예제)."""
포맷 함수들은 formatting.py 에 모아요. 지난 시간까지 여기저기서 쓰던 캡션 정리·해시태그 만들기를 한 모듈로 묶은 거예요.
# src/c_functions/insta/formatting.py
"""캡션·해시태그 포맷 함수."""
def clean_caption(text):
"""앞뒤 공백을 정리한 캡션을 돌려준다."""
return text.strip()
def to_hashtag(word):
"""단어를 해시태그로 바꾼다."""
return "#" + word.strip().replace(" ", "")
def hashtags_line(words):
"""여러 단어를 해시태그 한 줄로 잇는다."""
return " ".join(to_hashtag(w) for w in words)
정렬·통계 함수는 ranking.py 에 따로 모아요. 좋아요순 정렬은 지난 시간에 배운 sorted(key=...) 를 그대로 썼어요.
# src/c_functions/insta/ranking.py
"""게시물 정렬·통계 함수."""
def top_by_likes(posts):
"""좋아요 많은 순으로 정렬한 새 리스트를 돌려준다."""
return sorted(posts, key=lambda p: p["likes"], reverse=True)
def total_likes(posts):
"""전체 좋아요 합계."""
return sum(p["likes"] for p in posts)
패키지 안의 모듈에서 함수를 가져올 땐 점(.)으로 폴더와 파일을 이어줘요. from 패키지.모듈 import 함수 처럼요. 예를 들어 from insta.ranking import top_by_likes 라고 쓰면, insta 폴더 안 ranking.py 에서 top_by_likes 를 가져와요.
💡 한 줄 정리
여러 모듈을 폴더로 묶은 게 패키지다. 폴더에 __init__.py 를 두면 패키지가 되고, from 패키지.모듈 import 함수 로 가져온다.
🙋 학생 질문 — "튜터님, __init__.py 가 비어 있어도 되면 왜 굳이 만들어요?"
__init__.py 는 "이 폴더를 패키지로 다뤄 주세요" 라고 파이썬에게 알려주는 표시예요. 비어 있어도 그 표시 역할은 충분히 해요. 그래서 보통은 빈 파일이나 짧은 설명 한 줄만 둬요.
나중에 익숙해지면 이 파일에 "패키지를 가져올 때 미리 준비할 것" 을 적어두기도 해요. 지금은 "패키지 폴더엔 __init__.py 를 둔다" 라는 약속만 익히면 됩니다.
Step 7: "uv run -m 으로 모듈·패키지 실행하기"
패키지까지 만들었으니, 이제 패키지 안의 코드를 모아 실행하는 입구를 만들 차례예요. insta 패키지에 main.py 를 두고, 다른 모듈들을 가져와 조립할게요.
# src/c_functions/insta/main.py
from insta.formatting import clean_caption, hashtags_line
from insta.ranking import top_by_likes, total_likes
POSTS = [
{"title": "제주 여행", "likes": 1240},
{"title": "오늘의 점심", "likes": 320},
{"title": "한강 러닝", "likes": 8900},
]
def show_feed(posts):
ranked = top_by_likes(posts)
for rank, post in enumerate(ranked, start=1):
print(f"{rank}위: {post['title']} (좋아요 {post['likes']})")
print(f"전체 좋아요: {total_likes(posts)}")
if __name__ == "__main__":
print(clean_caption(" 제주에서 보낸 하루 "))
print(hashtags_line(["제주 여행", "맛집", "일상"]))
show_feed(POSTS)
맨 위에서 같은 패키지의 두 모듈(formatting·ranking)을 가져왔어요. 그리고 Step 5 에서 배운 if __name__ == "__main__": 가드 아래에서 그 함수들을 조립해 피드를 출력하죠.
자, 이 파일을 실행해 봅시다. 그런데 여기서 입문자들이 꼭 한 번 부딪히는 함정이 있어요. 파일을 직접 가리켜 실행하면 이렇게 에러가 나요.
uv run python insta/main.py
ModuleNotFoundError: No module named 'insta'
"분명 insta 폴더가 있는데 없다니?" 싶죠. 파일을 직접 실행하면 파이썬은 그 파일이 있는 폴더(insta/) 안쪽만 바라봐요. 그래서 insta 라는 패키지를 그 바깥에서 찾지 못해요. 해결책은 파일이 아니라 패키지 경로로 실행하는 거예요. -m 옵션을 쓰고, 점으로 패키지·모듈을 이어줍니다.
uv run python -m insta.main
제주에서 보낸 하루
#제주여행 #맛집 #일상
1위: 한강 러닝 (좋아요 8900)
2위: 제주 여행 (좋아요 1240)
3위: 오늘의 점심 (좋아요 320)
전체 좋아요: 10460
이번엔 잘 돌아가요! -m insta.main 은 "insta 패키지 안의 main 모듈을 실행해 줘" 라는 뜻이에요. 이렇게 실행하면 파이썬이 패키지를 제대로 찾을 수 있어서, from insta.formatting import ... 같은 가져오기도 잘 동작해요. 패키지 안의 코드를 돌릴 땐 "파일 직접 실행" 보다 "-m 패키지 경로 실행" 을 떠올리세요.
참고로
from .formatting import ...처럼 점을 앞에 붙이는 방식(상대 import)도 있어요. 다만 입문 단계에선from insta.formatting import ...처럼 패키지 이름을 또박또박 다 적는 방식(절대 import)이 헷갈리지 않아 권장돼요.
💡 한 줄 정리
패키지 안의 모듈은 파일 직접 실행 대신 uv run python -m 패키지.모듈 로 돌린다. 가져오기는 패키지 이름을 다 적는 절대 import 가 안전하다.
🙋 학생 질문 — "튜터님, python insta/main.py 랑 python -m insta.main 이 왜 결과가 달라요?"
python insta/main.py 는 "이 파일 하나를 실행해" 예요. 이때 파이썬은 그 파일이 든 폴더(insta/) 안쪽을 기준으로 삼아서, 그 바깥에 있는 insta 패키지를 못 찾아요.
python -m insta.main 은 "insta 라는 패키지에서 main 모듈을 찾아 실행해" 예요. 패키지가 있는 위치(insta/ 의 바깥 폴더)를 기준으로 보기 때문에 insta 를 제대로 찾아요. 그래서 패키지 안의 코드를 돌릴 땐 -m 을 쓰는 거예요.
Step 8: "종합 — 도메인을 모듈로 나누기"
오늘 배운 걸 한자리에 모아 볼게요. 우리는 인스타 도메인 코드를 주제별로 나눴어요. 포맷 함수는 formatting.py 에, 정렬·통계 함수는 ranking.py 에, 그리고 그것들을 조립해 화면에 뿌리는 입구는 main.py 에 뒀죠. 바로 Step 6~7 에서 만든 insta 패키지예요.
다시 전체 그림을 봅시다.
insta/ (패키지)
├── __init__.py 패키지 표시
├── formatting.py clean_caption · to_hashtag · hashtags_line
├── ranking.py top_by_likes · total_likes
└── main.py 위 둘을 import 해서 피드 조립 + 출력
main.py 안을 다시 들여다보면, 오늘 배운 도구가 전부 들어 있어요. from insta.formatting import ... 와 from insta.ranking import ... 로 같은 패키지의 모듈을 가져왔고(Step 2·6), 좋아요순 정렬엔 지난 시간의 sorted(key=...) 가, 순위 매기기엔 그 전 시간의 enumerate 가 쓰였어요. 그리고 if __name__ == "__main__": 가드(Step 5) 아래에서 모두 조립했죠.
uv run python -m insta.main
제주에서 보낸 하루
#제주여행 #맛집 #일상
1위: 한강 러닝 (좋아요 8900)
2위: 제주 여행 (좋아요 1240)
3위: 오늘의 점심 (좋아요 320)
전체 좋아요: 10460
캡션이 깔끔하게 정리되고, 해시태그가 한 줄로 이어지고, 게시물이 좋아요순으로 정렬돼 순위와 합계까지 나왔어요. 한 파일에 다 몰아 썼다면 길고 복잡했을 코드를, 주제별 모듈로 나누니 각 파일이 짧고 읽기 쉬워졌죠. 이게 오늘의 핵심이에요. 잘 나누고, import 로 가져다 쓰는 것.
이 함수들이 코드베이스 test_c3_modules.py 로 모두 검증돼 있어서, 정렬 결과나 합계(10460)가 의도대로 나온다는 걸 확인해 뒀어요.
💡 한 줄 정리
도메인 코드를 주제별 모듈로 나눠 패키지에 담고, main 에서 import 로 조립하면 짧고 읽기 쉬운 구조가 된다.
🙋 학생 질문 — "튜터님, 그럼 파일을 무조건 잘게 나눌수록 좋은 건가요?"
그건 아니에요. 너무 잘게 나누면, 함수 하나 따라가려고 파일을 여기저기 열어봐야 해서 오히려 흐름을 놓치기 쉬워요. 가져오기(import) 줄만 잔뜩 늘기도 하고요.
좋은 기준은 "주제" 예요. 포맷 관련은 한 모듈, 정렬·통계는 한 모듈처럼, 같이 묶여 다니는 함수끼리 모아두는 거죠. "이 기능은 어느 파일에 있겠지?" 하고 자연스럽게 떠오르면 잘 나눈 거예요. 적당한 크기는 코드를 쌓아가면서 감이 잡혀요.
마무리
오늘은 코드를 여러 파일로 나누고 import 로 가져다 쓰는 정리정돈의 기술을 배웠어요. import 와 from ... import 로 모듈을 가져오고, 표준 라이브러리를 둘러보고, if __name__ == "__main__" 으로 직접 실행과 import 를 구분했죠. 마지막엔 여러 모듈을 패키지로 묶어 uv run -m 으로 실행했어요. 카테고리 C 를 멋지게 마무리했습니다.
오늘 배운 핵심 세 가지
💡 하나: .py 파일 하나가 모듈이에요. import 모듈 로 통째로 가져와 모듈.함수() 로 쓰거나, from 모듈 import 이름 으로 함수만 콕 집어 바로 써요. 파이썬엔 설치 없이 쓰는 표준 라이브러리(math·random 등)도 잔뜩 딸려 와요.
💡 둘: import 하면 모듈이 한 번 통째로 실행돼요. 그래서 if __name__ == "__main__": 가드로 "직접 실행할 때만 돌릴 코드" 를 구분해요. __name__ 은 직접 실행이면 "__main__", import 되면 모듈 이름이에요.
💡 셋: 여러 모듈을 폴더로 묶은 게 패키지예요. 폴더에 __init__.py 를 두면 패키지가 되고, from 패키지.모듈 import 함수 로 가져와요. 패키지 안 코드는 uv run python -m 패키지.모듈 로 실행해요.
다음 시간 예고
오늘 우리는 게시물 데이터를 딕셔너리({"title": ..., "likes": ...})로 표현하고, 그걸 다루는 함수(top_by_likes·total_likes)를 따로 만들었어요. 데이터는 데이터대로, 함수는 함수대로 떨어져 있었죠.
그런데 잘 생각해 보면, "게시물" 이라는 하나의 대상에 "제목·좋아요" 라는 데이터와 "순위를 매긴다" 라는 행동이 같이 붙어 다니면 더 자연스럽지 않을까요? 다음 시간(D-1)엔 데이터와 행동을 하나로 묶는 클래스(class) 를 배웁니다. 붕어빵 틀로 붕어빵을 찍어내듯, 같은 모양의 데이터를 찍어내는 설계도예요. __init__·self 같은 새 식구도 만나게 돼요. 카테고리 D 의 문을 함께 열어 봅시다. 기대해 주세요!
과제
오늘 배운 모듈·패키지를 직접 손으로 만들어 봅시다. 눈으로 읽는 것과 직접 파일을 나눠보는 건 완전히 달라요. 막히면 Step 으로 돌아가 코드를 다시 보세요.
[기초] 나만의 도구 모듈 만들고 가져다 쓰기
함수를 담은 모듈을 만들고, 다른 파일에서 가져다 써보세요.
my_tools.py라는 파일을 만들고, 그 안에 함수 두 개를 둡니다 (예: 이름을 받아 멘션@이름을 만드는 함수, 숫자 리스트의 평균을 구하는 함수)- 다른 파일
use_tools.py에서import my_tools로 가져와my_tools.함수()형태로 호출해 봅니다 - 같은 함수를 이번엔
from my_tools import ...방식으로도 가져와 호출해 보고, 두 방식의 차이를 직접 느껴 봅니다 - (생각해보기) 어떤 함수를
import 모듈로 쓰고 싶고, 어떤 함수를from 모듈 import 이름으로 쓰고 싶나요?
[응용] 표준 라이브러리로 오늘의 추천 해시태그 뽑기
표준 라이브러리를 가져와 작은 기능을 만들어 보세요.
import random으로 무작위 도구 상자를 가져옵니다- 해시태그 후보 리스트(예:
["여행", "맛집", "일상", "운동"])를 준비합니다 random.choice로 "오늘의 추천 해시태그" 를 하나 뽑아#을 붙여 출력하는 함수를 만듭니다- 이 함수를
if __name__ == "__main__":가드 아래에서 호출해, 파일을 직접 실행할 때만 결과가 보이게 합니다 - (도전)
random.seed(...)로 씨앗을 고정하면 결과가 어떻게 달라지는지 관찰해 보세요
[심화] 작은 패키지 만들어 -m 으로 실행하기
오늘 만든 insta 패키지처럼, 여러분만의 작은 패키지를 만들어 보세요.
mypkg라는 폴더를 만들고 안에__init__.py를 둡니다 (비어 있어도 됩니다)mypkg안에 모듈 두 개를 둡니다 (예: 포맷 함수 모듈, 통계 함수 모듈)mypkg/main.py에서from mypkg.모듈 import 함수로 두 모듈의 함수를 가져와 조립합니다if __name__ == "__main__":가드 아래에서 결과를 출력하고,uv run python -m mypkg.main으로 실행해 봅니다- (도전) 일부러
uv run python mypkg/main.py로도 실행해 보고, 어떤 에러가 나는지 확인한 뒤-m으로 바꿔 보세요
생각해볼 주제
정답이 하나로 정해진 질문이 아니에요. 혼자 곰곰이 생각해 보거나, 함께 공부하는 동료와 이야기 나눠 보면 좋습니다.
1. import 모듈 과 from 모듈 import 이름, 언제 무엇을 쓸까?
오늘 두 가지 가져오기 방식을 배웠어요. import 모듈 은 모듈.함수() 처럼 출처가 분명하고, from 모듈 import 이름 은 짧고 간결하죠.
어느 쪽이 더 좋다고 딱 잘라 말하긴 어려워요. 코드를 읽는 사람 입장에서 "이 함수가 어디서 왔는지" 가 중요한 상황은 언제일까요? 반대로 짧게 쓰는 게 더 나은 상황은요? 그리고 from 모듈 import * 는 왜 편한데도 피하라고 하는지, 그 이유를 함께 생각해 보세요.
2. if __name__ == "__main__" 가드는 왜 필요할까?
오늘 우리는 import 하면 모듈이 한 번 통째로 실행된다는 걸 봤어요. 그리고 그 때문에 if __name__ == "__main__": 가드가 필요하다고 했죠.
만약 이 가드가 없다면 어떤 일이 벌어질까요? 테스트용으로 적어둔 실행 코드가 있는 모듈을, 다른 파일이 함수만 가져다 쓰려고 import 했을 때를 상상해 보세요. 이 가드가 "남에게 빌려주는 부품" 과 "직접 켜서 돌리는 프로그램" 을 어떻게 한 파일 안에서 공존시키는지 정리해 보세요.
3. 코드를 여러 파일로 나누면 무엇이 좋고, 너무 잘게 쪼개면 무엇이 나쁠까?
오늘 한 파일에 몰려 있던 코드를 주제별 모듈로 나눴어요. 나누니 각 파일이 짧고 읽기 쉬워졌죠.
그런데 무조건 잘게 나눌수록 좋기만 할까요? 파일이 너무 많아지면 어떤 불편이 생길까요? "잘 나눈 코드" 와 "지나치게 쪼갠 코드" 의 경계를 어디에 둘지, 그리고 그 기준을 무엇으로 삼을지 여러분의 생각을 정리해 보세요.
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안이에요. 먼저 스스로 충분히 파일을 나눠 짜본 뒤에 펼쳐 보세요. 정답을 외우는 게 아니라, "내가 만든 구조와 어디가 다른지" 를 비교하는 게 진짜 공부예요.
🎯 [과제 1 예시답안] 나만의 도구 모듈 만들고 가져다 쓰기
채점 포인트
| 항목 | 확인 | 핵심 |
|---|---|---|
| 모듈 만들기 | c3_my_tools.py 에 함수 정의 |
.py 파일 하나가 곧 모듈 |
| import 모듈 | import c3_my_tools → c3_my_tools.mention(...) |
출처를 점으로 밝혀 호출 |
| from import | from c3_my_tools import mention, average |
이름만 콕 집어 바로 호출 |
| 두 방식 비교 | 같은 함수를 두 방식으로 | 길지만 명확 vs 짧고 간결 |
풀이 예시
먼저 함수를 담은 도구 모듈을 만들어요.
# src/c_functions/c3_my_tools.py
"""나만의 작은 도구 모듈 (C-3 기초 과제)."""
def mention(name):
"""이름을 멘션 형태(@이름)로 만든다."""
return "@" + name.strip()
def average(numbers):
"""숫자 리스트의 평균을 구한다."""
return sum(numbers) / len(numbers)
이제 다른 파일에서 두 방식으로 가져다 써봅니다.
# src/c_functions/c3_use_tools_solution.py
import c3_my_tools
from c3_my_tools import mention, average
# 방식 1: import 모듈 → 모듈.함수() 로 출처를 밝혀 호출
print(c3_my_tools.mention(" 민지 "))
# 방식 2: from 모듈 import 이름 → 바로 호출
print(mention("승우"))
print(average([1240, 320, 8900]))
실행 결과예요.
@민지
@승우
3486.6666666666665
💡 튜터의 한마디: 같은 mention 함수를 c3_my_tools.mention(...) 으로도, 그냥 mention(...) 으로도 불렀죠. 앞쪽은 "어느 모듈에서 왔는지" 가 분명하고, 뒤쪽은 짧아서 편해요. 정답은 없어요. 출처를 분명히 하고 싶으면 import 모듈, 자주 써서 짧게 쓰고 싶으면 from 모듈 import 이름 을 고르면 됩니다. 평균값 끝이 길게 나온 건 / 나눗셈이 항상 실수(float)를 돌려주기 때문이에요(지난 카테고리에서 봤죠).
🎯 [과제 2 예시답안] 표준 라이브러리로 오늘의 추천 해시태그 뽑기
채점 포인트
| 항목 | 확인 | 핵심 |
|---|---|---|
| import random | import random |
설치 없이 쓰는 표준 라이브러리 |
| random.choice | random.choice(words) |
리스트에서 하나 무작위 선택 |
| 가드 패턴 | if __name__ == "__main__": |
직접 실행할 때만 결과 출력 |
| seed 고정 | random.seed(7) |
결과를 매번 똑같이 재현 |
풀이 예시
# src/c_functions/c3_recommend_solution.py
import random
CANDIDATES = ["여행", "맛집", "일상", "운동"]
def todays_hashtag(words):
"""후보 중 하나를 무작위로 골라 해시태그로 만든다."""
return "#" + random.choice(words)
if __name__ == "__main__":
random.seed(7)
print("오늘의 추천 해시태그:", todays_hashtag(CANDIDATES))
실행 결과예요.
오늘의 추천 해시태그: #일상
💡 튜터의 한마디: import random 한 줄로 무작위 도구 상자를 바로 가져왔어요. 따로 받을 필요가 없죠. 그리고 추천을 뽑는 코드를 if __name__ == "__main__": 가드 아래 둔 게 핵심이에요. 이러면 이 파일을 직접 실행할 때만 추천이 찍히고, 다른 파일이 todays_hashtag 함수만 가져다 쓸 땐 조용해요. random.seed(7) 을 빼면 실행할 때마다 결과가 바뀌어요. 직접 빼보고 여러 번 돌려보세요. 씨앗을 고정해야 예제 결과가 똑같이 재현된다는 걸 느낄 수 있어요.
🎯 [과제 3 예시답안] 작은 패키지 만들어 -m 으로 실행하기
채점 포인트
| 항목 | 확인 | 핵심 |
|---|---|---|
__init__.py |
폴더 안에 추가 | 이 폴더가 패키지라는 표시 |
| 모듈 분리 | formatting.py / stats.py |
주제별로 나누기 |
| 패키지 import | from c3pkg.formatting import shout |
패키지.모듈 절대 import |
-m 실행 |
uv run python -m c3pkg.main |
패키지 경로로 실행 |
| 함정 체험 | 파일 직접 실행 → ModuleNotFoundError |
-m 으로 해결 |
풀이 예시
폴더 c3pkg/ 를 만들고 안에 네 파일을 둬요.
# src/c_functions/c3pkg/__init__.py
"""작은 예제 패키지 (C-3 심화 과제)."""
# src/c_functions/c3pkg/formatting.py
"""포맷 함수."""
def shout(text):
"""텍스트를 외치는 형태로 바꾼다."""
return text.strip().upper() + "!"
# src/c_functions/c3pkg/stats.py
"""통계 함수."""
def average(numbers):
"""평균을 구한다."""
return sum(numbers) / len(numbers)
def biggest(numbers):
"""가장 큰 값을 돌려준다."""
return max(numbers)
# src/c_functions/c3pkg/main.py
from c3pkg.formatting import shout
from c3pkg.stats import average, biggest
LIKES = [1240, 320, 8900]
if __name__ == "__main__":
print(shout(" hello insta "))
print(f"평균 좋아요: {average(LIKES)}")
print(f"최고 좋아요: {biggest(LIKES)}")
패키지 경로로 실행해요.
uv run python -m c3pkg.main
HELLO INSTA!
평균 좋아요: 3486.6666666666665
최고 좋아요: 8900
도전 과제처럼 파일을 직접 실행하면 이렇게 에러가 나요.
uv run python c3pkg/main.py
ModuleNotFoundError: No module named 'c3pkg'
💡 튜터의 한마디: 포맷은 formatting.py, 통계는 stats.py 로 주제를 나누고, main.py 에서 둘을 import 해 조립했죠. __init__.py 를 둔 덕에 c3pkg 가 패키지로 인식돼요. 함정도 직접 부딪혀 보셨나요? 파일을 직접 가리켜 실행하면 파이썬이 c3pkg 패키지를 못 찾아 ModuleNotFoundError 가 나요. -m c3pkg.main 으로 바꾸면 패키지를 제대로 찾아 잘 돌아갑니다. 패키지 안 코드는 -m 으로 실행한다, 꼭 기억하세요.
🤔 [생각해볼 주제 1] import 모듈 과 from 모듈 import 이름, 언제 무엇을 쓸까?
문제 상황 요약
import 모듈 은 모듈.함수() 로 출처가 분명하고, from 모듈 import 이름 은 짧고 간결해요. 어느 쪽을 언제 써야 할까요? 그리고 from 모듈 import * 는 왜 편한데도 피하라고 할까요?
튜터의 가이드 및 해설
기준은 "읽는 사람이 헷갈리지 않는 쪽" 이에요.
import 모듈 이 좋은 경우는 출처가 중요할 때예요. random.choice(...)·math.sqrt(...) 처럼 앞에 모듈 이름이 붙으면, 코드만 봐도 "이건 random 에서, 저건 math 에서 왔구나" 가 한눈에 보여요. 서로 다른 모듈에 같은 이름의 함수가 있어도 점 덕분에 안 헷갈리죠.
from 모듈 import 이름 이 좋은 경우는 특정 함수를 아주 자주 쓸 때예요. 매번 c3_greetings.make_hashtag(...) 라고 길게 쓰는 대신 make_hashtag(...) 로 짧게 쓸 수 있어요. 다만 어느 모듈에서 왔는지가 코드에서 바로 안 보인다는 단점은 감수해야 해요.
그럼 from 모듈 import * 는요? 이건 모듈의 모든 이름을 한꺼번에 쏟아 가져와요. 편해 보이지만 위험해요.
- 무엇이 들어왔는지 코드만 봐선 알 수 없어요.
- 두 모듈에서 같은 이름을
*로 가져오면, 뒤엣것이 앞엣것을 조용히 덮어써요. 그래도 에러가 안 나서 버그를 찾기 어려워요.
그래서 입문 단계에선 * 를 피하고, 필요한 것만 이름으로 콕 집는 습관이 안전해요.
💡 핵심을 한마디로
"출처가 중요하면
import 모듈, 자주 써서 짧게 쓰고 싶으면from 모듈 import 이름이에요.import *는 무엇이 들어왔는지 알 수 없고 이름을 조용히 덮어쓸 수 있어서, 필요한 것만 이름으로 집는 게 안전해요."
🤔 [생각해볼 주제 2] if __name__ == "__main__" 가드는 왜 필요할까?
문제 상황 요약
import 하면 모듈이 한 번 통째로 실행돼요. 그래서 if __name__ == "__main__": 가드가 필요하다고 배웠죠. 만약 이 가드가 없다면 어떤 일이 벌어질까요?
튜터의 가이드 및 해설
가드가 없는 모듈을 상상해 봅시다. 함수도 정의돼 있고, 그 아래에 테스트용 print 나 실행 코드가 그냥 적혀 있어요.
이 모듈을 직접 실행하면 테스트 코드가 돌아요. 여기까진 좋아요. 그런데 다른 파일이 이 모듈에서 함수 하나만 import 하려고 하면 어떻게 될까요? import 하는 순간 모듈이 통째로 실행되니까, 가져다 쓰려던 것뿐인데 테스트용 print 까지 원치 않게 같이 찍혀버려요. 함수만 빌리고 싶었는데 시끄러운 부작용이 따라오는 거죠.
if __name__ == "__main__": 가드는 이 둘을 깔끔하게 나눠줘요.
- 가드 위(함수 정의 등): 직접 실행하든 import 하든 항상 준비돼요. = 남에게 빌려주는 부품
- 가드 아래(테스트·실행 코드): 직접 실행할 때만 돌아요. = 직접 켜서 돌리는 프로그램
__name__ 이 직접 실행이면 "__main__", import 면 모듈 이름이라는 차이를 이용한 거예요. 덕분에 한 파일이 "부품" 과 "실행 프로그램" 두 역할을 동시에 할 수 있어요.
💡 핵심을 한마디로
"가드가 없으면, 함수만 빌리려고 import 했을 뿐인데 모듈의 실행 코드까지 같이 돌아버려요.
if __name__ == \"__main__\":는 '남에게 빌려주는 부품' 과 '직접 켜서 돌리는 프로그램' 을 한 파일 안에서 깔끔하게 나눠줘요."
🤔 [생각해볼 주제 3] 코드를 여러 파일로 나누면 무엇이 좋고, 너무 잘게 쪼개면 무엇이 나쁠까?
문제 상황 요약
한 파일에 몰려 있던 코드를 주제별 모듈로 나누니 각 파일이 짧고 읽기 쉬워졌어요. 그런데 무조건 잘게 나눌수록 좋기만 할까요?
튜터의 가이드 및 해설
먼저 나눴을 때의 좋은 점이에요.
- 찾기 쉬워요. "해시태그 관련은
formatting.py" 처럼 어디를 열어야 할지 바로 떠올라요. - 고치기 안전해요. 한 모듈을 손봐도 다른 모듈에 영향이 적어요.
- 함께 일하기 좋아요. 여러 명이 서로 다른 파일을 건드려 충돌이 줄어요.
그런데 너무 잘게 쪼개면 반대로 불편해져요.
- 함수 하나 따라가려고 파일을 여기저기 열어야 해서 흐름을 놓치기 쉬워요.
- 파일마다
import줄만 잔뜩 늘어나요. - "이 기능이 대체 어느 파일에 있더라?" 하고 헤매게 돼요.
좋은 기준은 "주제" 예요. 같이 묶여 다니는 함수끼리 한 모듈에 모으는 거죠. 포맷은 포맷끼리, 정렬·통계는 그것끼리요. "이 기능은 어느 파일에 있겠지?" 하고 자연스럽게 떠오르면 잘 나눈 거예요. 처음부터 완벽하게 나눌 필욘 없고, 코드를 쌓아가며 "한 파일이 너무 커졌다" 싶을 때 쪼개도 충분해요.
💡 핵심을 한마디로
"나누면 찾기 쉽고 고치기 안전하지만, 너무 잘게 쪼개면 흐름을 좇느라 파일을 헤매게 돼요. 기준은 '주제' 예요. 같이 다니는 함수끼리 한 모듈에 모으고, '이 기능은 어느 파일에 있겠지?' 가 자연스럽게 떠오르면 잘 나눈 거예요."