본문으로 건너뛰기

함수

함수란?

함수는 특정 작업을 수행하는 코드 블록입니다. 코드를 재사용하고 구조화하는 핵심 도구입니다.

함수를 사용하는 이유

# ❌ 함수 없이 (코드 중복)
print("=" * 30)
print("환영합니다!")
print("=" * 30)

print("=" * 30)
print("로그인 성공!")
print("=" * 30)

# ✅ 함수 사용 (재사용 가능)
def print_banner(message):
print("=" * 30)
print(message)
print("=" * 30)

print_banner("환영합니다!")
print_banner("로그인 성공!")

함수 정의와 호출

기본 함수

# 함수 정의
def greet():
"""인사 메시지를 출력합니다"""
print("안녕하세요!")

# 함수 호출
greet() # 안녕하세요!
greet() # 안녕하세요!

# 여러 줄 실행
def introduce():
print("제 이름은 파이썬입니다")
print("만나서 반갑습니다")
print("잘 부탁드립니다")

introduce()

반환값이 있는 함수

# return 문
def add(a, b):
"""두 수를 더합니다"""
return a + b

result = add(3, 5)
print(result) # 8

# 반환값을 바로 사용
print(add(10, 20)) # 30

# 여러 연산
total = add(1, 2) + add(3, 4)
print(total) # 10

# 반환값 없으면 None
def say_hello():
print("Hello")

result = say_hello()
print(result) # None

여러 값 반환

# 튜플로 반환
def get_stats(numbers):
"""통계 정보 반환"""
total = sum(numbers)
average = total / len(numbers)
maximum = max(numbers)
minimum = min(numbers)
return total, average, maximum, minimum

# 언패킹
scores = [85, 90, 78, 92, 88]
total, avg, max_score, min_score = get_stats(scores)

print(f"총점: {total}")
print(f"평균: {avg}")
print(f"최고: {max_score}")
print(f"최저: {min_score}")

# 딕셔너리로 반환
def get_user_info():
return {
"name": "홍길동",
"age": 25,
"email": "hong@example.com"
}

user = get_user_info()
print(user["name"]) # 홍길동

매개변수 (Parameters)

위치 매개변수 (Positional Parameters)

# 순서대로 전달
def power(base, exponent):
"""거듭제곱 계산"""
return base ** exponent

print(power(2, 3)) # 8 (2³)
print(power(5, 2)) # 25 (5²)

# 순서 중요!
def divide(a, b):
return a / b

print(divide(10, 2)) # 5.0
print(divide(2, 10)) # 0.2

키워드 매개변수 (Keyword Arguments)

# 이름으로 전달
def make_profile(name, age, city):
return f"{name}님은 {age}세이고 {city}에 삽니다"

# 순서 무관
print(make_profile(name="홍길동", age=25, city="서울"))
print(make_profile(age=30, city="부산", name="김철수"))

# 위치 + 키워드 혼용 (위치가 먼저)
print(make_profile("이영희", age=28, city="대구"))

기본값 매개변수 (Default Parameters)

# 기본값 설정
def greet(name, greeting="안녕하세요"):
"""인사 메시지"""
return f"{greeting}, {name}님!"

print(greet("홍길동")) # 안녕하세요, 홍길동님!
print(greet("김철수", "반갑습니다")) # 반갑습니다, 김철수님!

# 실용 예제
def make_coffee(size="regular", sugar=1, milk=True):
"""커피 주문"""
coffee = f"{size.upper()} 커피"
if sugar > 0:
coffee += f", 설탕 {sugar}개"
if milk:
coffee += ", 우유 추가"
return coffee

print(make_coffee()) # REGULAR 커피, 설탕 1개, 우유 추가
print(make_coffee("large", 2, False)) # LARGE 커피, 설탕 2개
print(make_coffee(size="small", sugar=0)) # SMALL 커피, 우유 추가

# ⚠️ 주의: 기본값은 함수 정의 시 한 번만 평가
def add_to_list(item, target=[]): # 위험!
target.append(item)
return target

print(add_to_list(1)) # [1]
print(add_to_list(2)) # [1, 2] (예상: [2])

