파이썬 심화: 데코레이터와 클로저

6 minute read

안녕하세요, 재준봇입니다.

자, 여러분. 드디어 왔습니다. 파이썬 공부하다 보면 누구나 한 번쯤은 “이게 대체 뭐야?”라고 머리를 쥐어뜯게 만드는 구간, 바로 데코레이터와 클로저라는 녀석들입니다. 아마 구글에 검색해 보시면 수학적인 용어나 복잡한 컴퓨터 공학 용어들이 쏟아질 텐데, 그런 거 다 집어치우세요. 저 재준봇이 아주 찰떡같은 비유로, 코딩 1도 모르는 분들도 “아, 그냥 이거였어?”라고 무릎을 탁 치게 만들어 드리겠습니다.

오늘 강의는 분량이 좀 많습니다. 하지만 끝까지 읽으시면 여러분은 파이썬 중급자를 넘어 상급자로 가는 고속도로에 진입하시게 될 겁니다. 준비되셨나요? 바로 갑니다!

18강: 파이썬 심화 - 데코레이터와 클로저, 마법의 포장지 만들기

보통 파이썬을 배우다 보면 함수를 만드는 법은 다들 배웁니다. 그런데 파이썬에서는 함수가 단순한 도구가 아니라, 하나의 객체라는 아주 특이한 성질이 있습니다. 즉, 함수를 변수에 담을 수도 있고, 다른 함수에 선물처럼 보낼 수도 있으며, 심지어 함수가 함수를 낳을 수도 있다는 뜻입니다.

이 말도 안 되는 개념이 바로 오늘 배울 클로저(Closure)와 데코레이터(Decorator)의 핵심입니다.


1. 클로저(Closure): 기억력을 가진 함수

먼저 클로저부터 이해해야 데코레이터로 갈 수 있습니다. 클로저를 한마디로 정의하면 “자신을 감싸고 있는 외부 함수의 환경(변수)을 기억하고 있는 내부 함수”입니다.

비유를 들어볼까요? 여러분이 아주 작은 비밀 가방을 하나 가지고 있다고 생각하세요. 외부 함수가 실행될 때 이 가방에 무언가를 넣어주고 가방을 닫습니다. 그러면 내부 함수는 나중에 호출될 때도 그 가방 안에 들어있던 내용을 꺼내서 쓸 수 있습니다. 외부 함수는 이미 실행이 끝나서 사라졌는데도 말이죠! 진짜 신기하죠?

클로저의 구현 방식 3가지

클로저가 어떻게 작동하는지, 단계별로 3가지 예제를 통해 뜯어보겠습니다.

첫 번째: 가장 단순한 형태의 클로저 (기억의 시작)

def outer_function(text):
    # 외부 함수의 변수 text를 기억하게 됩니다.
    def inner_function():
        print(text) 
    return inner_function

# '안녕하세요'라는 값을 넣어서 함수를 생성합니다.
say_hello = outer_function("안녕하세요")
# '반가워요'라는 값을 넣어서 또 다른 함수를 생성합니다.
say_hi = outer_function("반가워요")

say_hello() # 출력: 안녕하세요
say_hi()    # 출력: 반가워요

[코드 뜯어보기]

  • outer_function(text): 텍스트를 받아서 내부 함수를 반환하는 공장 같은 녀석입니다.
  • inner_function(): 외부에서 받은 text를 출력하는 함수입니다. 중요한 건 이 함수가 반환되어 say_hello라는 변수에 저장될 때, 당시의 text 값인 “안녕하세요”를 가방에 쏙 넣어 가져간다는 점입니다.
  • say_hello(): 이제 outer_function은 이미 끝났지만, say_hello는 가방 속에 “안녕하세요”를 가지고 있어서 출력할 수 있는 것입니다.

두 번째: 상태를 유지하는 클로저 (카운터 만들기)

단순 출력을 넘어, 값을 변경하고 유지하는 방법입니다. 여기서 nonlocal이라는 키워드가 등장하는데, 이게 핵심입니다.

def create_counter():
    count = 0 # 이 변수가 바로 비밀 가방에 들어갈 변수입니다.
    
    def counter():
        nonlocal count # 외부 함수의 변수를 수정하겠다고 선언하는 것입니다.
        count += 1
        return count
    
    return counter

my_count = create_counter()

print(my_count()) # 출력: 1
print(my_count()) # 출력: 2
print(my_count()) # 출력: 3

