앞에서 함수 데코레이터에 대해 알아봤습니다.
함수 데코레이터는 기존 함수를 직접 수정하지 않고 새로운 기능을 추가할 때 사용했습니다.
@decorator
def hello():
print("Hello")
그런데 데코레이터는 함수에만 사용할 수 있는 것이 아닙니다.
클래스에도 데코레이터를 적용할 수 있습니다.
@decorator
class Person:
...
이것을 클래스 데코레이터(Class Decorator)라고 합니다.
클래스 데코레이터는 기존 클래스에 기능을 추가하거나, 클래스의 동작을 변경하거나, 여러 클래스에 공통적인 기능을 적용할 때 활용할 수 있습니다.
이번 글에서는 클래스 데코레이터가 어떻게 동작하는지부터 실제 활용 방법까지 알아보겠습니다.
1. 클래스 데코레이터란?
클래스 데코레이터는 클래스를 인자로 받아 클래스를 수정하거나 새로운 클래스를 반환하는 함수입니다.
가장 간단한 형태는 다음과 같습니다.
def decorator(cls):
return cls
그리고 클래스에 적용합니다.
@decorator
class Person:
pass
이 코드는 개념적으로 다음과 같습니다.
class Person:
pass
Person = decorator(Person)
즉, 클래스 데코레이터의 핵심은 다음과 같습니다.
클래스
↓
클래스 데코레이터
↓
수정되거나 확장된 클래스
함수 데코레이터와 기본적인 원리는 같습니다.
2. 가장 간단한 클래스 데코레이터
먼저 클래스가 제대로 전달되는지 확인해 보겠습니다.
def show_class(cls):
print(f"클래스 이름: {cls.__name__}")
return cls
@show_class
class Person:
pass
실행하면 다음과 같이 출력됩니다.
클래스 이름: Person
여기서 show_class()는 Person 클래스를 전달받았습니다.
def show_class(cls):
여기서 cls가 바로 클래스입니다.
그리고 마지막에
return cls
를 반환합니다.
클래스 데코레이터를 사용할 때는 클래스를 반환하는 구조가 자주 사용됩니다.
3. 클래스에 메서드 추가하기
클래스 데코레이터를 활용하면 기존 클래스에 새로운 메서드를 추가할 수 있습니다.
예를 들어 다음과 같은 Person 클래스가 있다고 해보겠습니다.
class Person:
def __init__(self, name):
self.name = name
여기에 클래스 데코레이터를 사용해서 hello() 메서드를 추가할 수 있습니다.
def add_hello_method(cls):
def hello(self):
return f"안녕하세요. 저는 {self.name}입니다."
cls.hello = hello
return cls
이제 클래스에 적용합니다.
@add_hello_method
class Person:
def __init__(self, name):
self.name = name
사용해 보면 다음과 같습니다.
person = Person("Kim")
print(person.hello())
결과:
안녕하세요. 저는 Kim입니다.
원래 Person 클래스에는 hello()가 없었습니다.
class Person:
def __init__(self, name):
self.name = name
하지만 클래스 데코레이터가 실행되면서 hello() 메서드가 추가되었습니다.
4. 클래스 속성도 추가할 수 있다
메서드뿐만 아니라 클래스 속성도 추가할 수 있습니다.
def add_version(cls):
cls.version = "1.0"
return cls
사용:
@add_version
class Person:
pass
이제 다음과 같이 사용할 수 있습니다.
print(Person.version)
결과:
1.0
객체에서도 접근할 수 있습니다.
person = Person()
print(person.version)
클래스에 추가된 속성이기 때문에 객체에서도 사용할 수 있습니다.
5. 여러 클래스에 같은 기능을 추가할 수 있다
클래스 데코레이터의 장점 중 하나는 여러 클래스에 동일한 기능을 쉽게 적용할 수 있다는 것입니다.
예를 들어 여러 클래스에 버전 정보를 추가한다고 해보겠습니다.
def add_version(cls):
cls.version = "1.0"
return cls
이제 여러 클래스에 적용할 수 있습니다.
@add_version
class User:
pass
@add_version
class Product:
pass
@add_version
class Order:
pass
각 클래스에서 사용할 수 있습니다.
print(User.version)
print(Product.version)
print(Order.version)
결과:
1.0
1.0
1.0
클래스마다 직접 다음 코드를 작성하지 않아도 됩니다.
version = "1.0"
공통 기능을 데코레이터 하나로 관리할 수 있습니다.
6. 클래스 데코레이터로 로깅 기능 추가하기
클래스 데코레이터는 클래스에 공통적인 동작을 추가할 때도 사용할 수 있습니다.
예를 들어 객체가 생성될 때 로그를 출력하도록 만들어 보겠습니다.
def log_creation(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
print(f"{cls.__name__} 객체 생성")
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
이제 클래스에 적용합니다.
@log_creation
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
객체를 생성하면:
person = Person("Kim", 30)
결과:
Person 객체 생성
기존 __init__()을 완전히 없앤 것이 아닙니다.
원래 __init__()을 저장해 두고 새로운 __init__()에서 호출한 것입니다.
구조를 보면 다음과 같습니다.
기존 __init__()
↓
original_init
↓
새로운 __init__()
↓
로그 출력
↓
기존 __init__() 실행
이런 방식으로 기존 클래스의 동작을 유지하면서 새로운 기능을 추가할 수 있습니다.
7. *args, **kwargs를 사용하는 이유
앞의 예제에서 다음 코드를 사용했습니다.
def new_init(self, *args, **kwargs):
그 이유는 데코레이터가 어떤 클래스에 적용될지 미리 알 수 없기 때문입니다.
예를 들어 다음 클래스가 있을 수 있습니다.
class Person:
def __init__(self, name, age):
...
또 다른 클래스는 다음과 같을 수 있습니다.
class Product:
def __init__(self, name, price, category):
...
생성자의 매개변수가 서로 다릅니다.
따라서 클래스 데코레이터에서 특정 매개변수만 지정하면 범용적으로 사용하기 어렵습니다.
def new_init(self, name, age):
대신
def new_init(self, *args, **kwargs):
를 사용하면 다양한 인자를 받을 수 있습니다.
그리고 기존 생성자에 그대로 전달합니다.
original_init(self, *args, **kwargs)
이렇게 하면 원래 클래스의 생성자 구조를 최대한 유지할 수 있습니다.
8. 클래스 데코레이터와 함수 데코레이터의 차이
함수 데코레이터와 클래스 데코레이터는 이름은 비슷하지만 무엇을 대상으로 하는지가 다릅니다.
함수 데코레이터
def decorator(func):
def wrapper():
...
return wrapper
함수를 전달받습니다.
@decorator
def hello():
...
클래스 데코레이터
def decorator(cls):
...
return cls
클래스를 전달받습니다.
@decorator
class Person:
...
정리하면 다음과 같습니다.
| 구분 | 함수 데코레이터 | 클래스 데코레이터 |
|---|---|---|
| 대상 | 함수 | 클래스 |
| 전달받는 값 | 함수 객체 | 클래스 객체 |
| 목적 | 함수 동작 변경/확장 | 클래스 동작 변경/확장 |
| 적용 | @decorator + 함수 | @decorator + 클래스 |
둘 다 기본적인 데코레이터 문법은 같습니다.
9. 클래스 자체를 다른 클래스로 바꾸는 방법
클래스 데코레이터는 기존 클래스를 수정하는 것뿐만 아니라 새로운 클래스를 반환하는 방식으로도 사용할 수 있습니다.
예를 들어 원래 클래스를 감싸는 새로운 클래스를 만들어 보겠습니다.
def add_logging(cls):
class Wrapper:
def __init__(self, *args, **kwargs):
print("객체 생성")
self.instance = cls(*args, **kwargs)
def __getattr__(self, name):
return getattr(self.instance, name)
return Wrapper
사용하면:
@add_logging
class Person:
def __init__(self, name):
self.name = name
def hello(self):
return f"안녕하세요. {self.name}입니다."
이제:
person = Person("Kim")
print(person.hello())
와 같이 사용할 수 있습니다.
이 방식은 원래 클래스를 직접 수정하는 대신 새로운 클래스로 감싸는 방식입니다.
다만 이런 구조는 동작 원리가 조금 복잡해질 수 있기 때문에 간단한 기능을 추가하는 경우에는 기존 클래스에 메서드나 속성을 추가하는 방식이 더 이해하기 쉽습니다.
10. 클래스 데코레이터를 여러 개 사용할 수도 있다
함수 데코레이터와 마찬가지로 클래스에도 여러 데코레이터를 적용할 수 있습니다.
@decorator_a
@decorator_b
class Person:
pass
이 경우 아래와 같은 형태로 동작합니다.
Person = decorator_a(decorator_b(Person))
즉, 아래쪽 데코레이터부터 먼저 적용됩니다.
Person
↓
decorator_b
↓
decorator_a
↓
최종 클래스
따라서 데코레이터를 여러 개 사용할 때는 적용 순서를 주의해야 합니다.
11. 클래스 데코레이터를 언제 사용할까?
클래스 데코레이터는 모든 클래스에 사용하는 기능은 아닙니다.
특히 여러 클래스에서 반복적으로 사용되는 공통 기능을 자동으로 추가할 때 유용합니다.
예를 들면 다음과 같은 경우입니다.
공통 메타데이터 추가
@add_version
class User:
pass
로깅 기능 추가
@log_creation
class User:
pass
클래스에 공통 메서드 추가
@add_hello_method
class Person:
pass
클래스 생성 과정을 변경
객체가 만들어지는 과정을 감싸서 추가적인 작업을 수행할 수도 있습니다.
여러 클래스에 동일한 규칙 적용
여러 클래스가 같은 구조를 가져야 할 때 데코레이터를 사용하면 공통 기능을 한 곳에서 관리할 수 있습니다.
12. 클래스 데코레이터와 상속은 어떻게 다를까?
클래스에 기능을 추가한다는 점에서 클래스 데코레이터와 상속이 비슷해 보일 수 있습니다.
하지만 목적은 다릅니다.
상속은 일반적으로 기존 클래스의 구조와 기능을 물려받아 새로운 클래스를 만드는 것입니다.
class Animal:
def eat(self):
print("먹습니다.")
class Dog(Animal):
def bark(self):
print("멍멍")
반면 클래스 데코레이터는 기존 클래스에 공통적인 기능을 추가하거나 동작을 변경하는 데 더 적합합니다.
@add_version
class Dog:
pass
간단하게 생각하면 다음과 같습니다.
상속
→ 기존 클래스를 기반으로 새로운 클래스를 만든다.
클래스 데코레이터
→ 기존 클래스에 공통 기능을 추가하거나 변경한다.
둘은 서로 대체 관계라기보다 목적에 따라 선택하는 기능입니다.
13. 클래스 데코레이터를 사용할 때 주의할 점
클래스 데코레이터는 편리하지만 너무 복잡하게 사용하면 클래스의 실제 동작을 파악하기 어려워질 수 있습니다.
예를 들어 다음 코드만 보면:
@decorator
class Person:
...
Person 클래스에 어떤 변화가 일어나는지 바로 알기 어려울 수 있습니다.
특히 데코레이터 내부에서 __init__()이나 다른 메서드를 변경하는 경우에는 디버깅할 때 주의해야 합니다.
또한 기존 메서드를 교체하는 방식이라면 원래 메서드의 동작을 제대로 유지해야 합니다.
따라서 클래스 데코레이터는
반복되는 공통 기능을 하나의 규칙으로 묶을 때
사용하는 것이 좋습니다.
단순한 기능 하나를 추가하기 위해 지나치게 복잡한 클래스 데코레이터를 만드는 것은 오히려 코드의 가독성을 떨어뜨릴 수 있습니다.
마무리
이번 글에서는 Python 클래스 데코레이터(Class Decorator)에 대해 알아봤습니다.
클래스 데코레이터의 기본 구조는 간단합니다.
def decorator(cls):
# 클래스에 기능 추가
return cls
그리고 클래스에 @ 문법으로 적용합니다.
@decorator
class Person:
pass
이는 다음과 같은 코드와 같은 의미입니다.
class Person:
pass
Person = decorator(Person)
클래스 데코레이터를 활용하면 기존 클래스를 직접 수정하지 않고도 공통 속성이나 메서드를 추가하고, 객체 생성 과정을 변경하거나 여러 클래스에 동일한 기능을 적용할 수 있습니다.
이번 글까지 살펴보면서 클래스의 기본적인 기능뿐만 아니라 property, Special Method, Callable Object, 클래스 데코레이터까지 알아봤습니다.
다음부터는 클래스의 중요한 객체지향 개념으로 넘어갑니다.
다음 글에서는 상속(Inheritance)을 살펴보겠습니다.
기존 클래스의 기능을 새로운 클래스가 물려받는 방법부터 부모 클래스와 자식 클래스의 관계, 메서드 오버라이딩, super()까지 차근차근 알아보겠습니다.