파이썬 함수 완벽 가이드 기초부터 핵심 활용까지

읽기 예상 시간: 8분

파이썬에서 코드를 효율적으로 재사용하려면 함수의 개념을 정확히 이해해야 해요. def 키워드로 시작하는 기본 구조부터, 가변 인자와 람다(lambda)를 활용한 유연한 코드 작성법까지 폭넓게 다뤄볼게요. 여기에 더해 나중에 코드를 수정하기 편하도록 돕는 Docstring과 타입 힌트 작성법까지 함께 알아두면 훨씬 더 탄탄한 프로그램을 만들 수 있어요.

목차

함수가 없으면 어떻게 될까요? (함수의 필요성)

코딩을 하다 보면 똑같은 코드를 여기서도 쓰고, 저기서도 쓰고, 또 다른 파일에서도 복사해서 쓴 경험 한 번쯤 있으시죠? 처음 복사해서 붙여넣기 할 때는 편하지만, 나중에 수정할 일이 생기면 그때부터 진짜 고생이 시작돼요.

만약 텍스트 위아래로 그어주는 구분선의 기호를 하나만 바꾸고 싶다고 가정해 볼게요. 코드를 10군데에 복사해 뒀다면, 그 10곳을 전부 찾아서 일일이 고쳐야 해요. 하나라도 빠뜨리면 화면이 이상하게 나오겠죠. 함수는 바로 이런 지루하고 반복적인 작업을 없애려고 만들어요. 코드를 한 곳에 예쁘게 모아두고 이름표를 붙여둔 다음, 필요할 때마다 그 이름만 불러서 쓰는 거예요.

함수를 쓰기 전과 후의 코드가 어떻게 달라지는지 눈으로 직접 비교해 볼게요.

python
without_function.py
# 함수가 없을 때는 같은 로직을 매번 복사해서 붙여넣어야 해요.
print("====================")
print("첫 번째 결과 출력")
print("====================")

# 또 필요하면 아래에 똑같이 적어야 하죠.
print("====================")
print("두 번째 결과 출력")
print("====================")

# 함수를 쓰면 한 번 정의해두고 이름만 불러서 여러 번 호출해요.
def print_box(message):
    print("====================")
    print(message)
    print("====================")

print_box("첫 번째 결과 출력")
print_box("두 번째 결과 출력")

이제 테두리 모양을 등호(=)에서 하이픈(-) 기호로 바꾸고 싶다면 어떻게 하면 될까요? 복사 붙여넣기를 한 코드는 여러 줄을 수정해야 하지만, 함수를 만들었다면 print_box 안의 테두리 출력 코드 딱 두 줄만 고치면 돼요. 100번을 호출했든 1000번을 호출했든 한 번에 싹 바뀌니까 유지보수가 엄청나게 편해져요.

흩어진 서류를 하나의 박스에 깔끔하게 정리하는 모습

파이썬 함수의 기본 구조와 다중 반환(Multiple Return)

매개변수와 인자, 그리고 반환값

함수를 만들 때는 def라는 키워드를 써서 정의해요. 여기서 초보자들이 가장 많이 헷갈려하는 용어가 바로 매개변수(parameter)와 인자(argument)예요.

쉽게 비유하자면, 매개변수는 붕어빵을 구울 때 쓰는 ‘빈 틀’ 같은 거고, 인자는 그 틀 안에 실제로 부어 넣는 ‘팥’이나 ‘슈크림’ 같은 실제 재료라고 생각하시면 돼요. 그리고 함수가 안에서 열심히 계산을 끝마치면 return 키워드를 통해 그 결과물(잘 구워진 붕어빵)을 바깥으로 돌려줘요.

1
이름과 재료(매개변수) 정하기

def 키워드 뒤에 함수의 이름과 괄호를 적고, 그 안에 받을 재료의 이름을 정해줘요.

2
들여쓰기 후 핵심 로직 작성

콜론(:)을 찍고 다음 줄로 넘어가서 스페이스바 4칸을 띄운 뒤, 실제로 실행할 코드를 쭉 작성해요.

3
return으로 결과 돌려주기

계산이 끝났다면 return 키워드 옆에 돌려줄 값을 적어요. 이 단계를 빼먹으면 파이썬은 아무것도 없다는 뜻의 None을 돌려줘요.

기본적인 함수 구조와 함께 여러 개의 값을 한 번에 돌려받는 코드를 살펴볼게요. 파이썬만의 아주 강력한 기능이랍니다.

