본문 바로가기
자기 개발/Python

파이썬(Python) 공부 6편 — 함수(Function) 완전 정복 | def·return·기본값·*args·**kwargs·lambda·재귀함수까지

by conrad 2026. 3. 30.
06 / 15 Python 공부 시리즈 — 함수(Function)
← 5편: 반복문 for/while 보러 가기
Python 공부 시리즈 · 6편 | def · return · 매개변수 · lambda · 재귀

파이썬(Python) 공부 6편
함수(Function) 정의와 활용 완전 정복

함수는 코드를 재사용 가능한 단위로 묶는 도구입니다. 같은 코드를 여러 번 복사하는 대신, 함수로 정의하고 필요할 때 호출하면 됩니다. def·return부터 기본값 매개변수·*args·**kwargs·lambda·재귀함수까지 파이썬 함수의 모든 것을 정리합니다.

def / return 매개변수 & 인수 기본값 & 키워드 인수 *args / **kwargs 스코프 (LEGB) lambda / 재귀함수

🔨 함수(Function)란 — 재사용 가능한 코드 블록

함수는 특정 작업을 수행하는 코드 블록에 이름을 붙인 것입니다. 한 번 정의하면 여러 곳에서 호출(실행)할 수 있어 코드 중복을 줄이고 유지보수를 쉽게 만듭니다.

def 함수이름(매개변수1, 매개변수2): ← 함수 시그니처 / 콜론 필수 """독스트링: 함수 설명 (선택사항)""" ← 문서화 주석 실행할 코드 ← 함수 본문 (들여쓰기 4칸) return 반환값 ← 생략 시 None 반환
파이썬 함수 Function 프로그래밍 코드 ▲ 함수는 코드를 재사용 가능한 단위로 묶는 핵심 개념이다. 잘 설계된 함수는 하나의 기능만 담당하고, 입력(매개변수)을 받아 결과(return값)를 돌려준다. (출처: Unsplash / 참고 이미지)

📝 함수 정의와 호출 — def, return

 
func_basic.py
# 가장 단순한 함수 — 매개변수 없음, 반환값 없음
def say_hello():
    print("안녕하세요!")

say_hello()        # 함수 호출 → 안녕하세요!
say_hello()        # 여러 번 호출 가능

# 매개변수가 있는 함수
def greet(name):
    print(f"안녕하세요, {name}님!")

greet("Alice")      # 안녕하세요, Alice님!
greet("Bob")        # 안녕하세요, Bob님!

# return으로 값 반환하는 함수
def add(a, b):
    return a + b

result = add(3, 5)
print(result)        # 8
print(add(10, 20))    # 30 (바로 사용도 가능)

# 여러 값 반환 (튜플로 묶어서 반환)
def min_max(numbers):
    return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 9, 4, 7])
print(lo, hi)         # 1 9
독스트링(Docstring) — 함수 설명을 코드 안에 남기기

함수 첫 줄에 삼중 따옴표로 설명을 작성하면 help(함수명)으로 조회할 수 있습니다. 협업 및 유지보수 시 큰 도움이 됩니다.

def add(a, b):
    """두 수를 더해 반환합니다.

    Args:
        a: 첫 번째 숫자
        b: 두 번째 숫자
    Returns:
        두 수의 합
    """
    return a + b

help(add)   # 독스트링 출력

📋 매개변수의 종류 — 5가지 총정리

종류 문법 예시 설명
위치 인수 def f(a, b) f(1, 2) 순서대로 매칭
키워드 인수 def f(a, b) f(b=2, a=1) 이름으로 매칭 (순서 무관)
기본값 매개변수 def f(a, b=10) f(5) → b=10 생략 시 기본값 사용
가변 위치 인수 def f(*args) f(1, 2, 3, 4) 개수 제한 없이 튜플로 수집
가변 키워드 인수 def f(**kwargs) f(x=1, y=2) 키=값 쌍을 딕셔너리로 수집
 
default_param.py — 기본값 & 키워드 인수
# 기본값 매개변수 — 생략하면 기본값 사용
def greet(name, greeting="안녕하세요"):
    print(f"{greeting}, {name}님!")

