Python 함수구조, 함수인자, 람다함수, 클로저, 고차함수, 데코레이터
목차
1. 함수 구조
Python에서 함수는 입력값을 받아 로직을 수행하고, 출력값을 반환하는 코드 블록입니다.
반복되는 코드를 줄이고, 코드를 모듈화하여 유지보수를 쉽게 해줍니다.
▶ 함수 정의 문법
def 함수이름(매개변수1, 매개변수2, ...):
"""선택적으로 함수 설명"""
실행할 코드
return 결과값
▶ 함수 호출 방법
함수이름(인자1, 인자2)
▶ 함수 예시
def add(a, b):
return a + b
result = add(3, 7)
print(result) # 10
함수는 반드시 정의한 뒤 호출해야 합니다.
같은 파일 내에서는 실행 시점에 따라 호출이 먼저 와도 오류는 발생하지 않지만, 가독성과 유지보수 측면에서 권장되지 않습니다.
2. 함수의 인자(Arguments)
함수에 외부에서 값을 전달할 때 사용되는 정보를 인자(argument)라고 하며,
함수를 정의할 때 괄호 ( ) 안에 쓰는 변수는 매개변수(parameter)라고 부릅니다.
유형 | 설명 | 저장형태 |
위치 인자 | 순서대로 전달 | 그대로 |
키워드 인자 | 변수명과 함께 전달 | 그대로 |
기본값 인자 | 값이 없으면 기본값 사용 | 값 생략 가능 |
*args | 가변 길이 위치 인자 | 튜플 (tuple) |
**kwargs | 가변 길이 키워드 인자 | 딕셔너리 (dict) |
▶ 위치 인자
인자를 순서대로 나열하여 전달하는 방식입니다.
함수를 호출할 때 정의된 순서와 동일하게 값을 넣어야 합니다.
순서가 맞지 않으면 예상치 못한 결과가 나올 수 있습니다.
def greet(name, age):
print(f"{name}는 {age}살입니다.")
greet("Alice", 25)
#
Alice는 25살입니다.
▶ 키워드 인자
함수를 호출할 때 변수명을 명시하여 인자를 전달하는 방식입니다.
순서를 무시하고도 정확하게 값 전달이 가능합니다.
키워드 인자는 순서에 관계없이 인자의 의미를 명확하게 해주므로, 협업 시 실수를 줄일 수 있습니다.
greet(age=30, name="Bob")
#
Bob는 30살입니다.
▶ 기본값 인자
함수를 정의할 때 매개변수에 기본값을 지정해두면, 호출 시 해당 인자를 생략할 수 있습니다.
기본값을 지정한 매개변수는 항상 뒤쪽에 위치해야 합니다.
예: def func(a, b=10) ✅ 정상
def func(a=10, b) ❌ 오류
def greet(name="Guest"):
print(f"Hello, {name}")
greet() # 기본값 사용
greet("David") # 전달된 값 사용
#
Hello, Guest
Hello, David
▶ 가변 인자(*args)
인자의 개수가 정해지지 않았을 때 사용합니다.
여러 개의 값을 튜플(tuple)로 받아 처리할 수 있습니다.
def total(*nums):
print("전달된 값들:", nums)
return sum(nums)
print(total(1, 2, 3, 4))
#
전달된 값들: (1, 2, 3, 4)
10
함수에 인자를 여러 개 전달할 수 있고, 내부에서는 for문 등을 통해 순회할 수 있습니다.
def print_scores(*scores):
print("학생 점수 목록:")
for idx, score in enumerate(scores, start=1):
print(f"{idx}번 학생: {score}점")
# 함수 호출
print_scores(85, 92, 78, 100)
▶ 키워드 가변 인자(**kwargs)
이름이 있는 인자들을 딕셔너리 형태로 받아 처리할 수 있습니다.
보통 설정값이나 옵션 처리를 위해 많이 사용됩니다.
kwargs는 dictionary 타입이므로, .items()를 사용해 키-값 쌍을 순회할 수 있습니다.
def config(**options):
for key, value in options.items():
print(f"{key} = {value}")
config(theme="dark", fontsize=12, autosave=True)
#
theme = dark
fontsize = 12
autosave = True
▶ *args + **kwargs 함께 쓰기
함수 정의 시 *args와 **kwargs를 함께 사용할 수 있으며,
순서는 항상 *args → **kwargs여야 합니다.
def show_info(title, *args, **kwargs):
print("제목:", title)
print("위치 인자:", args)
print("키워드 인자:", kwargs)
show_info("로그인 정보", "admin", "user", level=5, active=True)
#
제목: 로그인 정보
위치 인자: ('admin', 'user')
키워드 인자: {'level': 5, 'active': True}
3. 전역변수와 지역변수
Python 변수는 정의 위치에 따라 범위가 달라집니다.
구분 | 정의 위치 | 사용 범위 |
전역변수 | 함수 외부 | 파일 전체에서 사용 |
지역변수 | 함수 내부 | 함수 내부에서만 사용 |
x = 10 # 전역변수
def example():
y = 5 # 지역변수
print("내부:", x + y)
example()
print("외부:", x)
# print(y) # ❌ 오류: y는 함수 외부에서 접근 불가
def set_global():
global g
g = "전역 설정됨"
set_global()
print(g) # 전역 설정됨
- global g로 선언된 변수는 함수 외부에서도 사용할 수 있는 전역 변수
- 함수 안에서 선언했지만, 외부에서도 접근 가능
▶ 전역변수 없이 지역변수만 수정하려고 할 때 발생하는 문제
x = 5
def change_x():
x = x + 1 # ❌ 오류: 지역 변수 참조 전에 사용됨
change_x()
함수 내부에서 x를 지역 변수로 취급하면서 동시에 수정하려고 했기 때문에 오류 발생 : UnboundLocalError 오류
▶ 전역변수 수정 예시: 정상 처리
x = 5
def change_x():
global x
x = x + 1
change_x()
print(x) # 6
4. 람다함수(Lambda function)
람다(lambda) 함수는 간단한 연산을 한 줄로 정의할 수 있는 익명 함수입니다.
def를 사용하여 함수를 만들지 않고도, 즉석에서 함수를 정의하고 변수에 할당하거나 인자로 전달할 수 있습니다.
이러한 람다 함수는 map(), filter(), sorted() 같은 고차 함수(high-order function)와 함께 자주 사용됩니다
▶ 기본 문법
lambda 인자1, 인자2, ... : 표현식
- lambda는 함수 정의 키워드입니다.
- : 앞에는 인자(argument)를, 뒤에는 반환할 표현식(expression)을 작성합니다.
- 여러 줄 로직이나 if 문, 반복문은 사용할 수 없습니다.
- 람다 함수는 한 줄짜리 표현식만 사용할 수 있으며, return 키워드는 생략합니다.
▶ 일반함수 vs 람다 함수
# 일반 함수 정의
def square(x):
return x * x
# 람다 함수 정의
square_lambda = lambda x: x * x
print(square(5)) # 25
print(square_lambda(5)) # 25
▶ 인자가 여러 개인 람다 함수
add = lambda a, b: a + b
print(add(3, 7)) # 10
▶ 조건문을 포함한 람다 함수 (삼항 연산자 사용)
check_even = lambda x: "짝수" if x % 2 == 0 else "홀수"
print(check_even(4)) # 짝수
print(check_even(7)) # 홀수
5. 클로저(Closure)
클로저(Closure)는 내부 함수가 외부 함수의 지역 변수에 접근하고, 그 상태를 유지하는 함수입니다.
즉, 내부 함수가 외부 함수의 실행 컨텍스트(변수)를 기억한 채로 나중에 호출되더라도 그 값을 사용할 수 있는 구조입니다.
이 개념은 특히 다음과 같은 경우에 유용하게 사용됩니다.
- 함수 실행 후에도 특정 상태를 기억하고 있어야 할 때
- 상태를 은닉하고 캡슐화하려 할 때
- 데코레이터나 이벤트 핸들러 등에서 상태 기반의 동작 제어가 필요할 때
▶ 기본 구조
def outer(외부변수):
def inner():
# 외부변수 사용 가능
print(외부변수)
return inner
inner() 함수는 outer() 내부에 정의되어 있지만, outer()가 실행된 후에도 그 안의 msg 값을 계속 기억합니다.
▶ 외부 변수 접근
def outer(msg):
def inner():
print(f"메시지: {msg}")
return inner
f = outer("안녕하세요")
f() # 메시지: 안녕하세요
- outer() 함수는 "안녕하세요"라는 값을 가진 msg를 갖고 있습니다.
- inner()는 msg를 참조하지만, outer()의 실행이 끝난 후에도 msg를 기억하고 있어 출력이 가능합니다.
▶ 여러 클로저 인스턴스 만들기
def make_greeting(greeting):
def greet(name):
return f"{greeting}, {name}!"
return greet
hello = make_greeting("Hello")
hi = make_greeting("Hi")
print(hello("Alice")) # Hello, Alice!
print(hi("Bob")) # Hi, Bob!
- hello, hi는 서로 다른 greeting 상태를 기억하는 클로저입니다.
- 각각 "Hello", "Hi"라는 값을 내부 함수가 유지하고 있습니다.
▶ 상태를 유지하는 클로저 (Counter)
def counter():
count = 0
def increment():
nonlocal count # 외부 변수 count를 수정하려면 nonlocal 필요
count += 1
return count
return increment
c = counter()
print(c()) # 1
print(c()) # 2
d = counter()
print(d()) # 1 (새 인스턴스는 count를 0부터 시작)
- c()는 내부적으로 count 값을 유지하고, 호출할 때마다 1씩 증가합니다.
- nonlocal 키워드는 외부 함수의 지역변수를 수정하기 위해 필요합니다.
- 새로운 클로저 d()는 자체적으로 count를 갖고 독립적인 상태를 유지합니다.
▶ 클러저와 일반함수 차이
def regular_counter():
count = 0
count += 1
return count
print(regular_counter()) # 항상 1 반환
print(regular_counter()) # 또 1 반환
- regular_counter()는 호출할 때마다 count를 새로 선언하므로 상태가 유지되지 않음
- 클로저는 한 번 생성된 함수 안에서 상태를 누적/유지 가능
▶ 클로저의 유용한 활용 예시
def decorator(func):
def wrapper():
print("함수 호출 전 작업")
func()
print("함수 호출 후 작업")
return wrapper
@decorator
def say_hello():
print("Hello!")
say_hello()
- wrapper()는 내부에서 func()를 호출하며, func는 decorator의 인자로 들어온 함수입니다.
- 이 때 wrapper는 func를 기억하는 클로저 구조입니다.
▶ 클로저를 사용해야 하는 상황 예시
상황 | 설명 |
함수 호출 횟수 누적 | 내부 상태를 유지하며 카운트 가능 |
사용자별 맞춤 메시지 구성 | 각 사용자마다 greeting 상태 유지 가능 |
데코레이터 구현 시 함수 저장 필요 | 대상 함수 기억 후 래핑 가능 |
외부 접근 없이 상태 은닉 필요 | 함수 안에만 변수를 두어 외부에서 접근 차단 가능 |
6. 고차함수(High-Order Function)
고차 함수(High-Order Function)란 다음 조건 중 하나 이상을 만족하는 함수를 말합니다.
- 다른 함수를 인자로 받는 함수
- 다른 함수를 반환하는 함수
즉, 함수도 일급 객체로 취급되는 Python에서는 함수를 인자처럼 넘기거나, 함수 내부에서 새 함수를 생성해 반환할 수 있습니다.
특징 | 설명 |
함수는 값처럼 전달됨 | 인자로 전달하거나, 반환값으로 리턴 가능 |
함수 조합 가능 | 여러 함수를 조합하여 유연하고 확장성 높은 로직 작성 가능 |
함수형 프로그래밍 지원 | map, filter, reduce 등과 결합하여 선언형 코딩 가능 |
- 함수를 값으로 전달하므로, 전달하는 함수의 형태나 리턴값에 주의해야 함
- 가독성이 떨어질 수 있으므로 람다 중첩 사용은 자제
- 디버깅이나 로깅 시, 함수 객체를 전달하기 때문에 추적이 어려울 수 있음
▶ 함수를 인자로 받는 고차 함수
apply_func는 다른 함수를 인자로 받아 실행하므로 고차 함수입니다.
def apply_func(f, x):
return f(x)
# 제곱 함수 정의
def square(n):
return n * n
result = apply_func(square, 5)
print(result) # 25
▶ 함수를 반환하는 고차 함수 (클로저 포함)
make_multiplier()는 함수를 반환하는 고차 함수이며, 내부에서 n 값을 기억하는 클로저 구조입니다.
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
double = make_multiplier(2)
print(double(5)) # 10
triple = make_multiplier(3)
print(triple(5)) # 15
▶ map(), filter(), reduce()는 모두 고차 함수
# map(function, iterable)
nums = [1, 2, 3]
squared = list(map(lambda x: x ** 2, nums))
print(squared) # [1, 4, 9]
# filter(function, iterable)
even = list(filter(lambda x: x % 2 == 0, nums))
print(even) # [2]
# reduce(function, iterable) – functools 필요
from functools import reduce
total = reduce(lambda x, y: x + y, nums)
print(total) # 6
▶ sorted()의 key는 고차 함수 활용 사례
sorted()는 내부적으로 key 인자로 함수를 받아 처리하므로 고차 함수입니다.
words = ["apple", "banana", "kiwi", "grape"]
sorted_words = sorted(words, key=lambda x: len(x))
print(sorted_words) # ['kiwi', 'apple', 'grape', 'banana']
7. 함수 데코레이터(Decorator)
데코레이터(Decorator)는 함수를 인자로 받아, 기능을 확장한 새로운 함수를 반환하는 함수입니다.
기존 함수를 수정하지 않고도 코드 전후에 기능을 추가하거나, 반복적인 처리를 공통화할 때 매우 유용합니다.
☞ 데코레이터는 클로저(Closure) 구조를 기반으로 동작합니다.
▶ 기본 문법
def 데코레이터이름(func): # 함수를 인자로 받음
def wrapper(): # 내부에서 감싼 함수
# 전처리 코드
func() # 원래 함수 실행
# 후처리 코드
return wrapper # 감싼 함수 반환
@데코레이터이름
def 원래_함수():
...
@데코레이터이름은 원래_함수 = 데코레이터이름(원래_함수) 와 같은 의미입니다.
▶ 실행 전후 메시지 추가
def logger(func):
def wrapper():
print("함수 실행 전")
func()
print("함수 실행 후")
return wrapper
@logger
def say_hello():
print("Hello!")
say_hello()
'''
함수 실행 전
Hello!
함수 실행 후
'''
- logger(func) 라는 이름의 데코레이터 함수가 정의됨. 이 함수는 다른 함수를 인자로 받아 내부에서 wrapper() 함수를 감싸서 반환함
- wrapper()는 func()를 실행하기 전과 후에 메시지를 출력하는 함수로 정의됨
- logger()는 감싼 함수 wrapper를 반환함
- @logger는 say_hello = logger(say_hello) 와 같은 의미임. 즉, say_hello()를 실행하면 원래 함수가 아니라 wrapper()가 호출됨
- say_hello() 호출 시 → 실제로는 wrapper() 실행 → 내부에서 print, func(), print 순서로 실행됨
▶ @logger가 하는 일
@logger
def say_hello():
print("Hello!")
say_hello()
이 코드는 다음과 동일한 의미입니다:
def say_hello():
print("Hello!")
say_hello = logger(say_hello)
- say_hello 함수 자체를 logger()에 넣는다
- logger()는 이 함수를 받아서 내부에 wrapper() 함수를 만든다
- 그 wrapper()를 다시 say_hello에 덮어쓴다
바뀐 이후 say_hello는?
- 이제 say_hello는 더 이상 print("Hello!")만 실행하는 함수가 아닙니다
- 새로운 함수(wrapper)를 가리키는 변수입니다.
def wrapper():
print("함수 실행 전")
say_hello() # 이건 원래 함수가 아님. 이 라인 없고 내부에서 func() 호출됨
print("함수 실행 후")
그래서 say_hello()를 실행하면:
- wrapper()가 실행되고
- 그 안에서 원래 say_hello() (즉, func())가 호출됨
- 결과적으로 "Hello!" 출력은 여전히 실행되지만
- 전후로 추가된 print도 같이 실행됨
▶ 인자와 반환값 처리
def trace(func):
def wrapper(*args, **kwargs):
print(f"[trace] 호출된 함수: {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"[trace] 반환값: {result}")
return result
return wrapper
@trace
def add(x, y):
return x + y
add(3, 5)
'''
[trace] 호출된 함수: add((3, 5), {})
[trace] 반환값: 8
'''
▶ 중첩 데코레이터
def bold(func):
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def say():
return "Hello"
print(say()) # <b><i>Hello</i></b>
▶ 실행 시간 측정
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"실행 시간: {end - start:.5f}초")
return result
return wrapper
@timer
def long_task():
time.sleep(1)
return "완료"
long_task()
'''
실행 시간: 1.00123초
'''
▶ functools.wraps를 사용하는 이유
- @wraps(func)는 wrapper() 함수가 func의 이름, docstring, metadata를 그대로 유지하게 해줍니다.
- 없으면 __name__, __doc__ 등이 wrapper로 바뀌어 디버깅이나 문서화에 불편합니다.
관련 글 링크
2. Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수
2. Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수
Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수 목차 1. 지역변수 vs 전역변수 2. global 키워드의 역할과 주의사항 3. nonlocal 키워드 4. 변수처럼 다루는 함수-일급객체로서의 함수 5. 클래스
quadcube.tistory.com
'3.개발언어&라이브러리 > Python' 카테고리의 다른 글
6. Python 병렬 처리 : GIL, threading, asyncio, multiprocessing (0) | 2025.05.22 |
---|---|
5. Python 예외(Exception) 처리 : try-except-finally, with (0) | 2025.05.22 |
3. Python 클래스 정리: 클래스, 상속, 메서드, 접근제어 (0) | 2025.05.21 |
2. Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수 (0) | 2025.05.21 |
1. Python 모듈, 패키지, import 개념 정리: __init__.py (0) | 2025.05.20 |