1. 함수의 필요성과 기본 개념
함수를 사용하지 않고 코드를 작성할 경우 다음과 같은 문제점이 발생합니다.
- 코드의 구조가 불분명해지고 가독성이 크게 저하됩니다.
- 동일한 기능을 수행하는 코드를 반복해서 작성해야 하므로 중복이 발생합니다.
- 기능을 확장하거나 수정해야 할 때, 해당 로직이 포함된 모든 부분을 일일이 찾아 변경해야 하므로 유지보수가 매우 어렵습니다.
함수는 현실 세계의 '공구'에 비유할 수 있습니다. 수리공이 망치나 렌치를工具箱에 미리 준비해 두었다가 필요할 때 꺼내 쓰듯, 프로그래머도 특정 기능을 수행하는 코드 블록을 함수로 미리 정의해 두고 재사용합니다. Python에서는 해석기에 미리 내장된 len(), sum(), max()와 같은 내장 함수와, 개발자의 요구사항에 맞춰 직접 구현하는 사용자 정의 함수로 구분됩니다.
2. 함수 정의와 호출의 핵심 원칙
사용자 정의 함수 문법
함수를 정의할 때는 def 키워드를 사용하며, 함수명은 그 기능을 명확히 반영해야 합니다.
def verify_access(user_id: str, token: str) -> int:
"""
접근 권한 검증 함수
:param user_id: 사용자 ID
:param token: 인증 토큰
:return: 인증 상태 코드
"""
if user_id == 'admin' and token == 'valid_token':
return 200
return 401
# print(verify_access.__annotations__)
uid = input('ID 입력: ').strip()
tkn = input('Token 입력: ').strip()
status = verify_access(uid, tkn)
print(f"상태 코드: {status}")
선 정의 후 호출 원칙
함수는 변수와 마찬가지로 반드시 메모리에 정의된 후에 호출될 수 있습니다. 정의되지 않은 함수를 호출하면 존재하지 않는 변수를 참조할 때와 동일한 오류가 발생합니다.
# 테스트 1: 호출 시점에 process_b가 정의되지 않음
def process_a():
print('Processing A')
process_b() # NameError 발생
# 테스트 2: 정상적인 정의 순서
def process_b():
print('Processing B')
def process_a():
print('Processing A')
process_b()
process_a() # 정상 실행
함수의 정의 단계에서는 문법적 오류(Syntax Error)만 검사할 뿐, 내부 코드를 실제로 실행하지는 않습니다. 따라서 논리적 오류는 함수가 호출되는 실행 단계에서 발견됩니다.
함수의 세 가지 형태
- 매개변수 없는 함수: 외부 입력 없이 단순한 동작(예: 화면 출력, 로그 기록)을 수행할 때 사용합니다.
- 매개변수 있는 함수: 외부에서 전달된 데이터를 기반으로 연산이나 로직을 처리할 때 사용합니다.
- 빈 함수:
pass키워드를 사용하여 향후 구현할 코드의 구조와 인터페이스를 설계할 때 사용합니다.
3. 함수의 반환값과 호출 형태
함수 호출 시 return 문을 통해 결과를 반환할 수 있습니다. return이 없으면 None을 반환하며, 쉼표로 구분된 여러 값을 반환하면 자동으로 튜플(Tuple)로 묶여 반환됩니다.
- 반환값이 필요한 경우: 일련의 연산을 거쳐 최종 결과물이 필요한 로직 (주로 매개변수가 있는 함수)
- 반환값이 필요 없는 경우: 단순한 상태 변경이나 출력 등 부수 효과(Side effect)만 필요한 로직 (주로 매개변수가 없는 함수)
함수 호출은 독립적인 문장 형태(foo()), 표현식의 일부(3 * len('data')), 또는 다른 함수의 전달인자(range(len('data'))) 형태로 활용될 수 있습니다.
4. 매개변수의 종류와 고급 활용
함수 정의 시 사용하는 변수를 매개변수(Parameter), 호출 시 전달하는 실제 값을 전달인자(Argument)라고 합니다.
기본 매개변수 형식
- 위치 인수: 왼쪽에서 오른쪽으로 순서에 맞춰 전달해야 하는 필수 매개변수입니다.
- 키워드 인수:
key=value형태로 전달하여 순서에 구애받지 않습니다. 단, 위치 인수 뒤에 위치해야 하며 동일한 매개변수에 중복 전달할 수 없습니다. - 기본 매개변수: 정의 단계에서 미리 기본값을 할당합니다. 호출 시 값을 생략할 수 있으며, 주로 변경 빈도가 낮은 옵션에 사용합니다. (주의: 기본값은 가변 객체보다 불변 객체로 설정해야 합니다.)
가변 길이 매개변수 (*args, **kwargs)
전달인자의 개수가 유동적일 때 사용합니다.
# *args: 위치 인수들을 튜플로 수집
def calculate_total(base_price, *discounts):
print(f"Base: {base_price}, Discounts: {discounts}")
calculate_total(100, 10, 20, 5)
# **kwargs: 키워드 인수들을 딕셔너리로 수집
def apply_settings(theme, **options):
print(f"Theme: {theme}, Options: {options}")
apply_settings('dark', font='Arial', size=14)
# *args와 **kwargs 결합 및 언패킹(Unpacking)
def execute_task(task_id, *args, **kwargs):
print(f"Task {task_id} | Args: {args} | Kwargs: {kwargs}")
execute_task(1, 'data1', 'data2', priority='high', retries=3)
키워드 전용 매개변수 (Keyword-only Arguments)
* 뒤에 정의된 매개변수는 반드시 키워드 형태로만 전달해야 합니다. 이를 통해 특정 옵션이 명시적으로 전달되도록 강제할 수 있습니다.
def configure_network(ip, port, *, protocol='tcp', timeout=30):
print(f"Connecting to {ip}:{port} via {protocol} (timeout: {timeout}s)")
configure_network('192.168.1.1', 8080, protocol='udp', timeout=10)
5. 실전 문제 및 코드 구현
문제 1: 파일 경로를 받아 특정 문자열을 다른 문자열로 일괄 변경하는 함수를 작성하세요.
문제 2: 문자열을 입력받아 대문자, 소문자, 숫자, 특수문자의 개수를 각각 계산하여 반환하는 함수를 작성하세요.
문제 3: 전달된 시퀀스(문자열, 리스트, 튜플)의 길이가 특정 임계값(기본값 10)을 초과하는지 판단하는 함수를 작성하세요.
문제 4: 리스트를 입력받아 요소의 개수가 3개를 초과하면 앞의 3개 요소만 잘라서 반환하는 함수를 작성하세요.
문제 5: 리스트나 튜플에서 인덱스가 짝수(0, 2, 4...)인 요소만 추출하여 새로운 리스트로 반환하는 함수를 작성하세요.
문제 6: 딕셔너리의 각 값(Value)의 길이가 3을 초과할 경우, 앞의 3개 요소만 남기고 나머지는 버린 새로운 딕셔너리를 반환하는 함수를 작성하세요.
# 문제 1 풀이: 파일 내용 일괄 변경
def replace_text_in_file(file_path, target, replacement):
import os
temp_path = f"{file_path}.temp"
with open(file_path, 'r', encoding='utf-8') as src, \
open(temp_path, 'w', encoding='utf-8') as dst:
for line in src:
dst.write(line.replace(target, replacement))
os.replace(temp_path, file_path)
# 문제 2 풀이: 문자열 유형별 통계
def analyze_characters(text):
stats = {'upper': 0, 'lower': 0, 'digit': 0, 'special': 0}
for char in text:
if char.isupper():
stats['upper'] += 1
elif char.islower():
stats['lower'] += 1
elif char.isdigit():
stats['digit'] += 1
else:
stats['special'] += 1
return stats
# 문제 3 풀이: 시퀀스 길이 제한 판단
def is_length_exceeding_limit(sequence, limit=10):
return len(sequence) > limit
# 문제 4 풀이: 리스트 요소 자르기
def truncate_list(data):
return data[:3] if len(data) > 3 else data
# 문제 5 풀이: 짝수 인덱스 요소 추출
def get_even_index_elements(seq):
return list(seq[::2])
# 문제 6 풀이: 딕셔너리 값 길이 제한 및 자르기
def trim_dictionary_values(mapping):
trimmed = {}
for key, value in mapping.items():
if len(value) > 3:
trimmed[key] = value[:3]
else:
trimmed[key] = value
return trimmed
# 실행 예시
print(analyze_characters("Hello World 2023!"))
print(truncate_list([1, 2, 3, 4, 5]))
print(get_even_index_elements(['a', 'b', 'c', 'd', 'e']))
print(trim_dictionary_values({'k1': 'abcdef', 'k2': [1, 2], 'k3': (1, 2, 3, 4)}))