greet("Alice")                   # 안녕하세요, Alice님!
greet("Bob", "반갑습니다")         # 반갑습니다, Bob님!

# 키워드 인수 — 이름으로 지정하면 순서 무관
def create_user(name, age, city):
    print(f"{name} ({age}세) - {city}")

create_user(age=25, city="서울", name="Alice")
# Alice (25세) - 서울

# ⚠️ 기본값 매개변수는 반드시 일반 매개변수 뒤에 위치
# def f(a=1, b):  → SyntaxError! (기본값이 앞에 오면 안 됨)
def f(a, b=1): ...   # ✅ 올바른 순서

*args와 **kwargs — 가변 인수 처리

파이썬 고급 문법 인수 개수가 정해지지 않을 때 사용하는 두 가지 방법
  • *args: 위치 인수를 개수 제한 없이 받아 튜플로 묶음. 별표(*)가 핵심이고 args는 관례적인 이름
  • **kwargs: 키워드 인수를 개수 제한 없이 받아 딕셔너리로 묶음. 별표 두 개(**)가 핵심이고 kwargs는 관례적인 이름
 
args_kwargs.py
# *args — 위치 인수를 튜플로 수집
def total(*args):
    print(type(args), args)  # <class 'tuple'> (1, 2, 3, 4, 5)
    return sum(args)

print(total(1, 2, 3))           # 6
print(total(1, 2, 3, 4, 5))      # 15
print(total())                   # 0 (인수 없어도 가능)

# **kwargs — 키워드 인수를 딕셔너리로 수집
def show_info(**kwargs):
    print(type(kwargs), kwargs)
    for key, value in kwargs.items():
        print(f"  {key}: {value}")

show_info(name="Alice", age=25, city="서울")
# <class 'dict'> {'name': 'Alice', 'age': 25, 'city': '서울'}
#   name: Alice / age: 25 / city: 서울

# 모두 조합 — 순서 규칙: 일반 → *args → 기본값 → **kwargs
def mixed(first, *args, sep=", ", **kwargs):
    print(f"first={first}")
    print(f"args={args}")
    print(f"sep={sep!r}")
    print(f"kwargs={kwargs}")

mixed(1, 2, 3, sep=" | ", x=10, y=20)

# 리스트/딕셔너리를 언패킹해서 전달
def power(base, exp):
    return base ** exp

params = [2, 10]
print(power(*params))         # 1024  (리스트 언패킹)

kparams = {"base": 3, "exp": 3}
print(power(**kparams))        # 27   (딕셔너리 언패킹)

🌐 변수 스코프(Scope) — LEGB 규칙

파이썬은 변수를 찾을 때 LEGB 순서로 탐색합니다. Local(지역) → Enclosing(중첩함수의 바깥) → Global(전역) → Built-in(내장) 순입니다.

 
scope_legb.py
# 전역 변수 vs 지역 변수
x = 10          # 전역 변수 (Global)

def func():
    x = 20      # 지역 변수 (Local) — 전역 x와 별개
    print(x)    # 20

func()
print(x)        # 10 (전역 x는 그대로)

# global 키워드 — 함수 안에서 전역 변수 수정
count = 0

def increment():
    global count    # 전역 변수임을 명시
    count += 1

increment()
increment()
print(count)    # 2

# Enclosing — 중첩 함수에서 바깥 함수 변수 접근
def outer():
    msg = "바깥 함수"
    def inner():
        print(msg)       # Enclosing 스코프에서 msg 접근
    inner()

outer()             # 바깥 함수
global은 가능하면 피하기

global 키워드를 남용하면 코드 흐름을 추적하기 어려워집니다. 함수는 필요한 값을 매개변수로 받아 return으로 반환하는 구조가 좋습니다. global은 정말 필요한 경우에만 최소한으로 사용하는 것이 권장됩니다.


🔍 타입 힌트(Type Hint) — 코드 가독성 향상

