Step 44. 함수와 모듈 — 부품을 만들어 조립하다

Step 44. 함수와 모듈 — 부품을 만들어 조립하다

Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★☆☆☆ | 예상 소요 시간 3시간

전제: Step 41~43을 마쳤다. 변수, 조건문, 반복문, 리스트와 딕셔너리를 쓸 수 있다.

  • 준비물: Step 41의 작업대(파이썬 + VSCode). 오늘은 파일을 두 개 이상 만들므로 py_practice 폴더를 열어 둡니다.
  • 주의: 오늘 실습은 100% 안전합니다. 내 컴퓨터에서 파일을 만들고 실행하는 일뿐입니다.

지금까지는 코드를 "위에서 아래로 한 번 읽히는 글"로 썼습니다. 오늘부터는 "부품을 조립하는 공학"으로 씁니다. 자주 쓰는 코드 묶음에 이름을 붙여 보관하는 것이 함수(function)이고, 그 함수들을 정리한 파일이 모듈(module)입니다. 지금까지 써 온 print(), input(), len(), int()가 전부 누군가 미리 만들어 둔 함수입니다. 오늘부터는 우리가 직접 만듭니다. 긴 코드를 잘게 나누는 법을 아는 순간, 코드가 길어져도 무섭지 않아집니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • defreturn으로 함수를 정의하고 호출한다
  • 매개변수(투입구)와 반환값(배출구)의 역할을 설명한다
  • 함수 안의 변수가 밖에서 보이지 않는 이유(스코프)를 안다
  • 함수를 별도 .py 파일(모듈)로 나누고 import로 가져온다
  • 기본값 매개변수로 "안 넣으면 이걸로" 옵션을 설계한다
  • "감독 함수 + 부품 함수들" 구조로 메뉴형 프로그램을 조립한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 파이썬 3.12 이상, VSCode (Step 41의 작업대)
오늘의 명령어·문법 def 이름(매개변수):, return, 함수 호출 이름(값), 기본값 매개변수, import 모듈, from 모듈 import 함수
필요한 개념 정의와 호출의 구분, 매개변수와 반환값, 스코프(지역 변수), 모듈

2-1. 함수 — 입력을 넣으면 결과가 나오는 기계

함수의 모양은 이것입니다.

def add(a, b):
    return a + b
  • def: "함수를 정의한다"는 선언 (define의 준말)
  • add: 함수의 이름
  • (a, b): 매개변수(parameter) — 재료를 받는 투입구
  • return: 반환 — 결과를 보내고 함수를 끝내는 배출구

정의만으로는 작동하지 않습니다. 만들어 둔 것일 뿐입니다. 쓸 때는 이름을 불러 재료를 넣습니다. add(3, 5)라고 하면 3과 5가 ab에 들어가고 8이 나옵니다. 이것을 호출(call)이라고 합니다.

2-2. 왜 함수인가 — 복사의 저주

함수 없이 같은 일이 필요하면 코드를 복사합니다. 그런데 복사본 다섯 개 중 한 곳에 잘못이 있으면 다섯 곳을 다 찾아 고쳐야 하고, 하나를 빠뜨리면 그날 밤 사고가 납니다. 함수는 원본이 하나뿐이므로 고치면 한 곳에서 끝납니다. "같은 코드를 세 번 쓰고 있으면 함수로 만들 때"라는 격언이 있습니다.

2-3. 스코프 — 작업실 안의 비밀

함수 안에서 만든 변수는 밖에서 보이지 않습니다. 함수는 독립된 작업실이고, 밖에서는 return으로 내놓은 결과만 받습니다. 이것을 스코프(scope, 유효 범위)라고 하고, 작업실 안의 변수를 지역 변수(local variable)라고 합니다. 처음엔 답답해 보이지만, 덕분에 작업실 밖의 변수들이 오염되지 않습니다. 큰 프로그램일수록 이 격리가 생명줄입니다.

2-4. 모듈과 import — 창고에서 꺼내기

함수가 늘어나면 한 파일이 지저분해집니다. 그래서 관련 함수끼리 별도 .py 파일로 모읍니다. 그 파일이 모듈이고, 다른 파일에서 import 파일명으로 꺼내 씁니다. Step 43의 미션에서 import random을 써 봤듯, random은 파이썬이 기본 제공하는 모듈입니다. 오늘은 우리가 직접 모듈을 만듭니다.


