Python: decorator (데코레이터)

dev-swd·2020년 11월 2일
1

python

목록 보기
14/23
post-thumbnail

파이썬 데코레이터 개념 정리

  • 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용한다.
  • 기본 형태는 아래와 같다.
def test_decorator(func):
	def wrapper():
    	func()
    return wapper

def test():
	print("run test func")

deco = test_decorator(test)
deco() 
# test
# run test func
  1. test_decorator 함수를 만들고, 호출할 함수를 파라미터로 받는다.
  2. test_decorator 안에서, 호출할 함수를 감싸는 함수 wrapper 를 만든다.
  3. wrapper 함수 안에서, 파라미터로 받은 func 를 호출한다.
  4. test_decorator 안에서 정의한 wrapper 함수를 리턴한다. 리턴할 때는 () 반드시 생략해야 한다.
  5. 따라서 데코레이터의 형태는, 함수 안에서 함수를 만들고 반환하는 클로저이다.

@를 사용하여

'''
@데코레이터
def 함수이름():
    코드
'''

def test_decorator(func):
    def wrapper():
        print(func.__name__)
        func()
    return wrapper

@test_decorator
def test():
	print("run test func")

test()
# test
# run test func

데코레이터 여러 개 지정

  • 지정한 데코레이터는 위에서 부터 실행된다.
def test_deco1(func):
    def wrapper():
        print("test_deco1")
        func()
    return wrapper


def test_deco2(func):
    def wrapper():
        print("test_deco2")
        func()
    return wrapper


@test_deco1
@test_deco2
def test():
    print("run test()")

test()
  • 위에서 코드에서 test() 메서드가 실행되는 순서는 아래와 같다.
deco = test_deco1(test_deco2(test))
deco()

매개변수와 반환값을 처리하는 데코레이터

def trace(func):
    def wrapper(a, b):
        r = func(a, b)
        return r

    return wrapper

@trace
def add(a, b):
    return a + b

print(add(10, 20)) #  30
  • 데코레이터 기본 형태
  • 데코레이터에서 파라미터로 받는 함수의 파라미터 값을, 데코레이터 안에서 생성한 함수의 파라미터에 맞춰준다.

함수를 수정하지 않고, 데코레이터 안에서 추가 기능 구현할 때 1

def trace(func):
    def wrapper(a, b):
        r = func(a, b)
        return r * 10

    return wrapper

@trace
def add(a, b):
    return a + b

print(add(10, 20)) #  300
  • 위와 같이 사용하면, add 함수의 결과 값을 데코레이터에서 추가된 연산 값으로 바꿀 수 있다.

함수를 수정하지 않고, 데코레이터 안에서 추가 기능 구현할 때 2

def trace1(func):
    def wrapper(a, b):
        print("trace1")
        return func(a, b) + 12

    return wrapper

def trace2(func):
    def wrapper(a, b):
        print("trace2")
        return func(a, b) + 14

    return wrapper

@trace1
@trace2
def add(a, b):
    return a + b

print(add(10, 20)) 

# trace1
# trace2
# 56
  • 실행 순서는 지정한 데코레이터 순서이다.
  • 이 때 데코레이터 순서를 바꿔도 최종 결과 값은 같다.
@trace1
@trace2
def add(a, b):
    return a + b

print(add(10, 20)) 

# trace2
# trace1
# 56

가변인수를 받는 데코레이터

def trace(func):
    def wrapper(*args, **kwargs):
        pass

    return wrapper()
  • args는 튜플이고, kwargs는 딕셔너리이므로 func에 넣을 때는 언패킹하여 넣어준다.

매개변수가 있는 데코레이터

  • 매개변수가 있는 데코레이터를 만들 때는 함수를 하나 더 만들어야 한다.
def test_deco(x):
    def real_deco(func):
        def wrapper(a, b):
            print(x)
            func(a, b)
        return wrapper
    return real_deco

@test_deco("test_deco_parameter")
def test(a, b):
    print(a, b)

test(1, 2)
# test_deco_parameter
# 1, 2

원래 함수 이름이 안나올 때

데코레이터를 여러 개 사용하면, 데코레이터에서 반환된 wrapper 함수가 다른 데코레이터로 들어간다.
따라서 함수의 __name__ 을 출력해보면 wrapper 가 나온다.
이 때는 fuctools 모듈의 wraps 데코레이터를 사용해야 한다.

import functools
def test_deco(x):
    def real_decorator(func):
        @functools.wraps(func)
        def wrapper(a, b):
            print(func.__name__)
            print("x: " + str(x))
            return a + b
        return wrapper
    return real_decorator

@test_deco(1)
@test_deco(2)
def add(a, b):
    return a + b

print(add(10, 20))
  • @functools.wraps는 원래 함수의 정보를 유지시켜준다.

클래스로 데코레이터 만들기

class Trace:
    def __init__(self, func):  # 호출할 함수를 인스턴스의 초깃값으로 받음
        self.func = func  # 호출할 함수를 속성 func에 저장

    def __call__(self):
        print(self.func.__name__, '함수 시작')  # __name__으로 함수 이름 출력
        self.func()  # 속성 func에 저장된 함수를 호출
        print(self.func.__name__, '함수 끝')

@Trace  # @데코레이터
def hello():
    print('hello')

hello()  # 함수를 그대로 호출
    1. __init, __call 함수를 적절히 오버라이드 시켜줘야한다.
    1. __init__ 에서 호출할 함수를 파라미터로 지정한다.
    1. __call__ 에서 호출할 함수를 실행시켜준다.

클래스로 매개변수와 반환값을 처리하는 데코레이터

class TestDeco:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        r = self.func(*args)
        print(r)
        print(self.func.__name__)
        return r

@TestDeco
def test(a, b):
    return a + b

test(1, 2)

클래스로 매개변수가 있는 데코레이터 만들기

class TestDeco:
    def __init__(self, x):
        self.x = x

    def __call__(self, func):
        def wrapper(a, b):
            print(a, b)
            r = func(a, b)
            print(r)
            return r + self.x
        return wrapper

@TestDeco(3)
def test(a, b):
    return a + b

print(test(1, 2))
# 1, 2
# 3
# 6
    1. 이와 같은 형태는 __init__ 에서 데코레이터가 사용할 매개변수를 받는다.
    1. __call__ 메서드에서 호출할 함수를 매개변수로 받는다.

개념 정리

  • 데코레이터는 기본 함수를 수정하지 않으면서 추가 기능을 구현할 때 사용한다.
  • 디버깅, 함수의 성능 측정, 함수 실행 전에 데이터 확인 등에 자주 활용된다.

참고자료: 코딩도장: 데코레이터

profile
개발을 취미로 할 수 있는 그 때 까지

0개의 댓글