파이썬 3.5+에서 도입된 타입 힌트는 매개변수와 반환값의 자료형을 명시합니다. 실행에는 영향이 없지만 IDE 자동완성·오류 감지·협업 코드 가독성에 큰 도움이 됩니다.

 
type_hint.py
# 타입 힌트 없는 버전
def add(a, b):
    return a + b

# 타입 힌트 있는 버전 — 파라미터: 타입, -> 반환타입
def add(a: int, b: int) -> int:
    return a + b

def greet(name: str, times: int = 1) -> str:
    return f"Hello, {name}! " * times

def get_scores() -> list[int]:   # 반환값이 리스트
    return [90, 85, 92]

def find_user(uid: int) -> str | None:  # None이거나 str (3.10+)
    if uid == 1:
        return "Alice"
    return None

λ lambda — 익명 함수

lambda는 이름 없이 한 줄로 작성하는 간단한 함수입니다. lambda 매개변수: 표현식 형태이며, 표현식 하나만 작성할 수 있고 return을 명시하지 않습니다.

 
lambda_func.py
# 일반 함수 vs lambda
def square(x):
    return x ** 2

square_lambda = lambda x: x ** 2

print(square(5))          # 25
print(square_lambda(5))   # 25

# 인수 여러 개
add = lambda a, b: a + b
print(add(3, 7))          # 10

# lambda의 진가 — 고차 함수와 조합 (sorted, map, filter)
students = [
    {"name": "Alice", "score": 85},
    {"name": "Bob",   "score": 92},
    {"name": "Carol", "score": 78},
]

# 점수 기준으로 정렬
sorted_s = sorted(students, key=lambda s: s["score"], reverse=True)
for s in sorted_s:
    print(f"{s['name']}: {s['score']}")
# Bob: 92 / Alice: 85 / Carol: 78

# map() — 모든 요소에 함수 적용
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, nums))
print(squared)    # [1, 4, 9, 16, 25]

# filter() — 조건에 맞는 요소만 남김
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)      # [2, 4]

🔁 재귀 함수(Recursive Function) — 자기 자신을 호출

재귀 함수는 함수 안에서 자기 자신을 호출하는 함수입니다. 반드시 종료 조건(base case)이 있어야 하며, 없으면 무한 재귀 → RecursionError가 발생합니다.

 
recursion.py
# 팩토리얼 — 재귀의 대표 예제
# 5! = 5 × 4 × 3 × 2 × 1 = 120
def factorial(n: int) -> int:
    if n <= 1:          # ① 종료 조건 (base case)
        return 1
    return n * factorial(n - 1)  # ② 재귀 호출

print(factorial(5))    # 120
print(factorial(10))   # 3628800

# 피보나치 수열
# 0, 1, 1, 2, 3, 5, 8, 13, 21 ...
def fib(n: int) -> int:
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

for i in range(10):
    print(fib(i), end=" ")   # 0 1 1 2 3 5 8 13 21 34
재귀 함수 주의사항 재귀 깊이 제한과 성능 문제
  • 파이썬 기본 최대 재귀 깊이: 1000회 (sys.getrecursionlimit()으로 확인) → 초과 시 RecursionError
  • 피보나치 재귀의 성능 문제: fib(40)이면 수십억 번 중복 계산 → 실무에서는 반복문이나 메모이제이션(lru_cache) 활용
  • 언제 재귀를 쓰나: 트리 탐색, 폴더 구조 순회, 분할정복 알고리즘 등 구조가 재귀적인 문제에 적합
# functools.lru_cache로 성능 개선
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1: return n
    return fib(n-1) + fib(n-2)   # 결과 캐싱으로 중복 계산 방지

💻 실전 예제 — 함수로 구성한 학점 계산기

 
grade_with_functions.py
# 각 기능을 함수로 분리 → 읽기 쉽고 재사용 가능

def get_grade(score: int) -> str:
    """점수를 받아 학점 문자열을 반환합니다."""
    if   score >= 95: return "A+"
    elif score >= 90: return "A"
    elif score >= 85: return "B+"
    elif score >= 80: return "B"
    elif score >= 70: return "C"
    elif score >= 60: return "D"
    else:             return "F"