3. 따라 하기

3-1. 첫 함수 — 인사 기계

myfunc.py 파일을 만듭니다.

def greet(name):
    return f"안녕하세요, {name}님!"

msg = greet("민수")
print(msg)
print(greet("지영"))
안녕하세요, 민수님!
안녕하세요, 지영님!

(2026-09-09 실측.)

읽는 법: 두 가지 호출법을 보세요. msg = greet(...)는 결과를 변수에 담았다가 나중에 쓰는 법, print(greet(...))는 결과를 바로 쓰는 법입니다. return이 배출구이므로, 받는 사람(변수나 print)이 있어야 결과가 세상에 나옵니다.

실험: return을 지우고 그 자리에 print(f"안녕하세요, {name}님!")를 쓴 뒤, msg = greet("민수")print(msg)를 실행해 보세요. 화면에 인사가 한 번 출력된 다음 None이 출력됩니다 (2026-09-09 실측). 배출구가 없는 기계는 None을 돌려주기 때문입니다. 이 실험이 벽 2의 예방 주사입니다.

3-2. 매개변수 여러 개 — 계산 기계들

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

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    return a / b

print(add(10, 3))
print(sub(10, 3))
print(mul(10, 3))
print(div(10, 3))
13
7
30
3.3333333333333335

(2026-09-09 실측.)

읽는 법: 매개변수는 순서대로 들어갑니다. add(10, 3)에서 10은 a, 3은 b입니다. 이름이 아니라 자리로 들어갑니다. 그리고 /의 결과가 소수로 나오는 것을 보세요. 파이썬의 나눗셈 /는 항상 소수를 돌려줍니다.

3-3. 재료 없는 함수, 결과 없는 함수

def show_menu():
    print("=== 계산기 ===")
    print("1. 더하기")
    print("2. 빼기")

show_menu()
show_menu()

읽는 법: 재료가 필요 없으면 괄호를 비웁니다(def show_menu():). 결과를 돌려줄 필요가 없으면 return을 안 씁니다 — "일만 하고 끝"인 함수입니다. 메뉴를 두 번 출력했는데 코드는 한 번만 썼습니다. 이것이 함수의 첫 번째 덕입니다.

3-4. 스코프 실험 — 안과 밖

def make_secret():
    secret = "비밀번호는 1234"
    print("안:", secret)

make_secret()
print(secret)   # 일부러 오류를 냅니다

실행하면 (2026-09-09 실측):

안: 비밀번호는 1234
Traceback (most recent call last):
  File "...\s44_scope.py", line 6, in <module>
    print(secret)
          ^^^^^^
NameError: name 'secret' is not defined

읽는 법: "그런 이름 없는데요" — 작업실 밖에서는 안의 변수가 존재하지 않습니다. 함수 안의 print("안:", ...)는 정상 실행되고, 밖에서의 접근만 거부된 것에 주목하세요. 오류를 일부러 내 보는 것이 스코프를 익히는 가장 빠른 길입니다. 나중에 "분명 만들었는데 왜 모른다고 하지?" 하는 오류의 대부분이 이 스코프 문제입니다.

3-5. 모듈 만들기 — 계산기 창고

이제 두 개의 파일로 나눕니다. 같은 폴더에 두 파일을 만드세요.

창고 쪽, calc.py:

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

def sub(a, b):
    return a - b

def mul(a, b):
    return a * b

def div(a, b):
    return a / b

사용하는 쪽, main.py:

import calc

print(calc.add(10, 3))
print(calc.mul(10, 3))

실행은 main.py로 합니다.

13
30

(2026-09-09 실측.)

읽는 법: import calc는 "calc.py 파일을 통째로 가져온다"는 뜻이고, calc.add는 "그 창고의 add를 쓴다"는 뜻입니다. 점(.)은 "~의"라고 읽는다고 했지요. 실행은 사용하는 쪽(main.py)에서 하고, 창고(calc.py)는 실행할 필요가 없습니다. 파일을 나누는 순간, 프로그램은 "부품 창고 + 조립 라인"의 구조를 갖습니다.