# ✅ 올바른 방법
def add_to_list_safe(item, target=None):
if target is None:
target = []
target.append(item)
return target

print(add_to_list_safe(1)) # [1]
print(add_to_list_safe(2)) # [2]

가변 위치 인자 (*args)

# 개수 제한 없이 받기
def sum_all(*numbers):
"""모든 숫자 더하기"""
total = 0
for num in numbers:
total += num
return total

print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15
print(sum_all()) # 0

# 타입 확인
def print_args(*args):
print(type(args)) # <class 'tuple'>
print(args)

print_args(1, 2, 3) # (1, 2, 3)

# 리스트 언패킹
numbers = [1, 2, 3, 4, 5]
print(sum_all(*numbers)) # 15

# 실용 예제
def make_sentence(*words):
"""단어들을 문장으로"""
return " ".join(words)

print(make_sentence("파이썬은", "재미있는", "언어입니다"))
# 파이썬은 재미있는 언어입니다

가변 키워드 인자 (**kwargs)

# 키워드 인자 여러 개
def print_info(**kwargs):
"""정보 출력"""
for key, value in kwargs.items():
print(f"{key}: {value}")

print_info(name="홍길동", age=25, city="서울")
# name: 홍길동
# age: 25
# city: 서울

# 타입 확인
def show_kwargs(**kwargs):
print(type(kwargs)) # <class 'dict'>
print(kwargs)

show_kwargs(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3}

# 딕셔너리 언패킹
user = {"name": "김철수", "age": 30, "job": "개발자"}
print_info(**user)

# 실용 예제
def make_query(**conditions):
"""SQL WHERE 절 생성"""
clauses = []
for key, value in conditions.items():
if isinstance(value, str):
clauses.append(f"{key} = '{value}'")
else:
clauses.append(f"{key} = {value}")
return " AND ".join(clauses)

print(make_query(name="홍길동", age=25, city="서울"))
# name = '홍길동' AND age = 25 AND city = '서울'

매개변수 조합

# 순서: 일반 > *args > 기본값 > **kwargs
def complex_function(a, b, *args, c=10, **kwargs):
print(f"a: {a}")
print(f"b: {b}")
print(f"args: {args}")
print(f"c: {c}")
print(f"kwargs: {kwargs}")

complex_function(1, 2, 3, 4, 5, c=20, x=100, y=200)
# a: 1
# b: 2
# args: (3, 4, 5)
# c: 20
# kwargs: {'x': 100, 'y': 200}

# 실용 예제: 유연한 API 래퍼
def api_request(endpoint, method="GET", *params, **headers):
"""API 요청 시뮬레이션"""
print(f"Endpoint: {endpoint}")
print(f"Method: {method}")
print(f"Params: {params}")
print(f"Headers: {headers}")

api_request("/users", "POST", "data1", "data2",
Authorization="Bearer token",
Content_Type="application/json")

스코프 (Scope)

지역 변수와 전역 변수

# 전역 변수
global_var = "전역"

def my_function():
# 지역 변수
local_var = "지역"
print(global_var) # 읽기 가능
print(local_var)

my_function()
# print(local_var) # ❌ 에러: 지역 변수는 함수 밖에서 접근 불가

# 변수 조회 순서: LEGB
# L: Local (지역)
# E: Enclosing (둘러싼 함수)
# G: Global (전역)
# B: Built-in (내장)

x = "global"

def outer():
x = "enclosing"

def inner():
x = "local"
print(x) # local

inner()
print(x) # enclosing

outer()
print(x) # global

global 키워드

# 전역 변수 수정
count = 0

def increment():
global count
count += 1

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

# ⚠️ global 남용 주의
# ❌ 나쁜 예
total = 0

def add(x):
global total
total += x

# ✅ 좋은 예
def add_to_total(total, x):
return total + x

total = 0
total = add_to_total(total, 5)

nonlocal 키워드

# 둘러싼 함수의 변수 수정
def outer():
count = 0

def inner():
nonlocal count
count += 1
print(f"Inner count: {count}")

