1. 함수의 정의와 호출 (Function Definition)
프로그램을 만들다 보면 동일한 수식을 여러 번 반복해서 코딩해야 하는 경우가 생깁니다. 파이썬에서는 중복되는 코드 뭉치들을 하나의 이름으로 묶어 내고, 필요할 때 그 이름만 불러서 재활용할 수 있는 함수(Function) 기능을 제공합니다.
1.1 함수의 탄생: 정의와 호출 체계
함수를 만들기 위해서는 def (Define) 키워드로 선언을 시작합니다. 함수는 크게 ① 머리(Header), ② 몸통(Body), ③ 꼬리(Return) 세 부분으로 나뉩니다.
- Header (정의부): 함수의 이름표를 달고 외부에서 건네받을 재료(매개변수, Parameter)를 명시합니다. 구문의 끝에는 콜론(
:)을 붙여 본문의 시작을 알립니다. - Body (실행부): 들여쓰기(Indent) 후 함수가 실제로 수행할 구체적인 연산이나 로직을 작성합니다.
- Return (반환부): 결과를 밖으로 내보내는 꼬리 부분으로,
return키워드를 사용합니다. 만약 명시하지 않는다면 파이썬은 항상 묵시적으로 아무것도 없다는 의미인None을 반환합니다.
# 1. 함수의 정의 (Definition)
def calc_exchange_rate(dollars, rate):
"""
이런 식으로 함수 바로 밑에 작성된 큰따옴표 3개의 문자열을
함수의 도움말(Docstring)이라고 부릅니다.
"""
won = dollars * rate # 몸통 로직
return won # 결과 반환
# 2. 함수의 호출 (Call)
# 소괄호 () 연산자를 사용하여 진짜 데이터를(인자, Argument) 전달합니다.
my_money = calc_exchange_rate(100, 1300)
print(my_money) # 130000
[!WARNING] 파이썬 인터프리터는 코드를 위에서 아래로 순차적으로 읽어들입니다. 아직 정의되지 않은(def 구문이 등장하기 전) 함수를 먼저 호출하려고 하면 즉시
NameError예외가 발생하므로, 반드시 정의가 호출보다 먼저 실행되어야 합니다.
1.2 제네릭(Generic) 본능과 타입 제어
C언어나 Java와 다르게, 파이썬 함수는 매개변수의 데이터 타입(형)을 엄격하게 규정하지 않습니다. 정수를 넣든, 문자열을 넣든 무조건 들어갑니다. (이 특징을 가리켜 제네릭(Generic) 함수라고 합니다.)
이러한 유연성은 편하지만, 덧셈 연산자처럼 숫자가 들어오느냐 문자열이 들어오느냐에 따라 결과가 다르게 도출되어 버리는 논리적 약점을 만듭니다.
def generic_add(x, y):
return x + y
print(generic_add(5, 5)) # 10 (정수 덧셈)
print(generic_add("오", "점")) # "오점" (문자열 결합)
# print(generic_add({"a":1}, {"b":2})) -> 딕셔너리끼리 더하려다 TypeError 발생!
[안전한 방어 로직 추가하기]
무분별하게 엉뚱한 타입이 함수 내에 진입해 시스템을 오작동시키는 것을 막기 위해, 함수 시작 부분에서 isinstance 내장 함수를 사용해 데이터 타입을 강제로 필터링하는 방어 로직을 짤 수 있습니다.
def safe_add(x, y):
if not isinstance(x, int) or not isinstance(y, int):
raise ValueError("정수(int)만 더할 수 있습니다!") # 강제 에러 방출
return x + y
1.3 파이썬의 비밀: “함수도 결국 객체다”
파이썬의 가장 심오하고 매력적인 특징은 바로 “함수조차도 function 클래스에서 태어난 하나의 인스턴스(객체)”라는 점입니다.
우리가 함수를 정의하고 이름표를 달아주면, 파이썬은 내부적으로 메모리 상에 ‘함수 객체’를 빚어내어 활성화합니다. 이렇게 만들어진 함수 객체들은 다른 데이터들(리스트, 딕셔너리)처럼 엄청나게 융통성 있는 대우를 받습니다.
- 함수의 이름(레퍼런스)을 다른 변수에 할당해 이름을 바꿔 부를 수 있습니다.
- 인스턴스이기 때문에, 함수에 나만의 임의 속성(변수나 또 다른 함수)을 동적으로 꽂아 넣을 수 있습니다.
def my_func():
print("호출되었습니다.")
# 1. 함수형 객체가 지닌 기본 정보 파악
print(type(my_func)) # <class 'function'>
print(my_func.__name__) # 함수 이름 출력 ('my_func')
# 2. 객체이기 때문에 속성을 생성하여 저장 (마치 클래스 인스턴스처럼!)
# 함수가 불린 횟수를 누적하기 위해 변수를 함수 객체 자체에 박아넣음
my_func.call_count = 0
my_func.call_count += 1
print(my_func.call_count) # 1
[!TIP] 괄호
()를 붙이는 것은 사실 파이썬 함수 객체 내부에 기본 탑재된 숨겨진 스페셜 메서드인__call__()을 실행하라는 호출 연산자의 암호입니다. 즉,my_func()와my_func.__call__()은 기계적으로 완전히 동일한 뜻을 가집니다.