[Python] 데코레이터(@)
데코레이터란
Python 데코레이터(decorator)는 함수 또는 메서드의 동작을 수정하거나 확장하는 방법을 제공하는 고급 기능입니다. 데코레이터는 다른 함수나 메서드를 인수로 받아, 그 함수나 메서드의 앞뒤로 추가적인 동작을 수행한 뒤 원래의 함수나 메서드를 반환합니다.
데코레이터의 기본 구조
데코레이터 함수 정의: 데코레이터는 일반적으로 함수를 감싸는 함수로 정의됩니다.
데코레이터 적용: 데코레이터는 함수 정의 바로 위에 @데코레이터 이름 형태로 적용됩니다.
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 # @데코레이터 이름(my_decorator)
def say_hello():
print("Hello!")
say_hello()
이 예제에서는 say_hello 함수에 my_decorator 데코레이터가 적용되어, say_hello 함수가 호출될 때마다 데코레이터가 정의한 추가 동작이 실행됩니다.
데코레이터 다양한 활용 방법
데코레이터가 인자를 받는 경우
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello()
설명
데코레이터 팩토리 (repeat):
repeat 함수는 num_times 인자를 받아 데코레이터를 반환합니다.
decorator 함수는 func를 인수로 받아 wrapper 함수를 정의합니다.
데코레이터 적용:
@repeat(3)을 사용하여 say_hello 함수에 데코레이터를 적용합니다. 이는 repeat(3)이 decorator 함수를 반환하고, decorator(say_hello)가 실행되어 wrapper 함수를 반환하는 것과 같습니다.
함수 실행:
say_hello 함수가 호출될 때마다 wrapper 함수가 실행되며, 이 함수는 num_times만큼 func를 호출합니다.
@staticmethod
class MyClass:
class_variable = "Hello, static method!"
def __init__(self, value):
self.value = value
@staticmethod
def static_method():
print("This is a static method.")
def instance_method(self):
print(f"This is an instance method. Value is {self.value}")
# 정적 메서드 호출
MyClass.static_method() # 출력: This is a static method.
# 인스턴스 생성 후 인스턴스 메서드 호출
obj = MyClass(42)
obj.instance_method() # 출력: This is an instance method. Value is 42
특징
클래스 네임스페이스:
정적 메서드는 클래스에 속해 있으며, 인스턴스에 속해 있지 않습니다. 따라서 클래스의 모든 인스턴스에서 동일한 메서드를 호출할 수 있습니다.
인스턴스 변수 접근 불가:
정적 메서드는 self 매개변수를 사용하지 않기 때문에, 해당 메서드 내부에서는 인스턴스의 속성에 접근할 수 없습니다.
클래스 변수 접근 가능:
클래스 변수는 self 대신 클래스 이름으로 접근할 수 있습니다. 위의 예제에서 class_variable은 MyClass.class_variable로 바로 접근할 수 있습니다.
@classmethod
class MyClass:
class_variable = 10
@classmethod
def class_method(cls, x):
cls.class_variable += x
return cls.class_variable
# MyClass의 인스턴스 생성
obj1 = MyClass()
obj2 = MyClass()
# 클래스 메서드를 통해 클래스 변수 변경
print(obj1.class_method(5)) # 출력: 15
print(obj2.class_variable) # 출력: 15
특징
클래스 네임스페이스:
@classmethod 데코레이터는 클래스에 속하는 메서드로, 인스턴스와 독립적으로 클래스의 네임스페이스에서 동작합니다. 즉, 모든 인스턴스에서 동일한 메서드를 호출할 수 있습니다.
인스턴스 변수 접근 불가:
클래스 메서드는 첫 번째 파라미터로 클래스 자신을 가리키는 cls를 받으며, 인스턴스의 속성에는 직접 접근할 수 없습니다. 즉, self를 사용하여 인스턴스 변수에 접근할 수 없습니다.
클래스 변수 접근 가능:
클래스 메서드 내부에서는 클래스 변수에 접근할 수 있습니다. 클래스 변수는 클래스 이름을 통해 접근할 수 있으므로, cls.class_variable와 같은 형태로 사용할 수 있습니다.
@property & @메서드.setter
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, val):
self._radius = val
@property
def area(self):
return 3.14 * self._radius ** 2
# 사용 예시
circle = Circle(5)
print(circle.radius) # 5
circle.radius = 10
print(circle.radius) # 10
# 필요에 따라 계산된 값을 반환하도록 할 수 있습니다.
print(circle.area) # 314.0
@property 특징
읽기 전용 속성 제공: 속성을 읽을 수는 있지만 직접적으로 값을 설정할 수는 없습니다.
계산된 속성 제공: 내부적으로 다른 속성이나 데이터를 기반으로 값을 계산할 수 있습니다
@변수.setter 특징
쓰기 가능 속성: @property 데코레이터로 정의된 속성에 대해 값을 설정할 수 있게 해줍니다. @변수.setter를 사용하여 setter 메서드를 정의하면 속성에 값을 설정할 수 있습니다.
@cached_property
# Python >= 3.8
from funtools import cached_property
class MycClass:
def __init__(self, value):
self.value = value
@cashed_property
def expensive_calculation(self):
print('Calculating...')
return self.value * 2
obj = MyClass(10)
print(obj.expensive_calculation) # Calculation... 출력후 20
print(obj.expensive_calculation) # 캐싱된 값 20만 반환
@cached_property 특징
읽기 전용 속성 제공: 속성을 읽을 수는 있지만 직접적으로 값을 설정할 수는 없습니다.
계산된 속성 제공: 내부적으로 다른 속성이나 데이터를 기반으로 값을 계산할 수 있으며, 한 번 계산된 후에는 해당 값을 캐싱하여 반복적인 계산을 피할 수 있다는 점이 @property와 다른 점입니다.
@contextmanager
from contextlib import contextmanager
import os
@contextmanager
def open_file(file_path, mode):
# 파일 존재 여부 확인 및 생성
file = os.path.dirname(file_path)
if not os.path.isdir(file):
os.makedirs(file, exist_ok=True)
f = open(file_path, mode) # 파일 열기
try:
yield f
finally: # try 블럭 내부에 예러가 발생해도 무조건 실행!
f.close()
file_name = 'text/example.txt'
data_to_write = 'hello, world!'
# 파일을 쓰기 모드로 열고 데이터 쓰기
with open_file(file_name, 'w') as f:
f.write(data_to_write)
# 파일을 읽기 모드로 열고 데이터 읽기
with open_file(file_name, 'r') as f:
content = f.read()
print(content) # 'hello, world!'가 출력됩니다.
특징
리소스 관리 간소화:
@contextmanager를 사용하면 파일, 네트워크 연결, 데이터베이스 커넥션 등 리소스를 열고 닫는 작업을 자동으로 관리할 수 있습니다. with 블록을 벗어나면 자동으로 리소스가 해제됩니다.(거의 대부분 with문에서만 사용됩니다.)
가독성 향상:
코드가 간결해지고 가독성이 높아집니다. 리소스 할당과 해제의 논리를 한곳에 모아두어 코드의 유지보수가 쉬워집니다.
예외 처리 용이:
리소스 사용 중 발생하는 예외를 처리하기 쉽습니다. try…finally 블록을 사용하여 yield 이후의 코드는 with 블록에서 예외가 발생하더라도 실행되어 리소스가 적절히 해제됩니다.
@wraps
import inspect
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper
@my_decorator
def say_hello(name):
"""Greet someone by their name."""
return f"Hello, {name}!"
print(say_hello("Alice"))
'''
output:
Something is happening before the function is called.
Something is happening after the function is called.
Hello, Alice!
'''
print(say_hello) # <function say_hello at 0x102b45670>
print(say_hello.__doc__) # Greet someone by their name.
print(inspect.signature(say_hello)) # (name)
특징
원래 함수의 메타데이터 유지:
@wraps는 데코레이터가 원래 함수의 이름, 문서화 문자열(docstring), 모듈 등을 유지하도록 합니다.(@wraps가 없으면 say_hello는 wrapper이기 때문에 sayhello.__doc__의 값은 None이 출력됨)
디버깅 용이:
원래 함수의 정보를 유지하므로, 디버깅할 때 데코레이터가 적용된 함수의 이름과 문서화 문자열을 쉽게 참조할 수 있습니다.
함수 서명 유지:
데코레이터를 사용하더라도 원래 함수의 인자 리스트와 반환 값을 유지할 수 있습니다. 이는 IDE나 도구에서 함수의 서명을 정확하게 표시하는 데 도움이 됩니다.(@wraps가 없으면 함수서명의 값은 (*args, **kwargs)로 나옴)
데코레이터의 사용 사례
로깅(Logging): 함수가 호출될 때마다 로그를 남기는 데코레이터를 만들어 사용할 수 있습니다.
성능 측정(Timing): 함수 실행 시간을 측정하여 성능을 모니터링하는 데 유용합니다.
접근 제어(Access Control): 사용자의 권한을 확인하여 함수 접근을 제어할 수 있습니다.
캐싱(Caching): 함수의 반환값을 캐시하여 동일한 입력에 대해 중복 계산을 피할 수 있습니다.