이 예제에서multiply함수는 클로저입니다. 이는multiplier함수의 인자x를 기억하고 있습니다.
3. 데코레이터의 기본 문법
3.1 '@' 심볼의 비밀
'@' 심볼은 파이썬에서 데코레이터를 적용할 때 사용하는 문법적 설탕(syntactic sugar)입니다. 이를 통해 데코레이터를 더 간결하고 읽기 쉽게 적용할 수 있습니다.
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 위의 코드는 다음과 동일합니다:
# say_hello = my_decorator(say_hello)
say_hello()
3.2 인자가 없는 데코레이터
가장 간단한 형태의 데코레이터는 인자가 없는 데코레이터입니다. 이는 단순히 함수를 받아 새로운 함수를 반환합니다.
성능 분석은 모든 개발자에게 중요한 작업입니다. 함수의 실행 시간을 측정하는 데코레이터를 만들어 보겠습니다.
import time
def timing_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} 함수 실행 시간: {end_time - start_time:.5f} 초")
return result
return wrapper
@timing_decorator
def slow_function():
time.sleep(2)
print("Function completed")
slow_function()
이 데코레이터는 함수의 시작과 끝 시간을 측정하여 실행 시간을 출력합니다. 이를 통해 어떤 함수가 병목이 되는지 쉽게 파악할 수 있습니다.
4.2 로깅 데코레이터: 디버깅의 강력한 동반자
로깅은 디버깅과 모니터링에 필수적입니다. 함수의 입력과 출력을 자동으로 로깅하는 데코레이터를 만들어 보겠습니다.
import logging
logging.basicConfig(level=logging.INFO)
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.info(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
logging.info(f"{func.__name__} returned {result}")
return result
return wrapper
@log_decorator
def add(x, y):
return x + y
add(3, 5)
이 데코레이터는 함수 호출 시 인자와 반환값을 자동으로 로깅합니다. 이는 복잡한 시스템에서 함수의 동작을 추적하는 데 매우 유용합니다.
4.3 재시도 데코레이터: 네트워크 오류를 우아하게 처리하기
네트워크 요청과 같이 일시적으로 실패할 수 있는 작업에 대해 자동으로 재시도하는 데코레이터를 만들어 보겠습니다.
import time
from functools import wraps
def retry(max_tries=3, delay_seconds=1):
def decorator_retry(func):
@wraps(func)
def wrapper_retry(*args, **kwargs):
tries = 0
while tries ' max_tries:
try:
return func(*args, **kwargs)
except Exception as e:
tries += 1
if tries == max_tries:
raise e
print(f"Attempt {tries} failed. Retrying in {delay_seconds} seconds...")
time.sleep(delay_seconds)
return wrapper_retry
return decorator_retry
@retry(max_tries=3, delay_seconds=2)
def unreliable_function():
import random
if random.random() ' 0.7:
raise Exception("Random error occurred")
return "Success!"
print(unreliable_function())
이 데코레이터는 함수 실행이 실패할 경우 지정된 횟수만큼 재시도합니다. 네트워크 요청이나 데이터베이스 쿼리와 같은 불안정한 작업에 매우 유용합니다.
4.4 메모이제이션 데코레이터: 성능을 극대화하는 비밀 무기
메모이제이션은 이전에 계산한 결과를 저장하여 반복적인 계산을 피하는 최적화 기법입니다. 이를 구현하는 데코레이터를 만들어 보겠습니다.
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
key = str(args) + str(kwargs)
if key not in cache:
cache[key] = func(*args, **kwargs)
return cache[key]
return wrapper
@memoize
def fibonacci(n):
if n ' 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 이제 매우 빠르게 계산됩니다!
이 데코레이터는 함수의 결과를 캐시하여 동일한 입력에 대해 반복 계산을 피합니다. 특히 재귀 함수나 계산 비용이 높은 함수에서 큰 성능 향상을 가져올 수 있습니다.
5. 클래스 데코레이터: 객체 지향 프로그래밍의 혁명
5.1 클래스 데코레이터의 기본
클래스 데코레이터는 클래스 정의를 수정하거나 확장하는 데 사용됩니다. 이는 메타클래스의 더 간단한 대안이 될 수 있습니다.
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class DatabaseConnection:
def __init__(self):
print("Initializing database connection")
# 항상 같은 인스턴스를 반환합니다
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # 출력: True
이 예제에서singleton데코레이터는 클래스의 인스턴스가 하나만 생성되도록 보장합니다.
5.2 싱글톤 패턴 구현하기
싱글톤 패턴은 클래스의 인스턴스가 하나만 생성되도록 보장하는 디자인 패턴입니다. 위의 예제를 조금 더 발전시켜 보겠습니다.
def singleton(cls):
class SingletonWrapper(cls):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
cls._instance.__init__(*args, **kwargs)
return cls._instance
return SingletonWrapper
@singleton
class Configuration:
def __init__(self):
self.config = {}
def set(self, key, value):
self.config[key] = value
def get(self, key):
return self.config.get(key)
# 사용 예
config1 = Configuration()
config1.set('debug', True)
config2 = Configuration()
print(config2.get('debug')) # 출력: True
print(config1 is config2) # 출력: True
이 구현은 더 복잡한 클래스에 대해서도 싱글톤 패턴을 적용할 수 있게 해줍니다.
5.3 프로퍼티 자동 생성기: 보일러플레이트 코드 제거
getter와 setter 메서드를 자동으로 생성하는 데코레이터를 만들어 보겠습니다. 이는 Java의 lombok 라이브러리와 유사한 기능을 제공합니다.
def auto_property(cls):
class AutoPropertyWrapper(cls):
def __init__(self, *args, **kwargs):
self._values = {}
super().__init__(*args, **kwargs)
def __getattribute__(self, name):
if name in super().__getattribute__('_values'):
return super().__getattribute__('_values')[name]
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name != '_values' and hasattr(self, name):
self._values[name] = value
else:
super().__setattr__(name, value)
properties = [attr for attr in dir(cls) if not attr.startswith('__')]
for prop in properties:
setattr(AutoPropertyWrapper, prop, property())
return AutoPropertyWrapper
@auto_property
class Person:
name = None
age = None
# 사용 예
p = Person()
p.name = "Alice"
p.age = 30
print(p.name, p.age) # 출력: Alice 30
이 데코레이터는 클래스의 모든 속성에 대해 자동으로 getter와 setter를 생성합니다. 이를 통해 보일러플레이트 코드를 크게 줄일 수 있습니다.
6. 고급 데코레이터 테크닉
6.1 데코레이터 체이닝: 여러 기능을 조합하기
여러 데코레이터를 체인처럼 연결하여 사용할 수 있습니다. 이를 통해 여러 기능을 조합할 수 있습니다.
이 예제에서greet함수는 먼저italic데코레이터에 의해 처리된 후,bold데코레이터에 의해 처리됩니다.
6.2 파라미터가 있는 데코레이터 작성하기
데코레이터 자체에 파라미터를 전달하여 더 유연한 데코레이터를 만들 수 있습니다.
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # "Hello, Alice!"가 3번 출력됩니다.
이 예제에서repeat데코레이터는 함수를 몇 번 반복할지 지정할 수 있습니다.
6.3 클래스 메소드와 정적 메소드 데코레이팅
클래스 메소드와 정적 메소드에도 데코레이터를 적용할 수 있습니다. 그러나 이 경우 약간의 주의가 필요합니다.
from functools import wraps
def log_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
class MyClass:
@log_call
@classmethod
def class_method(cls):
print("This is a class method")
@log_call
@staticmethod
def static_method():
print("This is a static method")
MyClass.class_method()
MyClass.static_method()
이 예제에서log_call데코레이터는 클래스 메소드와 정적 메소드 모두에 적용됩니다.@wraps데코레이터를 사용하여 원본 함수의 메타데이터를 보존하는 것이 중요합니다.
7. 데코레이터와 함께하는 함수형 프로그래밍
7.1 순수 함수와 데코레이터
순수 함수는 같은 입력에 대해 항상 같은 출력을 반환하고, 부작용이 없는 함수를 말합니다. 데코레이터를 사용하여 함수의 순수성을 검증할 수 있습니다.
import functools
def ensure_pure(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result1 = func(*args, **kwargs)
result2 = func(*args, **kwargs)
if result1 != result2:
raise ValueError(f"{func.__name__} is not a pure function")
return result1
return wrapper
@ensure_pure
def add(x, y):
return x + y
@ensure_pure
def impure_function(x):
return x + random.randint(1, 10)
print(add(3, 4)) # 정상 작동
try:
impure_function(5)
except ValueError as e:
print(e) # "impure_function is not a pure function" 출력
이 데코레이터는 함수를 두 번 호출하여 결과가 같은지 확인함으로써 함수의 순수성을 검증합니다.
7.2 고차 함수로서의 데코레이터
데코레이터는 본질적으로 고차 함수입니다. 즉, 함수를 인자로 받아 새로운 함수를 반환하는 함수입니다. 이를 활용하여 더 복잡한 동작을 구현할 수 있습니다.
def compose(*funcs):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
result = f(*args, **kwargs)
for func in reversed(funcs):
result = func(result)
return result
return wrapper
return decorator
def double(x):
return x * 2
def increment(x):
return x + 1
@compose(double, increment)
def add(x, y):
return x + y
print(add(3, 4)) # 출력: 15 ((3 + 4 + 1) * 2)
이 예제에서compose데코레이터는 여러 함수를 조합하여 새로운 함수를 만듭니다.
7.3 함수 합성과 데코레이터
함수 합성은 여러 함수를 연결하여 새로운 함수를 만드는 기법입니다. 데코레이터를 사용하여 이를 우아하게 구현할 수 있습니다.
def compose(*funcs):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
for f in reversed(funcs):
result = f(result)
return result
return wrapper
return decorator
# 사용 예
def add_one(x):
return x + 1
def double(x):
return x * 2
@compose(double, add_one)
def base_func(x):
return x
print(base_func(3)) # 출력: 8 ((3 + 1) * 2)
이 예제에서compose데코레이터는 여러 함수를 조합하여 새로운 함수를 만듭니다. 이는 함수형 프로그래밍의 핵심 개념 중 하나인 함수 합성을 파이썬에서 구현하는 방법을 보여줍니다.
8. 실전 프로젝트: 웹 프레임워크 만들기
8.1 라우팅 데코레이터 구현하기
간단한 웹 프레임워크의 라우팅 시스템을 데코레이터를 사용하여 구현해 보겠습니다.
class SimpleWebFramework:
def __init__(self):
self.routes = {}
def route(self, path):
def decorator(handler):
self.routes[path] = handler
return handler
return decorator
def serve(self, path):
if path not in self.routes:
return "404 Not Found"
return self.routes[path]()
app = SimpleWebFramework()
@app.route("/")
def index():
return "Welcome to the home page!"
@app.route("/about")
def about():
return "This is the about page."
# 사용 예
print(app.serve("/")) # 출력: Welcome to the home page!
print(app.serve("/about")) # 출력: This is the about page.
print(app.serve("/contact")) # 출력: 404 Not Found
이 예제에서route데코레이터는 URL 경로와 해당 핸들러 함수를 연결합니다. 이는 Flask나 Django와 같은 실제 웹 프레임워크에서 사용하는 방식과 유사합니다.
8.2 미들웨어 데코레이터 만들기
웹 애플리케이션에서 미들웨어는 요청과 응답을 처리하는 중간 계층입니다. 데코레이터를 사용하여 미들웨어를 구현할 수 있습니다.
class SimpleWebFramework:
def __init__(self):
self.routes = {}
self.middlewares = []
def route(self, path):
def decorator(handler):
self.routes[path] = handler
return handler
return decorator
def middleware(self, middleware_func):
self.middlewares.append(middleware_func)
return middleware_func
def serve(self, path):
if path not in self.routes:
return "404 Not Found"
handler = self.routes[path]
for middleware in self.middlewares:
handler = middleware(handler)
return handler()
app = SimpleWebFramework()
@app.middleware
def logging_middleware(handler):
def wrapper():
print(f"Handling request to {handler.__name__}")
return handler()
return wrapper
@app.route("/")
def index():
return "Welcome to the home page!"
# 사용 예
print(app.serve("/")) # 콘솔에 "Handling request to index" 출력 후
# "Welcome to the home page!" 반환
이 예제에서middleware데코레이터는 핸들러 함수를 감싸는 새로운 함수를 만들어 추가적인 기능(이 경우에는 로깅)을 제공합니다.
8.3 인증 데코레이터로 보안 강화하기
웹 애플리케이션에서 특정 라우트에 대한 접근을 제한하는 인증 시스템을 데코레이터로 구현해 보겠습니다.
import functools
def login_required(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not is_authenticated(): # 이 함수는 실제 인증 로직을 구현해야 합니다
return "Access denied. Please log in."
return func(*args, **kwargs)
return wrapper
class SimpleWebFramework:
def __init__(self):
self.routes = {}
def route(self, path):
def decorator(handler):
self.routes[path] = handler
return handler
return decorator
def serve(self, path):
if path not in self.routes:
return "404 Not Found"
return self.routes[path]()
app = SimpleWebFramework()
@app.route("/")
def index():
return "Welcome to the home page!"
@app.route("/admin")
@login_required
def admin():
return "Welcome to the admin page!"
# 사용 예 (is_authenticated 함수가 구현되어 있다고 가정)
print(app.serve("/")) # 출력: Welcome to the home page!
print(app.serve("/admin")) # 출력: Access denied. Please log in. (인증되지 않은 경우)
이 예제에서login_required데코레이터는 인증되지 않은 사용자의 접근을 차단합니다. 이는 실제 웹 프레임워크에서 보안을 구현하는 일반적인 방법입니다.
9. 데코레이터 디버깅과 테스트
9.1 데코레이터가 적용된 함수 디버깅하기
데코레이터는 함수를 감싸기 때문에 디버깅이 어려울 수 있습니다. 이를 해결하기 위한 몇 가지 테크닉을 살펴보겠습니다.
import functools
def debug_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
print(f"Args: {args}")
print(f"Kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@debug_decorator
def add(x, y):
return x + y
add(3, 4)
이debug_decorator는 함수 호출 시 인자와 반환값을 출력합니다. 이를 통해 데코레이터가 적용된 함수의 동작을 쉽게 추적할 수 있습니다.
9.2 데코레이터 단위 테스트 작성법
데코레이터도 다른 코드와 마찬가지로 테스트가 필요합니다. 데코레이터를 테스트하는 방법을 살펴보겠습니다.
import unittest
def double_result(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
class TestDoubleResultDecorator(unittest.TestCase):
def test_double_result(self):
@double_result
def add(x, y):
return x + y
self.assertEqual(add(2, 3), 10)
self.assertEqual(add(0, 0), 0)
self.assertEqual(add(-1, 1), 0)
def test_preserves_docstring(self):
@double_result
def func():
"""This is a docstring."""
return 1
self.assertEqual(func.__doc__, "This is a docstring.")
if __name__ == '__main__':
unittest.main()
이 테스트 케이스는 데코레이터의 기능과 메타데이터 보존 여부를 검증합니다.
9.3 데코레이터 성능 최적화 팁
데코레이터는 편리하지만 잘못 사용하면 성능 저하를 일으킬 수 있습니다. 다음은 데코레이터 성능을 최적화하는 몇 가지 팁입니다:
functools.lru_cache를 사용하여 계산 비용이 높은 데코레이터의 결과를 캐시합니다.
데코레이터 내부에서 불필요한 함수 호출을 피합니다.
데코레이터 체인이 너무 길어지지 않도록 주의합니다.
예를 들어:
import functools
import time
def slow_decorator(func):
@functools.wraps(func)
@functools.lru_cache(maxsize=None)
def wrapper(*args, **kwargs):
time.sleep(1) # 시뮬레이션을 위한 지연
return func(*args, **kwargs)
return wrapper
@slow_decorator
def add(x, y):
return x + y
# 첫 호출은 느리지만, 이후 호출은 캐시된 결과를 사용하여 빠릅니다.
print(add(3, 4)) # 1초 후 결과 출력
print(add(3, 4)) # 즉시 결과 출력
이 예제에서lru_cache를 사용하여 데코레이터의 결과를 캐시함으로써 반복된 호출의 성능을 크게 향상시킵니다.
10. 파이썬 3.9+ 신규 기능과 데코레이터
10.1 타입 힌트와 데코레이터
파이썬 3.5부터 도입된 타입 힌트를 데코레이터와 함께 사용하면 코드의 가독성과 안정성을 높일 수 있습니다.
from typing import Callable, TypeVar, Any
T = TypeVar('T')
def log_decorator(func: Callable[..., T]) -' Callable[..., T]:
def wrapper(*args: Any, **kwargs: Any) -' T:
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_decorator
def add(x: int, y: int) -' int:
return x + y
result = add(3, 4)
print(result) # 출력: 7
이 예제에서log_decorator는 입력 함수의 반환 타입을 보존합니다. 이를 통해 정적 타입 검사 도구가 데코레이터가 적용된 함수의 타입을 올바르게 추론할 수 있습니다.
10.2 비동기 프로그래밍과 데코레이터
파이썬 3.5부터 도입된async/await구문을 사용한 비동기 프로그래밍에서도 데코레이터를 활용할 수 있습니다.
이 예제에서async_timed데코레이터는 비동기 함수의 실행 시간을 측정합니다. 비동기 컨텍스트에서 작동하도록 설계되었습니다.
10.3 데코레이터의 미래
파이썬은 계속해서 진화하고 있으며, 데코레이터도 이에 발맞추어 발전하고 있습니다. 향후 파이썬 버전에서는 데코레이터와 관련된 다음과 같은 개선사항들이 논의되고 있습니다:
데코레이터 합성을 위한 더 나은 문법
메타프로그래밍 기능 강화
런타임 최적화를 위한 데코레이터 특화 기능
이러한 발전은 데코레이터를 더욱 강력하고 유연하게 만들 것입니다. 파이썬 개발자로서, 이러한 변화를 주시하고 새로운 기능을 효과적으로 활용하는 것이 중요합니다.
결론적으로, 데코레이터는 파이썬의 가장 강력하고 유연한 기능 중 하나입니다. 이 전자책을 통해 우리는 데코레이터의 기본 개념부터 고급 기술까지 광범위하게 살펴보았습니다.
데코레이터의 주요 장점을 다시 한 번 정리해보면:
코드 재사용성 향상: 데코레이터를 사용하면 공통 기능을 여러 함수나 클래스에 쉽게 적용할 수 있습니다.
관심사의 분리: 핵심 로직과 부가 기능을 깔끔하게 분리할 수 있습니다.
가독성과 유지보수성 개선: 잘 설계된 데코레이터는 코드를 더 읽기 쉽고 유지보수하기 쉽게 만듭니다.
메타프로그래밍 능력: 데코레이터를 통해 런타임에 함수나 클래스의 동작을 수정할 수 있습니다.
우리는 이 책에서 다음과 같은 주요 주제들을 다루었습니다:
데코레이터의 기본 개념과 문법
함수 데코레이터와 클래스 데코레이터
파라미터가 있는 데코레이터
실무에서 자주 사용되는 데코레이터 패턴들
데코레이터와 함수형 프로그래밍의 결합
웹 프레임워크에서의 데코레이터 활용
데코레이터 디버깅과 테스트 기법
최신 파이썬 버전에서의 데코레이터 활용
앞으로 파이썬이 발전함에 따라 데코레이터의 역할과 중요성은 더욱 커질 것으로 예상됩니다. 특히 메타프로그래밍, 비동기 프로그래밍, 타입 힌팅 등의 영역에서 데코레이터의 활용도가 높아질 것입니다.
파이썬 개발자로서 데코레이터를 마스터하는 것은 단순히 하나의 문법을 배우는 것 이상의 의미를 가집니다. 이는 파이썬의 철학과 강력한 기능을 깊이 이해하고, 더 효율적이고 우아한 코드를 작성할 수 있게 해주는 열쇠입니다.
이 책에서 다룬 내용들을 기반으로, 여러분만의 창의적인 데코레이터를 만들고 활용해보시기 바랍니다. 데코레이터는 단순한 문법적 요소를 넘어, 파이썬 프로그래밍의 예술이자 과학입니다. 여러분의 코드에 마법 같은 우아함을 불어넣는 데코레이터의 힘을 충분히 활용하시기 바랍니다.
마지막으로, 프로그래밍 세계에서 학습은 끝이 없는 여정임을 기억하세요. 데코레이터에 대해 더 깊이 탐구하고, 실제 프로젝트에 적용해보며, 다른 개발자들과 지식을 공유하는 것을 두려워하지 마세요. 그 과정에서 여러분은 더 나은 파이썬 개발자로 성장할 것입니다.
파이썬과 데코레이터의 마법 같은 세계에서 여러분의 모험이 즐겁고 보람찼기를 바랍니다. 행운을 빕니다!
✅ 오늘의 레터는 어땠어요?
아쉽지만 구독자님을 위해 준비한 오늘의 뉴스레터는 여기까지입니다. 🥹 오늘 받은 뉴스레터에 대한 솔직한 피드백을 주실 수 있으실까요? 또한 받아보고 싶은 주제가 있다면 적어주세요. 뉴스레터 발행에 참고토록 할게요. 🙏
댓글
의견을 남겨주세요