inner() # Inner count: 1
inner() # Inner count: 2
print(f"Outer count: {count}") # Outer count: 2

outer()

# 클로저 (Closure) 예제
def make_counter():
count = 0

def counter():
nonlocal count
count += 1
return count

return counter

counter1 = make_counter()
print(counter1()) # 1
print(counter1()) # 2
print(counter1()) # 3

counter2 = make_counter()
print(counter2()) # 1 (독립적)

재귀 함수 (Recursion)

기본 재귀

# 팩토리얼
def factorial(n):
"""n! = n × (n-1) × ... × 2 × 1"""
if n <= 1:
return 1
return n * factorial(n - 1)

print(factorial(5)) # 120 (5 × 4 × 3 × 2 × 1)

# 피보나치
def fibonacci(n):
"""피보나치 수열"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

for i in range(10):
print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13 21 34

# 카운트다운
def countdown(n):
"""재귀적 카운트다운"""
if n <= 0:
print("발사!")
return
print(n)
countdown(n - 1)

countdown(5)

재귀 vs 반복

# 재귀 버전
def sum_recursive(n):
if n <= 0:
return 0
return n + sum_recursive(n - 1)

# 반복 버전
def sum_iterative(n):
total = 0
for i in range(1, n + 1):
total += i
return total

print(sum_recursive(100)) # 5050
print(sum_iterative(100)) # 5050

# 성능 비교
import time

n = 30
start = time.time()
fibonacci(n)
print(f"재귀: {time.time() - start:.4f}초")

# 재귀는 느릴 수 있음 (중복 계산)
# 해결책: 메모이제이션 (나중에 배울 내용)

유용한 재귀 예제

# 디렉토리 탐색 시뮬레이션
def print_tree(path, level=0):
"""트리 구조 출력"""
indent = " " * level
print(f"{indent}- {path}")

# 실제로는 os.listdir 사용
if path == "root":
print_tree("folder1", level + 1)
print_tree("folder2", level + 1)
elif path == "folder1":
print_tree("file1.txt", level + 1)
print_tree("file2.txt", level + 1)

print_tree("root")

# 이진 탐색 (Binary Search)
def binary_search(arr, target, left=0, right=None):
"""정렬된 배열에서 이진 탐색"""
if right is None:
right = len(arr) - 1

if left > right:
return -1 # 찾지 못함

mid = (left + right) // 2

if arr[mid] == target:
return mid
elif arr[mid] > target:
return binary_search(arr, target, left, mid - 1)
else:
return binary_search(arr, target, mid + 1, right)

numbers = [1, 3, 5, 7, 9, 11, 13, 15]
print(binary_search(numbers, 7)) # 3
print(binary_search(numbers, 10)) # -1

# 거듭제곱 (분할 정복)
def power(base, exp):
"""효율적인 거듭제곱"""
if exp == 0:
return 1
if exp == 1:
return base

if exp % 2 == 0:
half = power(base, exp // 2)
return half * half
else:
return base * power(base, exp - 1)

print(power(2, 10)) # 1024

람다 함수 (Lambda)

기본 람다

# 일반 함수
def add(x, y):
return x + y

# 람다 함수
add_lambda = lambda x, y: x + y

print(add(3, 5)) # 8
print(add_lambda(3, 5)) # 8

# 즉시 실행
print((lambda x: x ** 2)(5)) # 25

# 간단한 연산에 유용
square = lambda x: x ** 2
cube = lambda x: x ** 3

print(square(4)) # 16
print(cube(3)) # 27

람다 활용

# 정렬 키로 사용
students = [
{"name": "홍길동", "score": 85},
{"name": "김철수", "score": 92},
{"name": "이영희", "score": 78}
]

# 점수로 정렬
sorted_students = sorted(students, key=lambda x: x["score"])
print([s["name"] for s in sorted_students])
# ['이영희', '홍길동', '김철수']

# 여러 조건
points = [(1, 5), (3, 2), (1, 3), (3, 1)]
sorted_points = sorted(points, key=lambda p: (p[0], -p[1]))
print(sorted_points) # [(1, 5), (1, 3), (3, 2), (3, 1)]

# 조건부 연산
numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2 if x % 2 == 0 else x, numbers))
print(result) # [1, 4, 3, 8, 5]

람다 제한사항

# ✅ 가능: 간단한 표현식
valid = lambda x: x ** 2

# ❌ 불가능: 여러 문장
# invalid = lambda x:
# y = x ** 2
# return y

# ❌ 불가능: 복잡한 로직
# 이런 경우 일반 함수 사용
def complex_operation(x):
if x > 0:
result = x ** 2
else:
result = 0
return result

# ⚠️ 람다 남용 주의
# 가독성이 떨어지면 일반 함수 사용

실전 예제

계산기 함수

def calculator(operation, a, b):
"""다양한 연산 수행"""
operations = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y,
'divide': lambda x, y: x / y if y != 0 else "0으로 나눌 수 없음",
'power': lambda x, y: x ** y,
'mod': lambda x, y: x % y
}

if operation in operations:
return operations[operation](a, b)
else:
return "지원하지 않는 연산"

print(calculator('add', 10, 5)) # 15
print(calculator('multiply', 4, 3)) # 12
print(calculator('power', 2, 8)) # 256
print(calculator('divide', 10, 0)) # 0으로 나눌 수 없음

비밀번호 검증

def validate_password(password):
"""비밀번호 유효성 검사"""
# 최소 길이
if len(password) < 8:
return False, "비밀번호는 최소 8자 이상이어야 합니다"

# 대문자 포함
if not any(c.isupper() for c in password):
return False, "대문자를 포함해야 합니다"

# 소문자 포함
if not any(c.islower() for c in password):
return False, "소문자를 포함해야 합니다"

# 숫자 포함
if not any(c.isdigit() for c in password):
return False, "숫자를 포함해야 합니다"

# 특수문자 포함
special_chars = "!@#$%^&*"
if not any(c in special_chars for c in password):
return False, f"특수문자({special_chars})를 포함해야 합니다"

return True, "유효한 비밀번호입니다"

# 테스트
passwords = [
"short",
"alllowercase123!",
"ALLUPPERCASE123!",
"NoNumbers!",
"NoSpecial123",
"Valid123!"
]

for pwd in passwords:
valid, message = validate_password(pwd)
print(f"{pwd:20}{message}")

통계 함수 모음

def statistics(numbers):
"""다양한 통계 값 계산"""
if not numbers:
return None

# 기본 통계
total = sum(numbers)
count = len(numbers)
mean = total / count

# 중앙값
sorted_nums = sorted(numbers)
mid = count // 2
if count % 2 == 0:
median = (sorted_nums[mid - 1] + sorted_nums[mid]) / 2
else:
median = sorted_nums[mid]

# 최빈값
from collections import Counter
counter = Counter(numbers)
mode = counter.most_common(1)[0][0]

# 범위
range_val = max(numbers) - min(numbers)

# 분산과 표준편차
variance = sum((x - mean) ** 2 for x in numbers) / count
std_dev = variance ** 0.5

return {
"count": count,
"sum": total,
"mean": mean,
"median": median,
"mode": mode,
"min": min(numbers),
"max": max(numbers),
"range": range_val,
"variance": variance,
"std_dev": std_dev
}

# 테스트
data = [85, 90, 78, 92, 85, 88, 95, 85, 80, 87]
stats = statistics(data)

for key, value in stats.items():
if isinstance(value, float):
print(f"{key}: {value:.2f}")
else:
print(f"{key}: {value}")

텍스트 포매터

def format_text(text, **options):
"""텍스트 포매팅"""
# 옵션 추출
width = options.get('width', 50)
align = options.get('align', 'left')
uppercase = options.get('uppercase', False)
lowercase = options.get('lowercase', False)
title = options.get('title', False)
border = options.get('border', False)
border_char = options.get('border_char', '*')

# 텍스트 변환
if uppercase:
text = text.upper()
elif lowercase:
text = text.lower()
elif title:
text = text.title()

# 정렬
if align == 'left':
formatted = text.ljust(width)
elif align == 'right':
formatted = text.rjust(width)
elif align == 'center':
formatted = text.center(width)
else:
formatted = text

# 테두리
if border:
border_line = border_char * (width + 4)
formatted = f"{border_line}\n{border_char} {formatted} {border_char}\n{border_line}"

return formatted

# 테스트
print(format_text("Hello Python", width=30, align='center'))
print()
print(format_text("Important", uppercase=True, border=True, width=20))
print()
print(format_text("Title Case Example", title=True, align='center', width=40))

재귀적 팰린드롬 검사

def is_palindrome(text):
"""재귀로 팰린드롬 검사"""
# 문자만 추출 (공백, 구두점 제거)
text = ''.join(c.lower() for c in text if c.isalnum())

def check(s):
if len(s) <= 1:
return True
if s[0] != s[-1]:
return False
return check(s[1:-1])

return check(text)

# 테스트
test_cases = [
"A man, a plan, a canal: Panama",
"race a car",
"Was it a car or a cat I saw?",
"Madam",
"Python"
]

for case in test_cases:
result = "팰린드롬" if is_palindrome(case) else "팰린드롬 아님"
print(f"{case:35}{result}")

함수 타이머 데코레이터 (기초)

import time

def timer(func):
"""함수 실행 시간 측정"""
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} 실행 시간: {end - start:.4f}초")
return result
return wrapper

@timer
def slow_function():
"""시간이 걸리는 함수"""
total = 0
for i in range(1000000):
total += i
return total

result = slow_function()
# slow_function 실행 시간: 0.0521초

자주 묻는 질문

Q1. 함수는 언제 만들어야 하나요?

A: 다음 경우에 함수를 만드세요

# 1. 같은 코드가 반복될 때
# 2. 코드가 너무 길어질 때
# 3. 독립적인 작업을 수행할 때
# 4. 테스트가 필요한 로직일 때

# 좋은 함수의 특징
# - 하나의 작업만 수행
# - 이름만 봐도 역할을 알 수 있음
# - 입력과 출력이 명확
# - 부작용(side effect)이 없거나 최소화

Q2. return과 print의 차이는?

A: return은 값을 반환, print는 화면에 출력

def add_print(a, b):
print(a + b) # 화면에만 출력

def add_return(a, b):
return a + b # 값 반환

# print는 결과를 사용할 수 없음
result1 = add_print(3, 5) # 8 출력
print(result1) # None

# return은 결과를 사용할 수 있음
result2 = add_return(3, 5) # 출력 없음
print(result2) # 8
print(result2 * 2) # 16 (재사용 가능)

Q3. 매개변수가 너무 많으면 어떻게 하나요?

A: 딕셔너리나 객체로 그룹화

# ❌ 매개변수가 너무 많음
def create_user(name, email, age, city, country, phone, address, zipcode):
pass

# ✅ 딕셔너리로 그룹화
def create_user(user_info):
name = user_info['name']
email = user_info['email']
# ...

user = {
'name': '홍길동',
'email': 'hong@example.com',
'age': 25
}
create_user(user)

# ✅ **kwargs 사용
def create_user(**user_info):
print(user_info['name'])
print(user_info['email'])

create_user(name='홍길동', email='hong@example.com', age=25)

Q4. 함수 안에서 함수를 정의할 수 있나요?

A: 네, 중첩 함수(nested function)가 가능합니다

def outer(x):
def inner(y):
return x + y
return inner

add_five = outer(5)
print(add_five(3)) # 8
print(add_five(7)) # 12

# 유용한 경우: 헬퍼 함수
def process_data(data):
def clean(text):
return text.strip().lower()

def validate(text):
return len(text) > 0

cleaned = [clean(item) for item in data]
return [item for item in cleaned if validate(item)]

다음 단계

함수의 기초를 마스터했습니다!

핵심 정리:
✅ 함수 정의와 호출
✅ 매개변수 (위치, 키워드, 기본값, *args, **kwargs)
✅ 반환값과 여러 값 반환
✅ 스코프 (local, global, nonlocal)
✅ 재귀 함수
✅ 람다 함수 기초

다음 단계: 고급 함수에서 람다, 데코레이터, 제너레이터를 배워보세요!