3-6. from import — 필요한 것만 꺼내기

main2.py:

from calc import add, sub

print(add(1, 2))
print(sub(5, 3))
3
2

(2026-09-09 실측.)

읽는 법: "calc 창고에서 add와 sub만 꺼내온다"는 뜻입니다. 이렇게 가져오면 calc.add가 아니라 add라고 바로 부릅니다. 창고 이름을 매번 쓰는 방식(3-5)과 필요한 것만 꺼내는 방식(3-6), 둘 다 널리 쓰이니 둘 다 읽을 줄 알면 됩니다.

3-7. 기본값 매개변수 — 재료를 안 넣어도 되는 기계

매개변수에는 "안 넣으면 이걸로"라는 기본값(default value)을 정할 수 있습니다.

def greet(name, greeting="안녕하세요"):
    return f"{greeting}, {name}님!"

print(greet("민수"))
print(greet("지영", "반가워요"))
안녕하세요, 민수님!
반가워요, 지영님!

(2026-09-09 실측.)

읽는 법: 두 번째 재료를 안 넣으면 기본값 "안녕하세요"가 쓰이고, 넣으면 넣은 값이 쓰입니다. "대부분 이렇게 쓰지만 가끔 다르게 쓴다"는 상황에 맞는 설계입니다. 나중에 도구를 만들 때 "기본 포트 80, 다르면 직접 지정" 같은 옵션을 이 방법으로 만듭니다.

3-8. 함수가 함수를 부르기 — 조립 라인

함수는 다른 함수를 부를 수 있습니다. 이것이 "조립"의 진짜 모습입니다.

def ask_number(msg):
    return int(input(msg))

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

def main():
    x = ask_number("첫 번째 숫자: ")
    y = ask_number("두 번째 숫자: ")
    print("결과:", add(x, y))

main()

실행 예시 (3과 5를 입력) — 2026-09-09 실측:

첫 번째 숫자: 3
두 번째 숫자: 5
결과: 8

읽는 법: main()이 조립 라인의 감독입니다. 숫자 받는 일은 ask_number에게, 계산은 add에게 시키고, 자신은 순서만 지휘합니다. 각 함수가 한 가지 일만 하도록 나눈 모범 구조입니다. main() 호출이 파일 맨 아래에 있다는 것도 보세요. 정의를 다 끝내고 마지막에 조립 라인을 가동하는 배치입니다. 스크립트가 백 줄을 넘어가면 이 "감독 함수 + 부품 함수들" 구조가 유일한 생존 전략입니다.


4. 미션과 연습문제

미션 — 메뉴형 계산기

3-5의 calc.py를 그대로 창고로 쓰고, main.py를 아래 요구사항대로 완성하세요.

  1. 메뉴를 보여 준다:
=== 계산기 ===
1. 더하기  2. 빼기  3. 곱하기  4. 나누기  0. 종료
  1. 사용자가 번호와 두 숫자를 입력하면 해당 함수를 호출해 결과를 출력한다.
  2. 0을 입력할 때까지 메뉴가 반복된다 (while + if 조합).
  3. 없는 번호(예: 9)를 입력하면 없는 메뉴입니다를 출력한다.

힌트: 번호에 따라 다른 함수를 부르는 if/elif 갈림길을 while 안에 넣으면 됩니다. 완성되면 3+5, 10-4, 6*7, 8/2, 잘못된 번호, 종료까지 전부 시험해 보세요.

연습문제

문제 1. 함수를 def로 정의만 하고 실행했더니 아무 출력도 없었습니다. 왜 그렇고, 무엇이 빠진 건가요?

문제 2. return이 없는 함수를 호출해 그 결과를 변수에 담아 출력하면 무엇이 나오며, 왜 그런가요?

문제 3. 3-4에서 함수 밖의 print(secret)가 NameError가 난 이유를 "스코프" 개념으로 설명해 보세요.

문제 4. import calccalc.add(1, 2)로 부르는 방식과, from calc import addadd(1, 2)로 부르는 방식의 차이를 설명해 보세요.


5. 모범 답안과 완료 기준

미션 모범 답안

