# 파이썬 함수: def, return, 인자, *args와 **kwargs, lambda, 타입 힌트

URL: https://skillaria.top/ko/guides/python/python-functions

파이썬 함수는 def 키워드로 정의하고, 이름과 괄호로 호출하며, return으로 결과를 돌려줍니다. return이 없으면 None을 반환합니다. 인자는 순서대로 또는 이름으로 전달할 수 있고, *args는 남은 위치 인자를 튜플로, **kwargs는 남은 키워드 인자를 딕셔너리로 모읍니다. lambda는 한 줄짜리 익명 함수를 만듭니다.

파이썬 함수는 이름이 붙은 코드 조각으로, 서로 다른 입력으로 여러 번 호출할 수 있습니다. `def` 키워드로 정의하고, 매개변수로 값을 받고, `return`으로 결과를 돌려줍니다. 긴 스크립트를 이해하기 쉽고 검증하기 쉬운 단계들로 나누는 가장 기본적인 방법이 함수입니다.

## def로 함수 정의하기

함수 정의는 `def`, 이름, 괄호 안의 매개변수, 콜론으로 시작합니다. 본문은 4칸 들여씁니다. 파이썬에서 들여쓰기는 장식이 아니라 문법의 일부입니다.

```
def greet(name):
    """사용자에게 줄 인사말을 반환합니다."""
    return f"안녕하세요, {name}님!"

print(greet("안나"))   # 안녕하세요, 안나님!
```

헤더 바로 아래에 있는 세 개의 따옴표 문자열은 독스트링(docstring), 즉 함수의 설명문입니다. `help(greet)`나 편집기의 도움말에 표시됩니다. 함수 이름은 PEP 8 규칙에 따라 snake_case로 씁니다: `add_expense`, `get_rate`.

`def greet(name):` 같은 한 줄을 함수의 시그니처라고 합니다. 이름과 매개변수 목록(필요하면 타입 힌트 포함)입니다. 시그니처만 봐도 본문을 읽지 않고 함수를 어떻게 호출하는지 알 수 있습니다.

## return의 동작

`return`은 함수를 끝내고, 호출한 쪽에 값을 돌려줍니다. `return`이 실행되고 나면 그 아래 코드는 실행되지 않습니다. `return`이 없거나 값 없이 쓰이면 함수는 `None`을 반환합니다.

```
def area(w, h):
    w * h          # 계산은 되지만 버려집니다

print(area(3, 4))  # None

def area(w, h):
    return w * h

print(area(3, 4))  # 12
```

return을 빠뜨리는 것은 초보자가 가장 많이 하는 실수 중 하나입니다. 함수는 실행되지만 `None`을 돌려주고, 오류는 한두 줄 뒤에서 나타납니다. 값을 여러 개 돌려줄 수도 있습니다. 실제로는 튜플 하나가 반환되며, 편리하게 풀어서 받을 수 있습니다.

```
def min_max(numbers):
    return min(numbers), max(numbers)

low, high = min_max([4, 1, 9])
print(low, high)   # 1 9
```

이른 return은 중첩을 줄이는 데 좋습니다. 예외 상황을 먼저 걸러내고, 기본 로직을 마지막에 씁니다.

```
def discount(price, is_member):
    if price  limit    # 전역 변수 읽기는 가능

def raise_limit():
    global limit
    limit = 2000             # global이 없으면 지역 변수가 새로 생깁니다
```

실무에서는 `global`을 피하는 편이 좋습니다. 바깥 상태를 바꾸는 함수는 테스트하기 어렵습니다. 값을 인자로 받고 결과를 반환하는 방식이 더 안전합니다.

## 중첩 함수와 클로저

함수 안에 함수를 정의할 수 있습니다. 안쪽 함수는 바깥 함수가 끝난 뒤에도 바깥 변수를 기억합니다. 이것을 클로저라고 합니다. 바깥 변수의 값을 바꾸려면 `nonlocal`을 씁니다.

```
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c(), c(), c())   # 1 2 3
```

클로저는 데코레이터와 함수 팩토리의 바탕입니다.

## 함수는 객체다

파이썬에서 함수는 일반 객체입니다. 변수에 담을 수 있고, 다른 함수에 넘길 수 있고, 함수에서 반환할 수 있으며, 딕셔너리에 저장할 수도 있습니다. 함수에는 `__name__`, `__doc__`, `__defaults__`, `__annotations__` 같은 속성이 있습니다.

```
def double(x):
    return x * 2

f = double
print(f(5), f.__name__)                # 10 double
print(list(map(double, [1, 2, 3])))   # [2, 4, 6]
```

## lambda: 익명 함수

lambda 함수는 이름이 없고 본문이 식 하나로 된 짧은 함수입니다. 식의 결과가 자동으로 반환되므로 `return`은 쓰지 않습니다.

```
square = lambda x: x ** 2
print(square(4))   # 16

expenses = [("커피", 250), ("택시", 600), ("점심", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses)  # [('택시', 600), ('점심', 450), ('커피', 250)]

print(list(filter(lambda n: n % 2 == 0, range(10))))  # [0, 2, 4, 6, 8]
```

lambda는 `sorted`, `max`, `map`, `filter`에 넘기는 짧은 인자로 어울립니다. 위의 `square`처럼 lambda를 변수에 대입하는 것은 PEP 8이 권장하지 않습니다. 이름이 필요하다면 `def`를 쓰세요. 그래야 트레이스백에 제대로 된 `__name__`이 나오고, 독스트링을 적을 수 있습니다.