python
basic_return.py
# 매개변수 a와 b를 받아서 합과 차를 동시에 반환하는 함수예요.
def calculate(a, b):  
    # 여기서 a와 b가 외부에서 값을 받아오는 구멍, 즉 '매개변수'예요.
    sum_value = a + b
    diff_value = a - b
    
    # 파이썬은 쉼표만 찍어주면 여러 값을 튜플이라는 하나의 묶음으로 반환해요.
    return sum_value, diff_value

# 10과 3이라는 실제 '인자'를 넣고 부르면, 반환된 두 개의 값이 각 변수에 차례대로 담겨요.
my_sum, my_diff = calculate(10, 3)

print("두 수의 합:", my_sum)   # 13
print("두 수의 차:", my_diff)  # 7

복잡한 계산 결과를 바깥으로 꺼내기 위해 굳이 파이썬 전역변수 지역변수 개념을 억지로 섞어 쓰지 않아도 돼요. C나 Java 같은 다른 언어에서는 값을 여러 개 반환하려면 복잡한 구조체나 배열을 새로 만들어야 하지만, 파이썬에서는 쉼표 하나로 깔끔하게 다중 반환이 가능해요. 코드가 훨씬 간결해지겠죠?

하나의 기계에 재료를 넣고 두 개의 결과물이 나오는 모습

나와 팀원을 위한 배려, Docstring (함수 문서화)

