Language/Python

[Python] 파이썬 데코레이터 이해하기 : 함수의 동작을 확장하는 도구

박호연지기 2024. 8. 18. 22:36
반응형

 

 

@perf_clock
def time_func(seconds):
    time.sleep(seconds)

@perf_clock
def sum_func(*numbers):
    return sum(numbers)

 

파이썬의 데코레이터는 함수나 메소드의 동작을 수정, 확장할 때 사용하는 도구로

데코레이터를 활용하면 코드의 중복을 제거하고, 공통 함수를 작성하여 유지보수성을 높일 수 있다.

 

특히 로깅, 프레임워크, 유효성 체크 등 공통 기능에 유용하게 적용할 수 있다는 점이 장점!

단점으로는 코드 가독성 감소, 디버깅이 불편한 점 등이 있다.

 

1. @ 데코레이터란 무엇인가?

 

데코레이터는 다른 함수를 감싸 그 함수의 동작을 변경하거나 확장하는 함수를 말한다.

기존의 함수에 영향을 주지 않으면서 추가적인 기능을 부여할 수 있다는 점이 특징

 

def simple_decorator(func):
    def wrapper():
        print("Function is about to be called.")
        func()
        print("Function has been called.")
    return wrapper

@simple_decorator # 데코레이터 사용
def say_hello(): # say_hello 함수 정의
    print("Hello!")

say_hello()

# 결과
# Function is about to be called.
# Hello!
# Function has been called.

 

위 코드와 같이 say_hello 함수를 호출하면

say_hello 함수를 인자로 받아 simple_decorator 함수를 반환한다.

 

2. 동작 원리

데코레이터는 함수를 인자로 받아, 또 다른 함수를 반환하는 방식으로 동작한다.

데코레이터를 적용할 때 @decorator_name을 함수 정의 바로 위에 붙여 사용한다.

def decorator_name(func):
    def wrapper(*args, **kwargs):
        # 원래 함수 호출 전 작업
        result = func(*args, **kwargs)
        # 원래 함수 호출 후 작업
        return result
    return wrapper

 

*args와 **kwargs를 사용해 인자 개수나 형태에 상관없이 모든 함수를 처리할 수 있다.

 

`*`를 붙이는 규칙

 

  • *args:
    • *args에서 *는 함수가 임의의 개수의 위치 인자를 받을 수 있도록 합니다. 이 인자들은 튜플로 처리된다.
    • 예를 들어, def func(*args):라고 정의하면 func(1, 2, 3)처럼 여러 개의 인자를 전달할 수 있다. args는 (1, 2, 3)이라는 튜플로 함수 내에서 처리된다.
  • **kwargs:
    • **kwargs에서 **는 임의의 개수의 키워드 인자를 받아들인다. 이 인자들은 딕셔너리로 처리된다.
    • 예를 들어, def func(**kwargs):라고 정의하면 func(a=1, b=2)처럼 여러 개의 키워드 인자를 전달할 수 있다. kwargs는 {'a': 1, 'b': 2}이라는 딕셔너리로 함수 내에서 처리된다.
  • * 단독 사용:
    • 함수 정의에서 *를 단독으로 사용하면, 그 이후의 인자는 키워드 전용 인자로 강제된다. 즉, 그 인자들은 반드시 key=value 형식으로만 전달될 수 있다.
    • 예를 들어, def func(a, *, b, c):처럼 정의하면, func(1, b=2, c=3)처럼 b와 c는 반드시 키워드 인자로 전달해야 한다.

 

 

3. 예제

# 실행 시간 측정 : 함수의 실행 시간을 측정해보는 데코레이터

import time

def time_it(func):
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"{func.__name__} 실행 in {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@time_it
def compute():
    time.sleep(2)

compute()

# 결과
# compute 실행 in 2.0001 seconds

@time_it으로 데코레이터만 적용햇을 뿐인데 compute 함수를 실행하면 함수명과 실행 시간에 대한 데이터가 print문으로 출력된다.

 

# 권한 검사 : 사용자 권한에 따라 함수 실행을 제어하는 데코레이터

def requires_permission(permission):
    def decorator(func):
        def wrapper(user, *args, **kwargs):
            if user.has_permission(permission):
                return func(user, *args, **kwargs)
            else:
                raise PermissionError("Permission denied.")
        return wrapper
    return decorator

@requires_permission('admin')
def delete_user(user, user_id):
    print(f"User {user_id} deleted by {user.name}")

# 가정: 현재 사용자가 admin 권한을 가지고 있는 경우

사용자가 유저 정보를 삭제할 경우 위와 같은 데코레이터를 활용하여 실행 여부를 판단할 수 있다. 

 

# 중첩 데코레이터 사용

@decorator_one
@decorator_two
def my_function():
    pass

중첩 데코레이터를 사용하는 경우 데코레이터는 아래에서 위 순으로 적용된다.

 


 

데코레이터가 없어도 프로그래밍을 하는데 문제는 없다. 하지만 데코레이터를 사용하면 훨씬 우아한 구조를 만들 수 있다.

 

# 심화 학습

  • 데코레이터 패턴과 객체지향 프로그래밍의 융합
  • 파이썬 내장 데코레이터 (@staticmethod, @classmethod, @property)의 동작 원리
반응형