|  | def | lambda |
| --- | --- | --- |

| 이름 | 있음 | 없음 (`<lambda>`) |

| 본문 | 명령문을 여러 개 가능 | 식 하나 |

| return | 명시적으로 씀 | 자동 |

| 독스트링, 타입 힌트 | 가능 | 불가능 |

| 적합한 경우 | 모든 경우 | 짧은 key나 콜백 |

## 타입 힌트: 힌트일 뿐 검사가 아님

타입 힌트는 매개변수와 반환값의 기대 타입을 표시합니다. 파이썬은 실행할 때 힌트를 검사하지 않습니다. `x: int`로 적힌 함수에 문자열을 넘겨도 실행은 됩니다. 검사는 mypy, pyright, ty 같은 외부 도구와 편집기가 실행 전에 해 줍니다.

```
def add_expense(title: str, amount: float, category: str = "기타") -> dict[str, object]:
    return {"title": title, "amount": amount, "category": category}

def area(w: float, h: float) -> float:
    w * h   # mypy: Missing return statement
```

설치 없이 검사하려면 `uvx mypy .`를 실행하세요. 위 예제는 타입 힌트의 장점을 보여 줍니다. `-> float`로 선언했는데 return이 없으면, 분석 도구가 실행 전에 바로 알려 줍니다.

Python 3.12(PEP 695)부터는 제네릭 함수를 `TypeVar` 없이 쓸 수 있습니다. 타입 매개변수는 대괄호에 적고, 타입 별칭은 `type` 키워드로 선언합니다.

```
def first[T](xs: list[T]) -> T:
    return xs[0]

type Money = int | float
```

Python 3.14(PEP 649)에서는 어노테이션이 지연 평가됩니다. 처음 접근할 때 계산되므로, 아래에 선언된 클래스를 따옴표 없이 참조할 수 있고, `from __future__ import annotations`도 더 이상 필요하지 않습니다.

## 인자를 안 넘긴 것과 None을 넘긴 것의 차이: Python 3.15의 sentinel

기본값을 `None`으로 두면 '인자를 전달하지 않음'과 '일부러 None을 전달함'을 구분할 수 없습니다. 예전에는 직접 만든 자리 표시자로 이 문제를 풀었습니다.

```
_MISSING = object()

def get_rate(currency, default=_MISSING):
    rates = {"USD": 90.5}
    if currency in rates:
        return rates[currency]
    if default is _MISSING:
        raise KeyError(currency)
    return default
```

Python 3.15에는 내장 함수 `sentinel`이 추가되었습니다(PEP 661). `MISSING = sentinel("MISSING")`은 고유한 표식을 만들며, repr이 읽기 쉽고, 복사해도 같은 객체로 남고, pickle로 직렬화되며, `float | MISSING` 같은 타입 힌트에도 쓸 수 있습니다. 이전 버전에서는 `object()`를 이용한 앞의 방법을 계속 쓰면 됩니다.

## 함수에서 자주 하는 실수

- **return을 빠뜨림** — 함수가 `None`을 반환합니다.
- **괄호를 빠뜨림** — `print(greet)`는 함수 객체를 출력하고, 호출 결과는 출력하지 않습니다.
- **변경 가능한 기본값** — 모든 호출이 같은 리스트를 공유합니다.
- **인자 개수 틀림** — `TypeError: greet() missing 1 required positional argument: 'name'`.
- **내장 이름과 충돌** — `list`나 `sum`이라는 이름으로 함수를 만들면 내장 함수를 가려 버립니다.

## 정리

파이썬 함수는 `def`로 정의하고, `return`으로 결과를 돌려주며, 인자를 순서대로 또는 이름으로 받습니다. `*args`와 `**kwargs`는 유연성을 주고, `/`와 `*`는 호출 방식을 엄격하게 만들며, lambda는 한 번만 쓰는 함수를 짧게 표현하고, 타입 힌트는 mypy와 함께 실행 전에 오류를 잡습니다. 과정 강의에서는 이 주제를 실습으로 다룹니다. 지출 관리기를 타입 힌트가 붙은 함수들로 나누고 mypy로 검사합니다.

## FAQ

**매개변수와 인자는 무엇이 다른가요?**

매개변수는 함수 정의에 적힌 이름이고, 인자는 호출할 때 넘기는 실제 값입니다. def greet(name)에서 name은 매개변수이고, greet("안나")에서 "안나"는 인자입니다.

**함수를 정의하기 전에 호출할 수 있나요?**

모듈 최상위에서는 안 됩니다. def 문이 먼저 실행되어야 하므로, 그렇지 않으면 NameError가 납니다. 다른 함수 안에서는 호출 시점에 정의되어 있으면 괜찮습니다.

**함수는 값을 여러 개 반환할 수 있나요?**

형식상은 하나를 반환하지만, return a, b처럼 쓰면 두 원소의 튜플이 반환되고, x, y = f()로 풀어서 받을 수 있습니다.

**타입 힌트는 꼭 써야 하나요?**

필수는 아닙니다. 파이썬은 힌트 없이도 동작합니다. 하지만 다른 사람이 읽는 코드에서는 힌트가 유지보수를 쉽게 하고, mypy나 pyright가 실행 전에 오류를 찾게 해 줍니다.

**lambda에서 if를 쓸 수 있나요?**

조건식은 쓸 수 있습니다: lambda x: "짝" if x % 2 == 0 else "홀". 하지만 lambda 안에서는 명령문, 반복문, 대입문을 쓸 수 없습니다.


---
Source: Skillaria.Top (https://skillaria.top)
