TIL#60 Decorator

dnpxm387·2020년 9월 10일
0

python

목록 보기
35/46
post-thumbnail

데코레이터에 대해서 잠깐 공부했었지만 너무 어려웠고 다시 한번 제대로 짚고 넘어가고자 데코레이터 공부를 시작했다😅

Decorator

Decorate 는 장식하다, 꾸미다라는 뜻. 함수를 장식한다고 해서 이런 이름이 붙었다고 한다. @로 시작한다.

  • 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용

함수의 시작과 끝을 출력하는 데코레이터 만들기

def trace(func):   # 호출할 함수를 매개변수로 받음
    def wrapper():   # 호출할 함수를 감싸는 함수
        print(func.__name__, '함수 시작')
        func()
        print(func.__name__, '함수 끝')
    return wrapper   # wrapper 함수 반환
   
def hello():
    print('hello')
    
def world():
    print('world')
    
trace_hello = trace(hello)
trace_hello()
trace_world = trace(world)
trace_world()

이렇게 만들게 되면 매번 함수를 호출하고 선언해야 한다. 이를 더 간편하게 만들어 주는 게 데코레이터다.

def trace(func):
    def wrapper():
        print(func.__name__, '함수 시작')
        func()
        print(func.__name__, '함수 끝')
    return wrapper

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

@trace
def world():
    print('world')

hello()
world()

이렇게 함수이름에 @ 만 붙인상태로 데코 함수에 매개변수로 쓰려는 함수 위에 써주면 된다.

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

def trace(func):
    def wrapper(a, b):
        r = func(a, b)
        print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r))
        return r
    return wrapper

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

print(add(10, 20))

매개변수와 반환값을 처리하는 데코레이터를 만들 때는 안쪽 wrapper 함수의 매개변수를 호출할 함수의 매개변수와 똑같이 만들어준다. wrapper 함수에서 func의 반환값을 반환하지 않으면 add함수를 호출해도 반환값이 나오지 않는다.

가변 인수 함수 데코레이터 만들기

매개변수(인수)가 고정되지 않은 함수인데 이때는 wrapper 함수를 가변 인수 함수로 만들면 된다.

def trace(func):
    def wrapper(*args, **kwargs):
        r = func(*args, **kwargs)
        print('{0}(args={1}, kwargs={2} -> {3}'.format(func.__name__, args, kwargs, r))

        return r
    return wrapper

@trace
def get_max(*args):
    return max(args)
@trace
def get_min(**kwargs):
    return min(kwargs.values())

get_max 함수와 get_min 함수는 가변 인수 함수이기 때문에 데코레이터도 가변 인수 함수로 만들어준다. 위치 인수(args) 와 키워드 함수(kwargs)를 모두 받을 수 있도록 *args**kwargs 를 지정해준다. 가변 인수 함수 데코레이터는 가변 인수 함수뿐만 아니라 일반적인 함수에도 사용할 수 있다.

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

매개변수가 있는 데코레이터를 만들 때는 함수를 하나 더 만들어야 한다.

def is_multiple(x):
    def real_decorator(func):
        def wrapper(a, b):
            r = func(a, b)
            if r % x == 0:
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x))
            return r
        return wrapper
    return real_decorator

@is_multiple(3)
def add(a, b):
    return a + b

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

클래스를 활용할 때는 인스턴스를 함수처럼 호출하게 해주는 __call__ 메소드를 구현해야 한다.

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

    def __call__(self):
        print(self.func.__name__, '함수 시작')
        self.func()
        print(self.func.__name__, '함수 끝')

@Trace
def hello():
    print('hello')

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

클래스로 매개변수와 반환값을 처리하는 데코레이터를 만들 때는 __call__ 메소드에 매개변수를 지정하고 self.func 에 매개변수를 넣어서 호출한 뒤에 반환값을 반환해주면 된다.

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

    def __call__(self, *args, **kwargs):
        r = self.func(*args, **kwargs)
        print('{0}(args={1}, kwargs={2} -> {3}'.format(self.func.__name__, args, kwargs, r))

        return r

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

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

__init__ 에서 데코레이터가 사용할 매개변수를 받는다는 점이 다르다. 주의!
__call__ 메소드에 호출할 함수를 매개변수로 받는다. __call__ 메소드 안에서 wrapper 함수를 만들어준다.

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

    def __call__(self, func):
        def wrapper(a, b):
            r = func(a, b)
            if r % self.x == 0:
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, self.x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, self.x))
            return r
        return wrapper

@IsMultiple(3)
def add(a, b):
    return a + b

데코레이터참고영상-파이썬코딩도장

Method Decorator

클래스의 method 에도 데코레이터가 적용 가능하다. 클래스 method 는 첫 파라미터가 self 이므로 이부분을 데코레이터 작성시 포함시켜야 한다.

def tag_h1(func):
    def method_wrapper(self, *args, **kwargs):
        return '<h1>{0}</h1>'.format(func(self, *args, **kwargs))
    return method_wrapper

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
    
    @tag_h1
    def get_name(self):
        return self.first_name + ' ' + self.last_name

chulsookim = Person('Chulsoo', 'Kim')
print(chulsookim.get_name())
profile
개발자꿈나무🌲

0개의 댓글