파이썬을 공부하다 보면 다음과 같은 문법을 자주 만나게 됩니다.
@staticmethod
def add(a, b):
return a + b
@classmethod
def from_string(cls, data):
pass
@property
def name(self):
return self._name처음에는 단순히 함수 위에 붙는 특별한 문법처럼 보이지만, 사실 이들은 모두 데코레이터(Decorator) 입니다.
데코레이터는 파이썬의 매우 강력한 기능 중 하나이며, 클래스 메서드, 정적 메서드, 프로퍼티뿐만 아니라 Flask, Django, FastAPI, PyTorch 등 다양한 프레임워크에서도 핵심적으로 사용됩니다.
이번 글에서는 데코레이터의 개념부터 시작하여 @staticmethod, @classmethod, @property, 그리고 사용자 정의 데코레이터까지 한 번에 정리해 보겠습니다.
1. 데코레이터(Decorator)란?
파이썬 데코레이터(Decorator)는 기존의 함수나 클래스를 수정하지 않고도 새로운 기능을 추가하거나 동작을 확장할 수 있게 해주는 메타프로그래밍 기능입니다. 원본 코드를 변경하지 않고 기능을 덧붙일 수 있어 코드의 재사용성과 유연성이 높아집니다.
예를 들어 함수 실행 전후에 로그를 출력하고 싶다고 가정해 보겠습니다.
def hello():
print("Hello Python")실행 결과
Hello Python
함수도 객체(Object)이다
파이썬에서는 함수도 객체입니다.
따라서 변수에 저장할 수 있습니다.
def hello():
print("Hello Python")
func = hello
func()결과
Hello Python
함수를 다른 함수에 전달할 수 있다
def hello():
print("Hello Python")
def execute(func):
func()
execute(hello)결과
Hello Python
함수를 인자로 전달할 수 있다는 특징 때문에 데코레이터를 구현할 수 있습니다.
2. 사용자 정의 데코레이터 만들기
함수 실행 전후에 로그를 출력하는 데코레이터를 만들어 보겠습니다.
def my_decorator(func):
def wrapper():
print("함수 실행 시작")
func()
print("함수 실행 종료")
return wrapper사용 방법
def hello():
print("Hello Python")
hello = my_decorator(hello)
hello()결과
함수 실행 시작
Hello Python
함수 실행 종료
3. @ 문법의 의미
위 코드는 다음과 같이 작성할 수 있습니다.
@my_decorator
def hello():
print("Hello Python")실제로는 아래 코드와 동일합니다.
def hello():
print("Hello Python")
hello = my_decorator(hello)즉,
@something
은
함수 = something(함수)
를 의미합니다.
4. 데코레이터를 사용하는 이유
데코레이터가 왜 필요한지 가장 쉽게 이해할 수 있는 예로, 여러 함수에 같은 안내 문구를 붙이고 싶을 때를 생각해 보겠습니다.
예를 들어 공부, 운동, 독서라는 세 개의 함수가 있고, 각각 실행될 때마다 앞뒤로 같은 메시지를 출력하고 싶다고 가정해 보겠습니다.
데코레이터가 없다면 각 함수마다 같은 코드를 반복해서 작성해야 합니다.
def study():
print("=== 시작 ===")
print("공부 중...")
print("=== 끝 ===")
def exercise():
print("=== 시작 ===")
print("운동 중...")
print("=== 끝 ===")
def read():
print("=== 시작 ===")
print("독서 중...")
print("=== 끝 ===")이렇게 작성하면 함수가 많아질수록 같은 코드가 계속 반복됩니다.
실행 결과는 다음과 같습니다.
study()
exercise()
read()=== 시작 ===
공부 중...
=== 끝 ===
=== 시작 ===
운동 중...
=== 끝 ===
=== 시작 ===
독서 중...
=== 끝 ===
하지만 데코레이터를 사용하면 공통 기능을 한 번만 정의한 뒤 여러 함수에 쉽게 적용할 수 있습니다.
def add_message(func):
def wrapper():
print("=== 시작 ===")
func()
print("=== 끝 ===")
return wrapper
@add_message
def study():
print("공부 중...")
@add_message
def exercise():
print("운동 중...")
@add_message
def read():
print("독서 중...")실행 결과는 다음과 같습니다.
study()
exercise()
read()=== 시작 ===
공부 중...
=== 끝 ===
=== 시작 ===
운동 중...
=== 끝 ===
=== 시작 ===
독서 중...
=== 끝 ===
이 예제에서 중요한 점은, 만약 시작 문구를 "=== 시작 ==="에서 "작업을 시작합니다"로 바꾸고 싶다면 데코레이터 안의 코드만 수정하면 된다는 것입니다.
즉, 여러 함수의 코드를 하나씩 고칠 필요가 없습니다.
이처럼 데코레이터를 사용하면 같은 기능을 여러 함수에 반복해서 넣지 않아도 되므로, 코드가 더 짧아지고 관리하기도 쉬워집니다.
실무 활용 예시
- 로그 출력
- 실행 시간 측정
- 캐싱(Cache)
- 권한 검사
- 인증(Authentication)
- 예외 처리
- API 호출 제한
5. @staticmethod
정적 메서드를 만드는 데코레이터입니다.
class Calculator:
@staticmethod
def add(a, b):
return a + b사용
result = Calculator.add(10, 20)
print(result)결과
30
특징
정적 메서드는
- self 없음
- cls 없음
- 객체 상태 사용 안 함
- 클래스 상태 사용 안 함
즉, 클래스 안에 존재하지만 일반 함수처럼 동작합니다.
활용 예시
class StringUtil:
@staticmethod
def is_email(email):
return '@' in emailprint(StringUtil.is_email("test@gmail.com"))결과
True
6. @classmethod
클래스 메서드를 만드는 데코레이터입니다.
class Student:
school = "OpenAI 고등학교"
@classmethod
def show_school(cls):
print(cls.school)사용
Student.show_school()결과
OpenAI 고등학교
cls란?
class Student:
@classmethod
def show_class(cls):
print(cls)결과
<class '__main__.Student'>
즉 cls는 현재 클래스를 의미합니다.
대표 활용: 대체 생성자
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def from_string(cls, data):
name, age = data.split(',')
return cls(name, int(age))사용
person = Person.from_string("Tom,20")
7. @property
프로퍼티(Property)를 만드는 데코레이터입니다.
메서드를 속성처럼 사용할 수 있게 해줍니다.
일반적인 방법
class Person:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name사용
person = Person("Tom")
print(person.get_name())결과
Tom
@property 사용
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name사용
person = Person("Tom")
print(person.name)결과
Tom
메서드인데 마치 속성처럼 사용할 수 있습니다.
8. Setter 추가하기
class Person:
def __init__(self, age):
self._age = age # 내부에서 관리하는 실제 값
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("나이는 정수여야 합니다.")
if value < 0:
raise ValueError("나이는 0보다 작을 수 없습니다.")
self._age = value사용
person = Person(20)
print(person.age) # 나이 읽기
person.age = 21 # 나이 바꾸기
print(person.age)결과
20
21
어떻게 동작할까?
위 코드에서 age는 그냥 일반 변수처럼 보이지만, 실제로는 메서드입니다.
하지만 @property를 붙였기 때문에 다음처럼 사용할 수 있습니다.
print(person.age)
이 코드는 내부적으로 age() 메서드를 직접 호출하는 것이 아니라, @property가 만든 getter를 실행해서 값을 가져옵니다.
그리고 아래 코드는
person.age = 21
setter를 실행합니다. 즉, 값을 바로 바꾸는 것처럼 보이지만 실제로는 중간에서 검사를 한 뒤에 값을 저장합니다.
왜 setter가 필요할까?
예를 들어 나이에 음수를 넣는 실수를 막고 싶다고 해 보겠습니다.
person.age = -3
이 코드는 setter를 거치기 때문에 다음과 같은 오류가 발생합니다.
ValueError: 나이는 0보다 작을 수 없습니다.
또한 숫자가 아닌 값을 넣는 것도 막을 수 있습니다.
person.age = "스무 살"
이 경우에는 다음과 같은 오류가 발생합니다.
TypeError: 나이는 정수여야 합니다.
즉, setter를 사용하면 데이터를 저장하기 전에 조건을 검사할 수 있어서 잘못된 값이 들어가는 것을 방지할 수 있습니다.
왜 _age처럼 밑줄을 붙일까?
self._age
처럼 밑줄을 붙이는 이유는 이 값이 클래스 내부에서 관리하는 값이라는 뜻을 나타내기 위해서입니다.
파이썬에서는 밑줄이 붙은 변수에 접근하는 것을 완전히 막지는 않지만, 보통 외부에서 직접 건드리지 말라는 의미로 사용합니다.
예를 들어 아래처럼 직접 바꾸면 검사를 건너뛸 수 있습니다.
person._age = -100
이렇게 하면 setter를 통하지 않기 때문에 잘못된 값이 들어갈 수 있습니다.
그래서 실제로는 person._age를 직접 바꾸지 않고, person.age = ...처럼 property와 setter를 통해 값을 바꾸는 것이 좋습니다.
쉽게 이해하는 비유
setter는 문 앞의 경비원과 비슷합니다.
person.age로 값을 읽을 때는 getter가 값을 보여 줍니다.person.age = 21로 값을 바꿀 때는 setter가 먼저 확인합니다.- 값이 올바르면 통과
- 값이 잘못되면 거절
즉, setter는 데이터를 저장하기 전에 한 번 더 검사해 주는 안전장치입니다.
정리
@property와 @setter를 함께 사용하면 다음과 같은 장점이 있습니다.
- 값을 읽을 때는 속성처럼 간단하게 사용할 수 있다
- 값을 바꿀 때는 중간에서 검사를 할 수 있다
- 잘못된 데이터가 들어가는 것을 막을 수 있다
- 코드가 더 깔끔하고 사용하기 쉬워진다
즉, @property는 읽기 편하게 만들어 주고, @setter는 값을 안전하게 바꿀 수 있게 해 줍니다.
9. 사용자 정의 데코레이터 실전 예제
함수 실행 시간을 측정하는 데코레이터를 만들어 보겠습니다.
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"실행 시간: {end - start:.3f}초")
return result
return wrapper사용
@timer
def work():
time.sleep(1)
work()결과
실행 시간: 1.001초
10. 여러 개의 데코레이터 적용
@decorator1
@decorator2
def hello():
pass실제 실행 순서
hello = decorator1(
decorator2(hello)
)즉 아래에서 위 순서로 적용됩니다.
11. 세 가지 메서드 비교
class Example:
class_var = 100
def instance_method(self):
print(self.class_var)
@classmethod
def class_method(cls):
print(cls.class_var)
@staticmethod
def static_method():
print("Hello")| 구분 | self | cls | 객체 생성 |
|---|---|---|---|
| 인스턴스 메서드 | O | X | O |
| 클래스 메서드 | X | O | X |
| 정적 메서드 | X | X | X |
12. 데코레이터 정리
| 데코레이터 | 역할 |
| @staticmethod | 정적 메서드 생성 |
| @classmethod | 클래스 메서드 생성 |
| @property | 메서드를 속성처럼 사용 |
| @xxx.setter | 프로퍼티 값 설정 |
| 사용자 정의 데코레이터 | 기능 확장 |
핵심 요약
@staticmethod
- 객체 정보 사용 안 함
- 클래스 정보 사용 안 함
- 유틸리티 함수 구현 시 사용
@classmethod
- cls 사용
- 클래스 변수 접근 가능
- 대체 생성자 구현 시 사용
@property
- 메서드를 속성처럼 사용
- Getter / Setter 구현 가능
- 데이터 검증에 유용
사용자 정의 데코레이터
- 로그 출력
- 실행 시간 측정
- 권한 검사
- 예외 처리
- 캐싱
데코레이터는 단순히 함수 위에 붙는 문법이 아니라, 파이썬의 함수 객체 개념을 활용한 강력한 기능입니다. 특히 @staticmethod, @classmethod, @property를 이해하면 클래스 설계를 더욱 깔끔하게 할 수 있으며, 나아가 Flask의 @app.route, PyTorch의 @torch.no_grad, Dataclass의 @dataclass 같은 고급 기능도 훨씬 쉽게 이해할 수 있습니다.