calc.py는 3-5와 동일합니다. main.py:

import calc

def show_menu():
    print("=== 계산기 ===")
    print("1. 더하기  2. 빼기  3. 곱하기  4. 나누기  0. 종료")

def main():
    while True:
        show_menu()
        choice = input("번호 선택: ")
        if choice == "0":
            print("종료합니다.")
            break
        if choice not in ("1", "2", "3", "4"):
            print("없는 메뉴입니다")
            continue
        a = int(input("첫 번째 숫자: "))
        b = int(input("두 번째 숫자: "))
        if choice == "1":
            print("결과:", calc.add(a, b))
        elif choice == "2":
            print("결과:", calc.sub(a, b))
        elif choice == "3":
            print("결과:", calc.mul(a, b))
        elif choice == "4":
            print("결과:", calc.div(a, b))

main()

해설: 메뉴 출력은 show_menu()로, 전체 흐름은 main()으로 나눈 "감독 + 부품" 구조입니다. choice not in ("1", "2", "3", "4")는 튜플에 대한 포함 검사로, 없는 번호를 한 줄로 걸러냅니다. continue로 숫자 입력 없이 메뉴로 돌아가는 것이 포인트입니다.

검증하는 법: ① 1~4번 각각에 올바른 함수가 호출되는지 확인합니다. ② 9를 입력해 "없는 메뉴입니다" 후 메뉴로 돌아오는지 봅니다. ③ 0으로 종료되는지 봅니다. ④ 나누기에서 8 / 24.0으로 나오는 것은 정상입니다 — /는 항상 소수를 돌려줍니다.

연습문제 해답

문제 1 해답. 정의(def)는 만들어 두는 것이고 실행되지는 않습니다. 빠진 것은 호출입니다. 파일 아래쪽에 print(add(3, 5)) 같은 호출 줄을 추가해야 합니다. def 블록만 있는 파일은 조용히 끝나는 것이 정상입니다.

문제 2 해답. None이 나옵니다. return이 없으면 파이썬이 자동으로 None을 돌려주기 때문입니다 (2026-09-09 실측: return 없는 greet을 호출해 담으니 None 출력). 값을 돌려주는 함수에는 마지막에 return 결과가 필요합니다. print는 화면에 보여 줄 뿐 돌려주지 않습니다.

문제 3 해답. secretmake_secret 함수 안에서 만든 지역 변수라 스코프(유효 범위)가 함수 내부로 제한되기 때문입니다. 함수가 끝나는 순간 그 변수는 사라지고, 밖에서는 return으로 내놓은 값만 받을 수 있습니다. 실측에서도 함수 안의 출력은 정상이고 밖의 접근만 NameError: name 'secret' is not defined로 거부되었습니다 (2026-09-09 실측).

문제 4 해답. import calc는 모듈을 통째로 가져와서 calc.add처럼 창고 이름을 붙여 부르고, from calc import add는 특정 함수만 꺼내 와서 add라고 바로 부릅니다. 결과는 같고 부르는 모양만 다릅니다. 전자는 출처가 명확하고, 후자는 짧게 쓸 수 있다는 장단이 있습니다.

완료 기준 체크리스트

  • [ ] defreturn으로 함수를 정의하고 호출할 수 있다
  • [ ] 정의와 호출의 차이를 설명할 수 있다
  • [ ] 매개변수는 자리(순서)로 들어감을 안다
  • [ ] return 없는 함수가 None을 돌려줌을 실험으로 확인했다
  • [ ] 지역 변수와 스코프를 NameError 실험과 함께 설명할 수 있다
  • [ ] 모듈을 만들고 import / from import 두 방식으로 가져올 수 있다
  • [ ] 미션(메뉴형 계산기)을 완성하고 네 가지 경로를 모두 시험했다

6. 흔한 실수와 해결

벽 1. 함수를 정의했는데 아무 일도 안 일어난다

증상: 파일을 실행했는데 출력이 없습니다.
원인: 정의(def)와 호출은 다릅니다. 만들어 뒀을 뿐 쓰지 않은 것입니다.
해결: 파일 아래쪽에 호출 줄(예: print(add(3, 5)))이 있는지 확인하세요. def 블록만 있는 파일은 조용히 끝나는 것이 정상입니다.