내가 방금 열심히 짠 함수도 한 달 뒤에 다시 열어보면 “내가 이걸 왜 이렇게 만들었지?”, “여기엔 무슨 값을 넣어야 하더라?” 하고 까먹기 십상이에요. 그래서 함수를 만들 때는 그 함수 바로 아래에 큰따옴표 세 개(""")를 열고 닫아서 이 함수의 사용법을 꼼꼼하게 적어두는 것이 좋아요. 이를 Docstring이라고 불러요.

파이썬 def 문으로 함수를 정의할 때, 마치 파이썬 공식 문서처럼 예쁘게 설명을 남기는 방법을 확인해 볼게요.

python
docstring_example.py
# 함수 이름 바로 아랫줄에 큰따옴표 3개를 연달아 써서 설명을 작성해요.
def calculate_area(width, height):
    """
    사각형의 넓이를 계산하여 반환하는 함수예요.
    
    매개변수:
    width: 사각형의 가로 길이 (숫자)
    height: 사각형의 세로 길이 (숫자)
    
    반환값:
    가로와 세로를 곱한 넓이 (숫자)
    """
    return width * height

# 다른 사람이 이 함수를 쓸 때 help()를 이용해 사용법을 바로 확인할 수 있어요.
help(calculate_area)

Docstring을 꼼꼼하게 적어두면 나중에 코드를 유지보수할 때 파일을 다 뒤져보지 않아도 이 함수가 무슨 역할을 하는지 바로 알 수 있어요. 협업을 할 때 동료들에게 칭찬받는 아주 좋은 습관이랍니다.

💡 Tip

help() 함수를 굳이 출력하지 않아도, VS Code나 PyCharm 같은 코드 에디터에서 함수 이름 위에 마우스를 가만히 올려두면 작성해둔 Docstring이 팝업 창으로 짠 하고 나타나요. 이게 개발할 때 엄청나게 편리해요.

기계나 도구에 상세한 설명서가 홀로그램으로 떠 있는 모습

기본값과 키워드 인자 — 더 편리한 함수 호출

함수를 부를 때 항상 똑같은 값을 넣어야 하는 경우가 있어요. 카페에서 커피를 주문할 때 90%의 손님이 아메리카노를 마신다면, 종업원이 “무슨 커피 드릴까요?” 묻기 전에 기본으로 아메리카노를 세팅해두면 편하겠죠?

파이썬 함수도 마찬가지예요. 거듭제곱을 계산하는데 대부분 제곱(2)만 쓴다면, 굳이 매번 숫자 2를 입력하게 만들 필요가 없어요. 이때 매개변수에 기본값을 설정해 두면 호출할 때 그 값을 생략할 수 있어서 타이핑이 확 줄어들어요.

또한 함수를 호출할 때 순서대로 값을 넣는 대신, 매개변수 이름을 직접 적어서 전달하는 방식을 키워드 인자라고 해요. 이걸 쓰면 순서를 뒤죽박죽으로 넣어도 파이썬이 알아서 제자리를 찾아서 값을 꽂아줘요.

python
default_args.py
# exp 매개변수에 '=2'를 붙여서 기본값을 설정했어요.
def power(base, exp=2):
    return base ** exp

# exp 자리에 아무것도 안 적으면 알아서 기본값 2가 적용돼요.
print(power(3))      # 3의 2제곱 결과인 9가 출력돼요.

# 필요할 때는 기본값을 무시하고 새로운 값을 넣어도 돼요.
print(power(3, 3))   # 3의 3제곱 결과인 27이 출력돼요.

# 키워드 인자를 쓰면 순서를 마음대로 바꿔서 전달할 수 있어요.
# 헷갈리지 않게 이름을 콕 집어서 알려주는 거예요.
print(power(exp=3, base=2))  # 2의 3제곱 결과인 8이 출력돼요.

⚠️ Warning

기본값이 있는 매개변수는 반드시 기본값이 없는 매개변수보다 뒤쪽에 적어줘야 해요. 예를 들어 def power(exp=2, base): 처럼 적으면 파이썬이 값을 넣을 때 짝을 맞추지 못해서 곧바로 문법 에러(SyntaxError)를 뱉어내요.

다양한 옵션 버튼과 기본 모드가 있는 고급 커피 머신

몇 개가 올지 모를 땐? *args와 **kwargs

우리가 함수를 만들 때 값을 2개만 받을지, 아니면 10개를 받을지 미리 알 수 없는 상황이 종종 발생해요. 쇼핑몰 장바구니에 담긴 물건들의 가격을 전부 더하는 함수를 만든다고 생각해 보세요. 어떤 손님은 물건을 1개만 살 수도 있고, 어떤 손님은 50개를 살 수도 있잖아요?

이럴 때 매개변수 앞에 별 기호(*)를 하나 붙여주면, 파이썬이 넘쳐나는 인자들을 모조리 모아서 ‘튜플’이라는 하나의 보따리로 묶어줘요. 반대로 이름표가 달린 키워드 인자들을 무제한으로 받고 싶다면 별을 두 개(**) 붙여주면 돼요. 그러면 파이썬이 이를 ‘딕셔너리’ 형태로 깔끔하게 정리해 줘요.

python
args_kwargs.py
# *args를 사용해 개수 제한 없이 숫자를 받아 모두 더하는 함수예요.
def total_price(*args):
    result = 0
    # 건네받은 args는 튜플 형태라서 for문으로 하나씩 꺼내 쓸 수 있어요.
    for price in args:
        result += price
    return result

# 값을 2개 넣든 3개 넣든 문제없이 작동해요.
print(total_price(1000, 2000))        # 3000
print(total_price(500, 1000, 3000))   # 4500


# **kwargs를 사용해 회원 정보를 이름표가 붙은 채로 무제한으로 받아요.
def print_user_info(**kwargs):
    # kwargs는 딕셔너리 형태가 되니까 .items()로 키와 값을 같이 꺼내요.
    for key, value in kwargs.items():
        print(f"{key}: {value}")

# 넣고 싶은 정보(키워드)를 마음대로 추가해서 보낼 수 있어요.
print_user_info(name="김개발", age=30, job="데이터 엔지니어", city="서울")

데이터 개수가 상황에 따라 변할 때는 외부에서 리스트를 억지로 만들어서 넘겨줄 수도 있지만, *args**kwargs를 쓰면 함수를 호출하는 쪽의 코드가 훨씬 직관적이고 깔끔해져요. 다른 사람이 만든 라이브러리를 열어보면 이 문법이 정말 밥 먹듯이 나오니까 꼭 눈에 익혀두는 게 좋아요.

수많은 크기의 택배 상자를 무한히 담고 있는 거대한 카트

한 줄로 끝내는 마법, 람다(lambda) 함수

딱 한 번 쓰고 버릴 아주 간단한 계산을 해야 하는데, 굳이 파일 위쪽에 def를 써가며 거창하게 이름을 지어주고 들여쓰기까지 하기 귀찮을 때가 있죠? 마치 식당에서 쓰는 일회용 나무젓가락처럼 가볍게 쓰고 싶을 때 말이에요.

이때 lambda 키워드를 쓰면 단 한 줄짜리 이름 없는 함수(익명 함수)를 뚝딱 만들 수 있어요. 람다는 특히 리스트 안에 있는 데이터들을 특정한 기준에 따라 정렬하고 싶을 때 sorted() 함수의 key 조건으로 넘겨주기 아주 안성맞춤이에요.

python
lambda_sort.py
# 학생 이름과 점수가 튜플로 묶여서 리스트에 들어있어요.
students = [("김파이썬", 85), ("이자바", 92), ("박스크립트", 78)]

# 점수(튜플의 인덱스 1번)를 기준으로 내림차순(reverse=True) 정렬하고 싶어요.
# 여기서 lambda s: s[1] 이라는 코드가 바로 "s가 들어오면 s의 1번째 요소를 줘"라는 뜻이에요.
sorted_desc = sorted(students, key=lambda s: s[1], reverse=True)

# 정렬된 결과를 예쁘게 출력해 볼게요.
for name, score in sorted_desc:
    print(f"{name}: {score}점")

📌 Note

람다는 코드를 짧게 만들어준다는 강력한 장점이 있지만, 너무 복잡한 계산이나 여러 개의 if문을 억지로 구겨 넣으려고 하면 가독성이 최악으로 떨어져요. 코드가 길어질 것 같으면 주저하지 말고 일반적인 def 함수로 따로 빼서 쓰는 것이 훨씬 좋습니다.

복잡한 도구 대신 사용하는 작고 날렵한 다용도 도구

스코프(Scope) — 전역 변수와 지역 변수

파이썬에는 변수가 살아 숨 쉴 수 있는 유효 범위, 즉 스코프(Scope)라는 규칙이 있어요. 방 안에서만 가지고 노는 장난감이 있고, 거실에 두고 온 가족이 쓰는 TV가 있는 것처럼요.

함수 안에서 만든 변수는 ‘지역 변수’라고 해서, 함수가 일을 마치고 종료되면 메모리에서 흔적도 없이 완전히 사라져요. 반대로 함수 바깥의 가장 바깥쪽 줄에서 만든 변수는 ‘전역 변수’라고 해요. 이 전역 변수는 함수 안에서 자유롭게 읽어볼 수는 있지만, 내 마음대로 수정해서 값을 덮어쓸 수는 없도록 안전장치가 걸려 있어요.

만약 함수 안에서 바깥에 있는 전역 변수의 값을 꼭 바꿔야만 한다면 global이라는 키워드를 써서 파이썬에게 “나 이거 바깥에 있는 그 변수 수정할 거야”라고 명시적으로 허락을 받아야 해요.

python
scope_test.py
count = 0  # 이 녀석은 함수 밖에서 만든 '전역 변수'예요.

def increment():
    # global 키워드를 안 쓰고 그냥 count += 1 을 하면 에러가 나요.
    # 바깥에 있는 count를 가져다 쓰겠다고 선언해줍니다.
    global count
    count += 1

# 함수를 두 번 호출해 볼게요.
increment()
increment()

# 전역 변수의 값이 실제로 증가했는지 확인해 봅니다.
print(count)  # 2가 출력돼요.

❗ 중요

글로벌(global) 키워드를 너무 많이 쓰면 여기저기서 변수 값을 바꿔버리기 때문에, 나중에 코드가 꼬였을 때 도대체 어떤 함수가 범인인지 추적하기가 굉장히 어려워져요. 스파게티 코드가 되기 쉬우니 가급적 매개변수로 값을 전달받고 return으로 결과를 돌려주는 방식을 추천해요.

투명한 유리 방 안과 밖이 구분된 현대적인 사무실

안전한 코드를 위한 타입 힌트(Type Hint) 기초

파이썬은 원래 데이터 타입에 아주 관대한 언어예요. 네모난 구멍에 동그란 블록을 밀어 넣어도 일단 프로그램 실행 자체는 시켜주죠. 그런데 이게 꼭 좋은 것만은 아니에요. 함수 안에서 문자열을 잘라야 하는데 누군가 숫자를 집어넣으면, 프로그램이 돌다가 중간에 뻥 터져버리는 끔찍한 일을 겪게 돼요.

이런 초보적인 실수를 미리 막으려면 파이썬 매개변수 인자에 예상되는 데이터 타입이 무엇인지 미리 꼬리표를 달아두면 좋아요. 콜론(:)과 화살표(->)를 활용해서 함수에 타입 힌트를 적용하는 방법을 살펴볼게요.

python
type_hint.py
# name 뒤에 ': str'을 붙여서 "여긴 문자열만 와야 해"라고 알려주고,
# '-> str'을 붙여서 "이 함수는 끝나면 문자열을 돌려줄 거야"라고 약속해요.
def greet(name: str) -> str:
    return f"안녕하세요, {name}님!"

# 올바른 사용법
print(greet("김파이썬"))

# 숫자를 넣어도 파이썬이 강제로 에러를 내며 멈추지는 않아요.
# 하지만 VS Code나 PyCharm 같은 똑똑한 에디터에서는 "너 이거 타입 틀렸어!"라며 
# 빨간색 경고 밑줄을 띄워줘서 실수를 바로 알아챌 수 있어요.
print(greet(123)) 

타입 힌트는 프로그램 실행 속도에 영향을 주거나 강제로 에러를 발생시키지는 않아요. 그저 힌트일 뿐이니까요. 하지만 내가 코드를 짤 때 자동완성을 도와주고, 다른 사람이 내 함수를 쓸 때 잘못된 값을 넣는 실수를 개발 단계에서 사전에 차단해 주는 훌륭한 방어막 역할을 톡톡히 해내요.

알맞은 모양의 기하학적 블록만 들어갈 수 있는 퍼즐 상자

초보자가 자주 하는 함수 관련 실수 베스트 3

입문자가 파이썬에서 함수를 처음 만들고 써볼 때 가장 많이 겪게 되는 어이없는 문법, 논리 에러 세 가지를 짚어드릴게요. 사실 처음에는 누구나 다 이 함정에 한 번씩 빠진답니다. 이 실수들의 원인만 정확히 알고 피하셔도, 원인을 못 찾아 밤새워 디버깅하는 시간을 확 줄일 수 있을 거예요.

1
return 값을 아무 데도 저장하지 않고 공중으로 날려버리는 실수

함수가 기껏 계산을 해서 return을 해줬는데, 그걸 받아줄 변수를 안 쓰면 데이터는 그대로 사라져요. add(3, 5) 라고만 쓰면 계산만 하고 끝납니다. 반드시 result = add(3, 5) 처럼 변수에 담아서 써야 해요.

2
return이 없는 함수의 결과를 변수에 담는 실수

안에서 print()만 하고 return 문을 아예 안 쓴 함수를 msg = say_hello() 처럼 변수에 할당해 봤자, msg 안에는 아무것도 없다는 뜻의 None만 덩그러니 들어가게 돼요. 결과값이 필요하면 출력(print)이 아니라 반환(return)을 해야 해요.

3
기본값이 있는 매개변수를 맨 앞에 적어버리는 실수

def profile(age=20, name): 처럼 쓰면 파이썬은 인자가 들어왔을 때 이게 age를 덮어쓰려는 건지 name에 넣으려는 건지 판단을 못 해서 바로 SyntaxError를 발생시켜요. 기본값은 무조건 뒤쪽으로 몰아서 적어주세요.

프로그래밍의 흔한 실수를 경고하는 3개의 표지판

자주 묻는 질문

Q. 함수에 일반 주석(#)을 다는 것과 Docstring을 작성하는 것은 구체적으로 어떤 차이가 있나요?

일반 주석(#)은 함수 내부에서 “이 for문은 이런 원리로 동작해”라고 특정 복잡한 로직을 동료나 미래의 자신에게 설명할 때 써요. 반면 Docstring(""")은 함수 내부의 구조보다는, 이 함수 전체의 목적이 무엇인지, 어떤 매개변수를 넣어야 하고 어떤 결과가 나오는지를 사용자 관점에서 알려주는 공식 안내서 역할을 해요. 코드를 일일이 다 열어보지 않아도 help() 함수나 편집기 화면에서 마우스만 올려도 설명이 팝업으로 뜬다는 게 가장 큰 장점이자 차이점이에요.

