Launguage

Python 클래스 데코레이터 완벽 가이드

Somaz 2025. 11. 21. 00:00
728x90
반응형

Overview

데코레이터는 Python의 강력한 기능 중 하나로, 코드를 간결하고 우아하게 만들어준다.

클래스에서 사용하는 주요 데코레이터들을 알아보자.

 

 

 

 

 


 

 

1. @property: Getter 만들기

 

`@property` 는 메서드를 속성처럼 사용할 수 있게 해준다.

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        """반지름 getter"""
        return self._radius
    
    @property
    def diameter(self):
        """지름 계산"""
        return self._radius * 2
    
    @property
    def area(self):
        """넓이 계산"""
        return 3.14159 * self._radius ** 2

# 사용
circle = Circle(5)
print(circle.radius)    # 5 - 메서드처럼 ()가 필요 없음!
print(circle.diameter)  # 10
print(circle.area)      # 78.53975

 

 

장점

  • 메서드를 속성처럼 깔끔하게 사용
  • 계산된 값을 속성처럼 접근 가능
  • 내부 로직을 숨기고 깔끔한 인터페이스 제공

 

 

 

 

 

2. @property.setter: Setter 만들기

속성 값을 설정할 때 유효성 검사나 추가 로직을 넣을 수 있다.

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
    
    @property
    def age(self):
        return self._age
    
    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("나이는 0보다 작을 수 없습니다!")
        if value > 150:
            raise ValueError("나이가 너무 많습니다!")
        self._age = value
    
    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, value):
        if not value or not value.strip():
            raise ValueError("이름은 비어있을 수 없습니다!")
        self._name = value.strip()

# 사용
person = Person("철수", 25)
print(person.age)  # 25

person.age = 30    # setter 호출
print(person.age)  # 30

person.age = -5    # ValueError: 나이는 0보다 작을 수 없습니다!

 

 

왜 사용하나요?

  • 데이터 유효성 검사
  • 값 변경 시 추가 작업 수행
  • 캡슐화: 내부 구현을 숨기면서 안전하게 값 변경

 

 

 

 

 

3. @property.deleter: Deleter 만들기

속성 삭제 시 동작을 정의한다.

class BankAccount:
    def __init__(self, balance):
        self._balance = balance
    
    @property
    def balance(self):
        return self._balance
    
    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("잔액은 음수일 수 없습니다!")
        self._balance = value
    
    @balance.deleter
    def balance(self):
        print("계좌를 초기화합니다.")
        self._balance = 0

# 사용
account = BankAccount(10000)
print(account.balance)  # 10000

del account.balance  # 계좌를 초기화합니다.
print(account.balance)  # 0

 

 

 

 

4. @dataclass: 자동으로 클래스 생성

Python 3.7+에서 도입된 `@dataclass` 는 보일러플레이트 코드를 줄여준다.

from dataclasses import dataclass, field
from typing import List

# 일반 클래스
class PersonOld:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email
    
    def __repr__(self):
        return f"Person(name={self.name}, age={self.age}, email={self.email})"
    
    def __eq__(self, other):
        return (self.name == other.name and 
                self.age == other.age and 
                self.email == other.email)

# @dataclass 사용
@dataclass
class Person:
    name: str
    age: int
    email: str
    hobbies: List[str] = field(default_factory=list)  # 기본값

# 사용
person1 = Person("철수", 25, "chulsoo@email.com")
person2 = Person("철수", 25, "chulsoo@email.com")

print(person1)  # Person(name='철수', age=25, email='chulsoo@email.com', hobbies=[])
print(person1 == person2)  # True - __eq__ 자동 생성!

 

 

@dataclass 옵션

@dataclass(
    frozen=True,      # 불변 객체로 만들기
    order=True,       # <, >, <=, >= 비교 연산자 추가
    eq=True,          # == 연산자 (기본값 True)
    repr=True         # __repr__ 자동 생성 (기본값 True)
)
class Product:
    name: str
    price: int
    stock: int = 0  # 기본값

 

 

 

5. 클래스 데코레이터: 클래스 자체를 수정

클래스 전체에 데코레이터를 적용할 수 있다.

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 Database:
    def __init__(self):
        print("데이터베이스 연결 생성!")
        self.connection = "Connected"

# 사용
db1 = Database()  # 데이터베이스 연결 생성!
db2 = Database()  # 출력 없음 - 같은 인스턴스 반환
print(db1 is db2)  # True

 

 

6. 메서드 데코레이터: 성능 측정

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"{func.__name__} 실행 시간: {end - start:.4f}초")
        return result
    return wrapper

class DataProcessor:
    @timer
    def process_large_data(self, data_size):
        """대용량 데이터 처리 시뮬레이션"""
        total = 0
        for i in range(data_size):
            total += i
        return total

# 사용
processor = DataProcessor()
result = processor.process_large_data(1000000)
# process_large_data 실행 시간: 0.0234초

 

 

7. 실전 예제: 모든 데코레이터 활용

from dataclasses import dataclass
from functools import wraps

def validate_positive(func):
    """양수 검증 데코레이터"""
    @wraps(func)
    def wrapper(self, value):
        if value <= 0:
            raise ValueError(f"{func.__name__}: 양수만 가능합니다!")
        return func(self, value)
    return wrapper

class Product:
    def __init__(self, name, price, stock):
        self._name = name
        self._price = price
        self._stock = stock
    
    @property
    def name(self):
        return self._name
    
    @property
    def price(self):
        return self._price
    
    @price.setter
    @validate_positive
    def price(self, value):
        self._price = value
    
    @property
    def stock(self):
        return self._stock
    
    @stock.setter
    @validate_positive
    def stock(self, value):
        self._stock = value
    
    @property
    def total_value(self):
        """재고 총액 계산"""
        return self._price * self._stock
    
    @staticmethod
    def apply_tax(price, tax_rate=0.1):
        """세금 계산"""
        return price * (1 + tax_rate)
    
    @classmethod
    def create_bundle(cls, name, items):
        """묶음 상품 생성"""
        total_price = sum(item.price for item in items)
        total_stock = min(item.stock for item in items)
        return cls(name, total_price, total_stock)

# 사용
laptop = Product("노트북", 1000000, 50)
print(laptop.total_value)  # 50000000

laptop.price = 1200000  # setter로 가격 변경
print(laptop.total_value)  # 60000000

print(Product.apply_tax(laptop.price))  # 1320000.0

laptop.price = -100  # ValueError: price: 양수만 가능합니다!

 

 

데코레이터 비교 정리

데코레이터 용도 적용 대상
`@property` Getter 만들기 메서드
`@속성.setter` Setter 만들기 메서드
`@속성.deleter` Deleter 만들기 메서드
`@staticmethod` 정적 메서드 메서드
`@classmethod` 클래스 메서드 메서드
`@dataclass` 자동 클래스 생성 클래스
커스텀 데코레이터 원하는 기능 추가 클래스/메서드

 

 

 

 

 


 

 

결론

데코레이터를 잘 활용하면

  • 코드가 간결하고 읽기 쉬워진다
  • 반복되는 로직을 재사용할 수 있다
  • 관심사를 분리하여 유지보수가 쉬워진다
  • 더 안전하고 견고한 코드를 작성할 수 있다

 

 

데코레이터는 처음엔 어렵게 느껴질 수 있지만, 익숙해지면 없어서는 안 될 강력한 도구가 된다.

 

 

 

 

 

 

 


Reference

튜토리얼 & 가이드

심화 자료

728x90
반응형