def calc_average(*scores: int) -> float:
    """가변 개수의 점수를 받아 평균을 반환합니다."""
    if not scores:
        return 0.0
    return sum(scores) / len(scores)

def print_report(name: str, **subjects) -> None:
    """학생 이름과 과목별 점수를 받아 성적표를 출력합니다."""
    print(f"\n{'='*30}")
    print(f"  {name} 성적표")
    print(f"{'='*30}")
    for subj, score in subjects.items():
        grade = get_grade(score)
        print(f"  {subj:8}: {score:3}점 ({grade})")
    avg = calc_average(*subjects.values())
    print(f"  {'평균':8}: {avg:.1f}점 ({get_grade(int(avg))})")
    print(f"{'='*30}")

# 함수 호출
print_report("Alice", Python=92, Math=88, English=95)

📝 6편 실습 문제

실습 1 — 계산기 함수 만들기
  • calculate(a, b, op="+") 함수를 만드세요
  • op가 "+", "-", "*", "/" 일 때 각각 계산 결과 반환
  • 0으로 나누기 시도 시 "0으로 나눌 수 없습니다" 반환
  • 지원하지 않는 op 입력 시 "지원하지 않는 연산자" 반환
실습 2 — *args와 lambda 활용
  • stats(*numbers) 함수: 최솟값·최댓값·합계·평균을 딕셔너리로 반환
  • 단어 리스트를 lambda로 글자 수 기준 오름차순 정렬
 
practice_06.py — 예시 답안
# 실습 1 — 계산기 함수
def calculate(a: float, b: float, op: str = "+") -> float | str:
    if   op == "+": return a + b
    elif op == "-": return a - b
    elif op == "*": return a * b
    elif op == "/":
        if b == 0: return "0으로 나눌 수 없습니다"
        return a / b
    return "지원하지 않는 연산자"

print(calculate(10, 3, "+"))    # 13
print(calculate(10, 0, "/"))    # 0으로 나눌 수 없습니다

# 실습 2 — stats 함수 & lambda 정렬
def stats(*numbers: float) -> dict:
    return {
        "min": min(numbers),
        "max": max(numbers),
        "sum": sum(numbers),
        "avg": sum(numbers) / len(numbers),
    }

print(stats(3, 1, 9, 4, 7))
# {'min': 1, 'max': 9, 'sum': 24, 'avg': 4.8}

words = ["python", "is", "fun", "and", "powerful"]
sorted_words = sorted(words, key=lambda w: len(w))
print(sorted_words)
# ['is', 'fun', 'and', 'python', 'powerful']
6편 핵심 요약
  • 함수 정의: def 이름(매개변수): 콜론 필수 / return 생략 시 None 반환
  • 여러 값 반환: return a, b → 튜플로 묶여 반환 / 언패킹으로 받기
  • 기본값 매개변수: def f(a, b=10) / 기본값은 일반 매개변수 뒤에
  • 키워드 인수: f(b=2, a=1) 이름으로 지정 / 순서 무관
  • *args: 가변 위치 인수 → 튜플로 수집
  • **kwargs: 가변 키워드 인수 → 딕셔너리로 수집
  • 스코프 LEGB: Local → Enclosing → Global → Built-in 순 탐색 / global 키워드 남용 금지
  • 타입 힌트: def f(a: int) -> str: 실행에 영향 없음 / 가독성·IDE 지원
  • lambda: lambda 매개변수: 표현식 / sorted·map·filter와 조합
  • 재귀 함수: 종료 조건(base case) 필수 / 재귀 깊이 제한 1000회 / 성능 주의
다음 편 예고 7편 — 리스트·튜플·딕셔너리·집합 완전 정복

리스트 메서드 / 컴프리헨션 / 딕셔너리 심화 / 집합 연산 / 불변 vs 가변

🐍

※ 본 포스팅은 Python 3 공식 문서(docs.python.org)의 함수 정의 레퍼런스를 기반으로 작성된 학습용 콘텐츠입니다. 코드 예시는 Python 3.10 이상 환경에서 테스트되었습니다.