함수와 모듈로 코드 나누기
4편에서 조건과 반복으로 흐름을 만들다 보면 코드가 길어지기 시작합니다. 이때 함수를 사용하면 이름 있는 블록으로 로직을 나눌 수 있고, 모듈로 파일을 분리하면 재사용과 테스트가 쉬워집니다. 이번 글에서는 함수를 정의하는 기본 문법부터 매개변수, 반환값, 모듈 임포트까지 단계별로 살펴봅니다.
이번 글에서 새로 나오는 용어
- 매개변수: 함수가 입력 값을 받을 때 사용하는 이름표로 함수 선언부에 적습니다.
- 모듈: Python 파일 하나를 가리키는 단위로, 필요한 기능을 임포트해 재사용합니다.
- 타입 힌트:
name: str처럼 변수나 함수 입출력에 예상 타입을 적어 의도를 밝히는 표기 - if name == "main": 파일을 직접 실행할 때만 코드를 돌리고 임포트 시에는 건너뛰게 하는 관용구
핵심 개념
학습 메모
- 소요 시간: 60분
- 준비물: 조건·반복 기초,
uv기반 프로젝트- 학습 목표: 함수를 정의하고 모듈로 분리해 재사용 가능한 구조 만들기
함수는 로직을 묶는 이름표이고, 모듈은 기능을 파일 단위로 나누는 방법입니다. 두 가지를 조합하면 같은 계산을 여러 곳에서 활용하고, 테스트도 분리할 수 있습니다.
코드로 따라하기
함수 정의 기본
def greet(name: str) -> str:
return f"안녕하세요, {name}님"
message = greet("지민")
print(message)
함수는 입력 값을 매개변수(parameter)로 받고 처리 결과를 return으로 돌려줍니다. 타입 힌트를 적으면 의도가 더 명확해집니다.
기본값과 키워드 인자
def notify(message: str, urgent: bool = False):
prefix = "[긴급] " if urgent else "[일반] "
print(prefix + message)
notify("배포 완료")
notify("에러 발생", urgent=True)
기본값을 지정해 호출 코드를 단순하게 만들 수 있고, urgent=True처럼 키워드 인자를 쓰면 가독성이 좋아집니다.
가변 인자와 키워드 인자
def combine(*values: str, separator: str = ", ") -> str:
return separator.join(values)
result = combine("A", "B", "C", separator=" | ")
print(result)
*values는 임의 개수의 인자를 튜플로 받습니다. **kwargs를 사용하면 키-값 쌍을 딕셔너리로 받을 수 있습니다.
모듈로 함수 분리하기
프로젝트 구조 예시:
python-playground/
├─ app.py
└─ helpers/
└─ reports.py
helpers/reports.py에 함수를 정의합니다.
# helpers/reports.py
def summarize_scores(scores: list[int]) -> dict:
total = sum(scores)
average = total / len(scores)
return {"total": total, "average": round(average, 2)}
app.py에서 임포트하여 사용합니다.
from helpers.reports import summarize_scores
scores = [82, 75, 91]
report = summarize_scores(scores)
print(report)
모듈을 분리하면 테스트와 유지보수가 쉬워지고, 다른 스크립트에서도 같은 함수를 재사용할 수 있습니다.
🧱 모듈이란? Python 파일 하나가 곧 모듈입니다. 기능 단위로 파일을 쪼개 두면 임포트해 재사용하거나 테스트할 수 있어 코드가 엉키지 않습니다.
실전 예시: 리포트 생성기 계층 나누기
# services/reports.py
def create_summary(records: list[dict]) -> dict:
total = len(records)
completed = sum(1 for record in records if record["done"])
return {"total": total, "completed": completed}
# cli/report_command.py
from services.reports import create_summary
def render_report(records: list[dict]) -> str:
summary = create_summary(records)
return f"전체 {summary['total']}건 중 {summary['completed']}건 완료"
핵심 로직을 services 모듈에 두고, 입출력(터미널, 파일, UI 등)은 별도 모듈에서 담당하면 테스트 범위가 자연스럽게 나뉩니다. 동료와 역할을 나눌 때도 "데이터 처리", "표시"처럼 경계를 명확히 설명할 수 있습니다.
모듈 간 책임 흐름 한눈에 보기
아래 다이어그램은 CLI → 서비스 → 데이터 경로가 어떻게 이어지는지 보여 줍니다. 각 노드는 Python 파일이고, 화살표는 함수 호출 방향입니다.
프로젝트를 시각화해두면 모듈이 늘어나도 어디에 기능을 배치할지 빨리 결정할 수 있습니다.
if __name__ == "__main__":
모듈이 직접 실행될 때만 특정 코드를 실행하고, 임포트될 때는 실행하지 않도록 하는 패턴입니다.
def main():
print("메인 로직 실행")
if __name__ == "__main__":
main()
이 구조를 쓰면 모듈이 다른 파일에 임포트될 때는 main()이 호출되지 않아 의도치 않은 실행을 막을 수 있습니다.
왜 중요한가
함수로 로직을 나누면 테스트를 독립적으로 작성할 수 있고, 모듈을 나누면 동료와 역할을 나누기도 쉽습니다. 또 if __name__ == "__main__" 패턴을 넣으면 같은 파일이라도 실행 모드와 임포트 모드를 분리해 안정적으로 재사용할 수 있습니다.
실습
- 따라 하기:
helpers폴더를 만들고summarize_scores함수를 그대로 작성한 뒤app.py에서 임포트해 실행합니다. - 확장하기:
render_report같은 표시용 함수를 새 파일에 추가하고main()에서 문자열만 출력하도록 연결합니다. - 디버깅: 모듈 경로를 일부러 틀려
ModuleNotFoundError를 만든 뒤__init__.py추가 또는 올바른 상대 경로로 수정합니다. - 완료 기준: 최소 두 개 파일에서 함수를 서로 임포트하고
if __name__ == "__main__"블록 안에서 실행 흐름이 명확히 나뉘었을 때입니다.
마무리
함수와 모듈을 활용하면 코드를 기능별로 나눌 수 있고, 테스트와 재사용이 쉬워집니다. 이렇게 구조화된 코드는 파일 입출력이나 외부 API 연동 같은 작업에서도 큰 도움이 됩니다. 다음 글에서는 파일과 JSON을 다루면서 외부 데이터를 흡수하고 저장하는 흐름을 만들어 보겠습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요