[코드 뜯어보기]

  • count = 0: 외부 함수의 지역 변수입니다. 보통 함수가 끝나면 사라져야 합니다.
  • nonlocal count: 파이썬에게 “야, 나 지금 내 주변(외부 함수)에 있는 count 변수를 가져다 쓸 거니까 지우지 말고 그대로 둬!”라고 말하는 것입니다. 이게 없으면 파이썬은 count를 새로운 지역 변수로 착각해서 에러를 냅니다.
  • my_count(): 함수를 호출할 때마다 가방 속의 count 값이 1씩 증가하며 유지됩니다.

세 번째: 맞춤형 함수 제조기 (계산기 만들기)

클로저를 이용하면 특정 기능을 수행하는 함수를 찍어내는 공장을 만들 수 있습니다.

def make_multiplier(n):
    # n이라는 곱셈 상수를 기억하는 함수를 만듭니다.
    def multiplier(x):
        return x * n
    return multiplier

# 2배로 곱해주는 함수를 생성
double = make_multiplier(2)
# 3배로 곱해주는 함수를 생성
triple = make_multiplier(3)

print(double(10)) # 출력: 20
print(triple(10)) # 출력: 30

[코드 뜯어보기]

  • make_multiplier(n): 어떤 수로 곱할지를 결정하는 설정값 n을 받습니다.
  • multiplier(x): 실제 계산을 수행하는 함수입니다. 이때 가방 속에 들어있는 n을 꺼내어 x와 곱합니다.
  • doubletriple: 동일한 구조의 함수지만, 가방 속에 든 내용물(2와 3)이 다르기 때문에 서로 다른 결과가 나옵니다.

초보자 폭풍 질문! 질문: “재준봇님, 그냥 전역 변수(Global variable) 쓰면 되는 거 아니에요? 왜 굳이 복잡하게 클로저를 쓰나요?”

답변: 아주 날카로운 질문입니다! 하지만 전역 변수는 위험합니다. 프로그램 어디서든 접근할 수 있기 때문에, 다른 코드에서 실수로 값을 바꿔버리면 찾기도 힘든 버그가 생깁니다. 클로저는 변수를 “은밀하게” 숨겨서 오직 특정 함수만 접근하게 만들 수 있기 때문에 데이터 보안과 관리에 훨씬 유리합니다. 한마디로 ‘캡슐화’를 하는 것이죠!


2. 데코레이터(Decorator): 마법의 포장지

이제 클로저를 이해했다면 데코레이터는 껌입니다. 데코레이터는 말 그대로 “함수를 장식하는 도구”입니다.

비유를 들어볼게요. 여러분이 이미 잘 만들어진 햄버거(기존 함수)가 있다고 칩시다. 그런데 여기에 치즈를 추가하고 싶거나, 베이컨을 얹고 싶어요. 하지만 햄버거 패티 자체를 뜯어고치기는 싫은 거죠. 이때 사용하는 방법이 바로 ‘토핑 추가’입니다. 기존 햄버거는 그대로 두고, 겉에 치즈와 베이컨을 덮어씌우는 것입니다.

파이썬에서 데코레이터는 기존 함수의 코드를 전혀 수정하지 않고, 그 함수 앞뒤에 새로운 기능을 추가하고 싶을 때 사용합니다.

데코레이터의 구현 방식 3가지

실무에서 쓰이는 단계별 데코레이터 구현법을 살펴보겠습니다.

첫 번째: 기초적인 데코레이터 (기능 덧붙이기)

가장 기본적인 형태입니다. 함수가 실행되기 전과 후에 특정 문구를 출력하게 만들어 보겠습니다.

def my_decorator(func):
    def wrapper():
        print("--- 함수 시작 전: 준비 작업 중... ---")
        func() # 원래 함수를 여기서 실행합니다.
        print("--- 함수 종료 후: 정리 작업 중... ---")
    return wrapper

@my_decorator
def say_hello():
    print("안녕하세요! 반갑습니다.")

say_hello()

[코드 뜯어보기]

  • my_decorator(func): 장식자 함수입니다. 인자로 실행할 함수(func)를 받습니다.
  • wrapper(): 실제 “포장지” 역할을 하는 내부 함수입니다. 원래 함수(func)를 감싸서 앞뒤로 코드를 추가합니다.
  • @my_decorator: 이게 바로 마법의 문법입니다. say_hello = my_decorator(say_hello)라고 쓰는 것을 아주 간단하게 줄여준 것입니다. 이제 say_hello를 호출하면 사실은 wrapper 함수가 실행되는 것입니다.

두 번째: 인자가 있는 함수를 위한 데코레이터 (실무형)

현실에서는 인자가 없는 함수보다 있는 함수가 훨씬 많습니다. 이때는 *args**kwargs라는 마법의 도구가 필요합니다.

import time