벽 2. return 대신 print를 써서 None이 나온다

증상: msg = greet("민수")print(msg)None.
원인: 함수에 return이 없으면 파이썬이 자동으로 None을 돌려줍니다 (2026-09-09 실측 확인).
해결: 그 함수가 "화면에 보여 주는 일"을 하는지, "값을 돌려주는 일"을 하는지 먼저 정하세요. 값을 돌려주려면 마지막에 return 결과가 필요합니다.

벽 3. 모듈을 import할 수 없다고 나온다

증상 (2026-09-09 실측):

Traceback (most recent call last):
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'calc_wrong'

원인: 두 파일이 다른 폴더에 있거나, 파일 이름 철자가 다르거나, .py가 아닌 다른 확장자로 저장된 경우입니다.
해결: 터미널에서 dir로 두 파일이 같은 폴더에 있는지 확인합니다. 파일명은 정확히 calc.py여야 합니다. 윈도우는 확장자를 숨기므로 calc.py.txt로 저장된 함정을 조심하세요 — 탐색기의 "보기 → 파일 확장명"을 켜서 확인합니다.

벽 4. 모듈을 고쳤는데 옛날 결과가 나온다

증상: calc.py를 수정했는데 결과가 그대로입니다.
원인: 파일을 저장하지 않고 실행했거나, 어딘가에 옛날 복사본이 있는 경우입니다.
해결: 모든 파일을 저장(Ctrl+S)하고 다시 실행합니다. VSCode에서 파일 탭에 점(●)이 있으면 저장 안 된 표시입니다.

벽 5. 처음부터 완벽하게 나누려다 아무것도 못 만든다

증상: 설계를 고민하다 손이 멈춥니다.
원인: 설계는 결과물이 있어야 다듬을 수 있습니다.
해결: 실무의 순서는 "먼저 작동, 다음 정리"입니다. 일단 while 하나에 다 넣어 돌려 보고, 그다음 메뉴 출력 부분을 함수로 빼 보세요. 돌아가는 코드를 나누고 깎는 작업을 리팩터링(refactoring)이라 부릅니다.


7. 정리

오늘의 개념

개념 한 줄 설명
함수 코드 묶음에 이름을 붙인 부품 — def로 정의, 이름으로 호출
매개변수 재료를 받는 투입구 — 자리(순서)로 들어간다
반환값 return이 보내는 결과 — 없으면 None
스코프 변수의 유효 범위 — 함수 안 변수는 밖에서 안 보인다
모듈 함수를 모은 .py 파일 — import로 꺼내 쓴다
기본값 매개변수 "안 넣으면 이걸로" 옵션 (greeting="안녕하세요")

오늘의 문법

문법 하는 일
def 이름(매개변수): 함수 정의
return 값 결과 반환 + 함수 종료
이름(값) 함수 호출
def f(a, b="기본"): 기본값 매개변수
import calccalc.add(...) 모듈 통째로 가져오기
from calc import addadd(...) 필요한 함수만 꺼내기

문법보다 중요한 감각

오늘의 세 가지 수확은 언어가 바뀌어도 따라다니는 자산입니다. 반복되는 코드에 이름을 붙이는 용기(함수), 재료와 결과의 약속을 설계하는 눈(매개변수와 return), 부품을 창고에 정리하는 습관(모듈). 좋은 함수의 격언 두 가지도 함께입니다. "한 가지 일만 할 것" — 더하기 함수가 출력까지 하면 출력 없이 계산만 필요할 때 못 씁니다. "이름이 일을 말할 것" — do_it보다 add가, f1보다 show_menu가 낫습니다.

이 책의 뒷부분에서 여러분은 직접 만든 도구들을 mytools.py 같은 모듈에 모아 재사용하게 됩니다. 실무의 보안 엔지니어들도 저마다 이런 개인 모듈을 키웁니다. 재사용 가능한 도구가 강력할수록 사용처의 원칙은 더 단단해야 합니다. 모든 공격 실습은 내 랩·합법 플랫폼에서만. 실서비스 무단 공격은 범죄입니다.


전부 체크되면 Step 44 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.