Q. 함수에서 계산된 두 개 이상의 결과값을 동시에 바깥으로 빼내려면 어떻게 해야 하나요?

파이썬만의 강력한 기능인 튜플을 적극적으로 쓰면 아주 간단하게 해결돼요. 함수 마지막에 return a, b, c처럼 쉼표를 기준으로 돌려줄 변수들을 나열하면, 파이썬이 알아서 이를 하나의 튜플 묶음으로 포장해서 반환해요. 그러면 함수를 부른 바깥쪽에서는 result1, result2, result3 = my_function() 형태로 쉽고 깔끔하게 값을 나눠서 저장할 수 있어요.

Q. 함수 매개변수에 실수로 문자열 대신 숫자를 넣어서 에러가 나는 걸 미리 방지할 수 있는 방법이 있나요?

파이썬 3.5 버전부터 도입된 타입 힌트(Type Hint)를 사용해 def greet(name: str) -> str: 같은 형식으로 타입을 명시해 줄 수 있어요. C 언어나 Java처럼 타입이 틀렸다고 컴파일 단계에서 아예 실행을 막아버리는 완벽한 강제성은 파이썬에 없지만, VS Code나 PyCharm 같은 최신 에디터에서 코드를 짜는 즉시 빨간 줄로 경고를 시각적으로 해주기 때문에 버그를 사전에 잡고 예방하기가 훨씬 수월해져요.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기