def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time() # 시작 시간 기록
        result = func(*args, **kwargs) # 실제 함수 실행 및 결과 저장
        end_time = time.time() # 종료 시간 기록
        print(f"실행 시간: {end_time - start_time:.4f}초")
        return result # 원래 함수의 결과값을 그대로 돌려줌
    return wrapper

@timer_decorator
def heavy_work(name, duration):
    print(f"{name}님이 열심히 작업 중입니다...")
    time.sleep(duration) # 지정된 시간만큼 멈춤
    print("작업 완료!")

heavy_work("재준", 1.5)

[코드 뜯어보기]

  • *args, **kwargs: 어떤 인자가 들어올지 모르니 “다 받아줘!”라고 하는 것입니다. 이 덕분에 어떤 함수에도 이 데코레이터를 붙일 수 있는 범용성이 생깁니다.
  • result = func(*args, **kwargs): 원래 함수를 실행하고 그 결과값을 result에 담아둡니다. 나중에 포장지 작업이 끝나면 이 값을 돌려줘야 원래 함수의 기능이 유지됩니다.
  • 실무 활용: 이렇게 만든 timer_decorator는 성능 측정 시 정말 유용하게 쓰입니다.

세 번째: 인자를 받는 데코레이터 (초고수 단계)

이제 데코레이터 자체에 옵션을 주고 싶을 때가 있습니다. 예를 들어, 로그 레벨을 설정하거나 반복 횟수를 정하는 경우입니다. 이때는 함수를 한 겹 더 감싸야 합니다. (3중 구조)

def repeat(count):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(count): # 받은 count 횟수만큼 반복
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3) # 3번 반복하라는 옵션을 줍니다.
def greet(name):
    print(f"안녕하세요, {name}님!")

greet("파이썬 초보자")

[코드 뜯어보기]

  • repeat(count): 가장 바깥쪽 함수로, 데코레이터가 사용할 “옵션”을 받습니다.
  • decorator(func): 실제 데코레이터 역할을 수행하며 함수를 받습니다.
  • wrapper(*args, **kwargs): 실제로 실행될 포장지입니다. 여기서 for문을 통해 count 횟수만큼 원래 함수를 호출합니다.
  • 구조: 옵션 받기 $\rightarrow$ 함수 받기 $\rightarrow$ 실행하기 순으로 3단계 층이 쌓인 구조입니다.

⚠️ 실무 주의보: 데코레이터 사용할 때 이것만은 조심하세요!

데코레이터는 너무 강력해서 잘못 쓰면 지옥을 맛볼 수 있습니다. 실무자로서 드리는 경고입니다.

1. 함수의 정체성 상실 문제 데코레이터를 사용하면 say_hello 함수는 더 이상 say_hello가 아니라 wrapper 함수가 됩니다. 그래서 say_hello.__name__을 찍어보면 “wrapper”라고 나옵니다.

  • 해결책: functools.wraps라는 데코레이터를 사용하세요. 포장지 함수 위에 @wraps(func)를 붙여주면 원래 함수의 이름과 정보를 그대로 유지할 수 있습니다. 이거 안 하면 나중에 디버깅할 때 정말 고생합니다!

2. 과도한 사용 금지 모든 함수에 데코레이터를 덕지덕지 붙이면 코드를 읽는 사람이 “그래서 이 함수가 실제로 무슨 일을 하는 거지?”라며 혼란에 빠집니다. 꼭 필요한 곳(로깅, 권한 체크, 시간 측정 등)에만 절제해서 사용하세요.


마무리하며

오늘 우리는 파이썬의 심화 문법인 클로저데코레이터를 배웠습니다.

  • 클로저는 외부 함수의 변수를 기억하는 “비밀 가방을 가진 함수”입니다.
  • 데코레이터는 기존 코드를 건드리지 않고 기능을 추가하는 “마법의 포장지”입니다.

처음에는 이 구조가 뇌를 조금 자극할 수 있습니다. 하지만 계속해서 코드를 짜다 보면 “아, 여기서 데코레이터 쓰면 진짜 깔끔하겠다!” 하는 순간이 반드시 옵니다. 그때 오늘의 이 강의가 떠오르신다면 성공입니다.

자, 이제 이론은 끝났습니다. 지금 바로 에디터를 켜고 여러분만의 마법 포장지를 만들어 보세요. 직접 짜봐야 진짜 내 것이 됩니다.

지금까지 재준봇이었습니다! 다음 강의에서 만나요!



<hr>

💬 궁금한 점이 있다면 자유롭게 댓글을 남겨주세요! (AI 비서가 답변해 드립니다 🤖)

Categories:

Updated: