[Python 재입문] 9. Python 함수 def 완전 정복 - DRY 원칙/import 모듈화로 AI 앱 코드 정리하는 실전 가이드

AgentKim·2026년 3월 13일

여기서는 직접 만든 함수를 이용한 "코드 정리 기술"을 배워나갑니다.
공통된 처리는 함수(def)로 묶어내고, 자주 사용하는 범용적인 처리는 별도의 .py 파일에 정리해두어,
import문으로 필요할 때 불러오는 구성으로 만들어갑니다.

이를 통해 "같은 처리를 몇 번씩 쓰지 않는다"는 DRY 원칙을 익혀봅시다.

DRY 원칙 (Don't Repeat Yourself)
같은 코드를 반복해서 쓰지 않는 것을 권장하는 개발 원칙입니다.
함수를 사용하여 코드를 정리함으로써 재사용성/유지보수성/가독성이 모두 향상됩니다.


def (함수) - 기초부터 이해하기

함수란 무엇인가?

함수의 개념을 이해하기 위해, 먼저 가장 단순한 예시를 살펴봅시다.
Excel이나 Google 스프레드시트의 SUM() 함수를 사용해본 적이 있으시다면, 이미 함수의 핵심을 아는 것입니다. 매번 덧셈을 반복하는 대신 함수를 정의해두면 어디서든 한 번에 호출할 수 있습니다.

함수 기본 구조:

def 함수이름(인수):
    처리 내용
    return 반환값
키워드역할
def함수를 정의하기 시작하는 키워드
인수함수에 전달하는 입력값
return함수의 결과를 호출처에 반환

가장 단순한 예: 2배 함수

def double(x):
    return x * 2

# 함수를 호출한다
result = double(5)
print(result)  # 출력: 10

double(5)를 호출하면 → x = 5return 5 * 210이 반환됩니다.


실전 예: 할인 + 부가세 계산 함수

def tax_and_discount(price, tax_rate, discount):
    return round(price * tax_rate * (1 - discount))

💡 포인트: round(3.14)3
round()가 없으면 3.14원처럼 실제로 계산할 수 없는 금액이 됩니다.

  • price: 원래 가격
  • tax_rate: 부가세율 (예: 1.1 = 10%)
  • discount: 할인율 (예: 0.2 = 20%)

for문과 조합하여 여러 가격을 한 번에 처리:

def tax_and_discount(rate, discount=0.2):
    return round(rate * 1.1 * (1 - discount))

prices = [1980, 510, 2990]

for price in prices:
    print(f"할인 후 세금 포함 가격은 {tax_and_discount(price)}원입니다.")

# 출력:
# 할인 후 세금 포함 가격은 1738원입니다.
# 할인 후 세금 포함 가격은 449원입니다.
# 할인 후 세금 포함 가격은 2631원입니다.

함수 없이 작성하면:

prices = [1980, 510, 2990]
for price in prices:
    final_price = round(price * 1.1 * (1 - 0.2))
    print(f"할인 후 세금 포함 가격은 {final_price}원입니다.")

이 정도라면 큰 차이가 없어 보입니다. 하지만 할인율을 15%로 바꿔야 할 때는 어떨까요? 함수가 있으면 discount=0.15로 한 줄만 바꾸면 되지만, 함수가 없으면 for문 안의 계산식을 모두 찾아서 일일이 수정해야 합니다.


Gemini 호출을 함수로 정리하기

지금까지 계속 등장한 Gemini API 호출 처리는, 특히 함수화의 효과가 큽니다.

import os
from langchain_google_genai import ChatGoogleGenerativeAI
import streamlit as st
from dotenv import load_dotenv

load_dotenv()
google_api_key = os.getenv("GOOGLE_API_KEY")

if not google_api_key:
    st.error("Google API 키를 찾을 수 없습니다. 환경변수를 확인해주세요.")
    exit(1)

def ask_gemini(prompt):
    gemini = ChatGoogleGenerativeAI(
        google_api_key = google_api_key,
        model = "gemini-2.5-flash"
    )
    response = gemini.invoke(prompt)
    return response.content

# 함수를 호출한다
prompt = "Python의 함수에 대해 처음부터 알려주세요."
gemini_response = ask_gemini(prompt)

st.markdown(gemini_response)

# 기동 명령어:
# streamlit run ask_gemini.py
# 종료:
# Ctrl + C

함수화 전후 비교:

# ❌ 함수 없음: Gemini를 쓸 때마다 5줄을 반복 작성
gemini = ChatGoogleGenerativeAI(google_api_key=google_api_key, model="gemini-2.5-flash")
response = gemini.invoke("Python이란?")
answer1 = response.content

gemini = ChatGoogleGenerativeAI(google_api_key=google_api_key, model="gemini-2.5-flash")
response = gemini.invoke("리스트란?")
answer2 = response.content

# ✅ 함수 있음: 한 줄로 호출
answer1 = ask_gemini("Python이란?")
answer2 = ask_gemini("리스트란?")

Gemini와 Velog를 함수화한 코드 (velog_analyzer.py)

8장에서 작성한 velog_analyzer.py의 코드를, 함수를 사용하여 정리해봅시다.
Velog에서 게시글 제목을 취득하는 처리를 get_velog_titles(),
Gemini에게 분석시키는 처리를 ask_gemini()로 각각 함수화합니다.

아래 프로그램을 velog_analyzer.py 라는 이름으로 저장하세요.

import requests
from langchain_google_genai import ChatGoogleGenerativeAI
from dotenv import load_dotenv
import os
import streamlit as st

# ====================================
# 함수의 정의
# ====================================

# Velog에서 게시글 제목을 취득하는 함수
def get_velog_titles(item_count):
    url = "https://v2.velog.io/graphql"
    # GraphQL 쿼리를 딕셔너리로 정의
    graphql_query = {
        "query": f"""
            query {{
                posts(limit: {item_count}) {{
                    id
                    title
                    short_description
                    tags
                    user {{
                        username
                    }}
                }}
            }}
        """
    }
    try:
        # Velog GraphQL API에 POST 요청을 보낸다
        response = requests.post(
            url,
            json=graphql_query,
            headers={"Content-Type": "application/json"}
        )
        # 상태 코드가 200(성공)인 경우
        if response.status_code == 200:
            # JSON을 Python의 딕셔너리로 변환
            result = response.json()
            # GraphQL 응답은 "data" 키 안에 중첩되어 있다
            posts = result.get("data", {}).get("posts", [])
            # 장래적으로 게시글 제목을 저장할 리스트
            titles = []
            # posts에서 제목만 추출하여 titles 리스트에 추가
            for post in posts:
                title = post.get("title")
                if title:
                    titles.append(title)
            # Velog 제목 목록을 리스트로 반환
            return titles
    # try 블록이 실패한 경우의 처리
    except Exception as e:
        print(f"Velog에서 데이터 취득에 실패했습니다: {e}")
        # 에러가 발생한 경우 빈 리스트를 반환
        return []

# Gemini에게 프롬프트를 전달하여 분석시키는 함수
def ask_gemini(prompt):
    gemini = ChatGoogleGenerativeAI(
        google_api_key = google_api_key,
        model = "gemini-2.5-flash"
    )
    response = gemini.invoke(prompt)
    return response.content

# ====================================
# 메인 처리
# ====================================

# if __name__ == "__main__": 이하는, 이 Python 파일이 직접 실행되었을 때만 실행되는 코드 블록
if __name__ == "__main__":
    load_dotenv()
    google_api_key = os.getenv("GOOGLE_API_KEY")

    if not google_api_key:
        st.error("Google API 키를 찾을 수 없습니다. 환경변수를 확인해주세요.")
        # API 키가 없는 경우 에러 메시지를 표시하고 프로그램을 종료
        exit(1)

    # get_velog_titles 함수를 호출하여 Velog에서 게시글 제목을 20건 취득
    titles = get_velog_titles(item_count=20)

    if not titles:
        st.warning("Velog에서 게시글 제목을 가져오지 못했습니다.")
        exit(1)

    prompt = f"""
    다음은 Velog 인기 게시글 제목 목록입니다.\n
    기술 트렌드 랭킹을, 프로그래밍 초보자도 알 수 있는\n
    블로그 기사로 정리해주세요.\n\n
    【Velog 게시글 제목】\n\n
    {titles}
    """

    # ask_gemini 함수를 호출하여 Gemini에게 프롬프트를 전달하고 분석 결과를 취득
    response = ask_gemini(prompt)

    st.title("Velog 기술 트렌드 분석")
    st.subheader("분석 결과")
    st.markdown(response)

# 기동 명령어:
# modulesge
# 종료:
# Ctrl + C

코드 해설 (velog_analyzer.py)

두 함수의 역할 분리

함수역할입력(인수)출력(반환값)
get_velog_titles(item_count)Velog에서 제목 취득게시글 수제목 리스트
ask_gemini(prompt)Gemini 분석질문 텍스트AI 답변 텍스트

이처럼 "데이터 취득""AI 분석" 을 별도 함수로 분리하면, 각 함수의 역할이 명확해져 코드를 읽기도, 수정하기도 쉬워집니다.


get_velog_titles() 내부 — try-except + Falsy의 연계

def get_velog_titles(item_count):
    ...
    try:
        ...
        return titles    # ← 성공 시: 제목 리스트 반환
    except Exception as e:
        print(f"Velog에서 데이터 취득에 실패했습니다: {e}")
        return []        # ← 실패 시: 빈 리스트 반환

빈 리스트 []를 반환하는 이유:

에러가 발생했을 때 return []로 빈 리스트를 반환하면, 호출처에서 Falsy 체크로 간단히 처리할 수 있습니다.

titles = get_velog_titles(item_count=20)

if not titles:           # 빈 리스트는 Falsy → 에러 처리
    st.warning("제목을 가져오지 못했습니다.")
    exit(1)

# 여기에 도달하면 titles에 데이터가 있음이 보장된다

제4부에서 배운 Truthy/Falsy 개념이 실전에서 활용되는 전형적인 패턴입니다.


if __name__ == "__main__": — 함수 정의와 실행을 분리

# ① 함수 정의 (파일을 import하기만 해도 실행됨)
def get_velog_titles(item_count): ...
def ask_gemini(prompt): ...

# ② 메인 처리 (직접 실행할 때만 실행됨)
if __name__ == "__main__":
    titles = get_velog_titles(item_count=20)
    ...
상황if __name__ == "__main__": 블록 실행 여부
streamlit run velog_analyzer.py✅ 실행됨
from velog_analyzer import ask_gemini (다른 파일에서 import)❌ 실행 안 됨

이 구문 덕분에, 나중에 import문으로 함수만 가져올 때 메인 처리가 의도치 않게 실행되는 것을 막을 수 있습니다.


import문 — 함수를 별도 파일로 분리하기

단일 파일 내에서 함수화하는 것만으로도 가독성은 크게 향상됩니다.
더 나아가 자주 사용하는 함수를 modules/ 폴더에 정리해두면, 다른 프로젝트에서도 재사용할 수 있는 "나만의 부품 창고"가 됩니다.

목표 폴더 구성:

project_root/
├── modules/
│   ├── velog.py      ← Velog 데이터 취득 함수
│   └── gemini.py     ← Gemini AI 호출 함수
├── velog_analyzer.py ← 메인 코드 (import로 함수를 불러온다)
└── .env

modules/velog.py

import requests

def get_velog_titles(item_count):
    url = "https://v2.velog.io/graphql"
    graphql_query = {
        "query": f"""
            query {{
                posts(limit: {item_count}) {{
                    id
                    title
                    short_description
                    tags
                    user {{
                        username
                    }}
                }}
            }}
        """
    }
    try:
        response = requests.post(
            url,
            json=graphql_query,
            headers={"Content-Type": "application/json"}
        )
        if response.status_code == 200:
            result = response.json()
            posts = result.get("data", {}).get("posts", [])
            titles = []
            for post in posts:
                title = post.get("title")
                if title:
                    titles.append(title)
            return titles
    except Exception as e:
        print(f"Velog에서 데이터 취득에 실패했습니다: {e}")
        return []

modules/gemini.py

from langchain_google_genai import ChatGoogleGenerativeAI
from dotenv import load_dotenv
import os

load_dotenv()
google_api_key = os.getenv("GOOGLE_API_KEY")

if not google_api_key:
    raise ValueError("Google API 키를 찾을 수 없습니다. 환경변수를 확인해주세요.")

def ask_gemini(prompt):
    gemini = ChatGoogleGenerativeAI(
        google_api_key = google_api_key,
        model = "gemini-2.5-flash"
    )
    response = gemini.invoke(prompt)
    return response.content

raise ValueError(...) vs exit(1) 비교

exit(1)raise ValueError(...)
용도메인 스크립트에서 즉시 종료모듈(라이브러리)에서 에러를 알릴 때
호출처직접 실행하는 파일import되어 사용되는 파일
특징프로그램 전체를 강제 종료에러를 던져 호출처에서 처리하게 함

모듈 파일에서는 exit(1) 대신 raise를 사용하는 것이 올바른 패턴입니다.


velog_analyzer2.py (import 버전 — 최종 형태)

from modules.velog import get_velog_titles
from modules.gemini import ask_gemini
import streamlit as st

if __name__ == "__main__":
    titles = get_velog_titles(item_count=20)

    if not titles:
        st.warning("Velog에서 게시글 제목을 가져오지 못했습니다.")
        exit(1)

    prompt = f"""
    다음은 Velog 인기 게시글 제목 목록입니다.\n
    기술 트렌드 랭킹을, 프로그래밍 초보자도 알 수 있는\n
    블로그 기사로 정리해주세요.\n\n
    【Velog 게시글 제목】\n\n
    {titles}
    """

    response = ask_gemini(prompt)

    st.title("Velog 기술 트렌드 분석")
    st.subheader("분석 결과")
    st.markdown(response)

# 기동 명령어:
# streamlit run velog_analyzer.py
# 종료:
# Ctrl + C

비교: 함수화 전 vs import 후

함수화 전단일 파일 함수화modules 분리 후
코드 줄 수60줄+50줄+15줄
가독성낮음보통높음
재사용성없음이 파일에서만다른 프로젝트에서도 가능
수정 영향 범위전체 파일함수 내부만모듈 파일만

import문 문법 정리

# 패턴1: 특정 함수만 가져오기 (권장)
from modules.velog import get_velog_titles
from modules.gemini import ask_gemini

# 패턴2: 모듈 전체를 가져오기
import modules.velog as velog
velog.get_velog_titles(item_count=20)  # 접두사가 붙는다

# 패턴3: Python 내장/설치 모듈 (이미 사용해왔던 방식)
import os
import requests
from dotenv import load_dotenv

from A import B 구문 해설:

from modules.velog import get_velog_titles
     ↑               ↑    ↑
     폴더.파일명      키워드  가져올 함수명

응용: 다른 API 모듈도 같은 패턴으로 추가하기

modules/ 폴더에 새 파일을 추가하기만 하면, 어떤 API도 같은 구조로 확장할 수 있습니다.

project_root/
├── modules/
│   ├── velog.py        ← Velog 게시글 취득
│   ├── gemini.py       ← Gemini AI 호출
│   ├── kakao.py        ← 카카오 API (추가 예정)
│   └── weather.py      ← 기상청 공공데이터 (추가 예정)
├── velog_analyzer.py
├── kakao_analyzer.py   ← from modules.kakao import ... 로 호출
└── .env

정리

개념한 줄 요약
def 함수명(인수):함수를 정의하는 키워드. 재사용 가능한 처리 단위를 만든다
인수함수에 전달하는 입력값. 함수 안에서 변수처럼 사용된다
return함수의 결과를 호출처에 반환한다
기본값 인수def f(x, discount=0.2): — 인수를 생략하면 기본값이 사용됨
DRY 원칙Don't Repeat Yourself — 같은 코드를 반복하지 않는다
if __name__ == "__main__":직접 실행할 때만 실행되는 블록. import 시에는 건너뜀
from A import BA 파일(모듈)에서 B 함수(또는 클래스)만 가져온다
return []에러 시 빈 리스트 반환 → Falsy 체크와 연계되는 안전 패턴
raise ValueError(...)모듈에서 에러를 호출처로 전파할 때 사용 (exit(1) 대신)

©2024-2026 MDRULES.dev, Hand-crafted & made with Jaewoo Kim.
이메일문의: jaewoo@mdrules.dev

AI강의/개발/기술자문, AI 업무 자동화 컨설팅  문의: https://talk.naver.com/ct/w5umt5

AI 프롬프트 및 워크플로우 설계 대행: https://mdrules.dev

📌 함수화 3단계 패턴

  1. 공통 처리를 발견한다 → "이 코드 여러 곳에서 쓰이네?"
  2. def 로 묶는다 → 인수와 반환값을 정의한다
  3. modules/로 분리한다 → 다른 프로젝트에서도 재사용

이 3단계를 반복하면, 자연스럽게 "나만의 부품 창고"가 완성됩니다.

profile
AI-fluent liberal arts engineer | AX Consulting | AI Workflow Architect | LLM Engineer | Claude Code | AI강의/개발/기술자문 | AI 업무 자동화 컨설팅

0개의 댓글