[Python] Decorator

최창현·2022년 1월 3일
0

Closure와 Decorator를 이해하기 전에 Nested Function(중첩함수)에 대한 이해가 필요하다.

def greeting():
	def hello():
    	print("Hello!")
    hello()

>>>greeting()
Hello!

위의 코드를 살펴보면 greeting이라는 함수 안에서 hello라는 함수를 호출 했기 때문에
gretting 함수만 Call을 해도 'Hello!'라는 문구가 정상적으로 출력 된다.

또한, 중첩함수(hello)는 부모함수(greeting) 안 에서만 사용할 수 있다.
만약 greeting 함수 밖에서 hello를 사용했다가는 가차 없이 에러가 뜰 것이다.

그렇다면 중첩 함수를 쓰는 이유는 무엇일까?
크게 두 가지의 이유가 있다.

  1. 가독성
  2. Closure

부모함수의 변수나 정보를 가두는것을 'closure'라고 한다.
Closure에는 세 가지 조건이 있다.

  1. Nested 구조를 갖춰야 한다.(중첩이 되어야 한다.)
  2. 중첩함수가 부모함수의 변수나 정보를 중첩함수 내에서 사용해야 한다.
  3. 부모함수는 return 값으로 중첩함수를 return 해야 한다.

이러한 조건을 보았을 때, Closure는 어떤 정보를 기반으로 연산을 실행하고 싶으나 그 정보의
접근을 제한하여 노출이나 수정을 막고자 할 때 사용!

Closure는 주로 factory 패턴을 구현할 때 사용한다. factory는 무엇인가를 생성해내는 패턴이다. 주로 함수나 Object를 생성하는데 사용한다. factory에서 뭔가를 생성하기 위해서는 설정값이 필요하다. 결국, 설정값을 노출하지 않아서 수정이 불가능하게(closure의 개념)
하면서 해당 설정 값을 기반으로 한 연산을 할 수 있는 함수를 만들 때, closure를 사용할 수 있다.

다음과 같은 예제코드가 있다

def generate_power(base_number): 
	def nth_power(power):
    	return base_number ** power
    return nth_power
#closure 생성 및 출력
>>calculate_power_of_two = generate_power(2)
>>calculate_power_of_two(7)
128

위의 함수는 승(power) 또는 지수라고 하는 값을 구하는 함수이다.
생성 및 출력 부분을 보면, generate_power에 arguments 2를 넣은 값(즉 함수)을
calculate_power_of_two 라는 변수에 대입합니다.


Decorator

Decorator의 역할은 '함수를 인자로 받아 새로운 함수를 만들어 변환하는 함수' 이다.
함수 실행 전 특정동작을 하게 하는걸 간단하게 할 수 있게 만드는 것이라고 할 수 있다.

아래의 예제 코드를 살펴보자

import datetime
def delivery_ok():
	print(datetime.datetime.now())
    print("배송완료")
#결과
>>delivery_ok()
2022-01-03 11:45:39.811267
배송완료

이런 함수가 여러개이거나 delivery_ok 라는 함수에 기능이 많을 수록 가독성은 떨어지고,
에러 찾기도 어려워진다.
이런 이유 때문에 조금 전 설명한 Decorator를 사용한다.

우선 중첩함수(function)을 만들어야 한다.

def decorator(func):
    def wrapper():
        print(datetime.datetime.now())
        return func()
    return wrapper
def delivery_ok():
    print("배숑완료")
delivery_ok = decorator(delivery_ok)
delivery_ok()

위와 같이 decorator의 뼈대 기능이 구현 되었다.
12번째 Line에서 Closure와 같이 decorator 함수가 실행된다.
decorator라는 함수는 함수를 인자로 받기 때문이다.
결과값은 다음과 같다.

2022-01-03 12:03:27.072163
배숑완료


*args, **kwargs 받기
이제 추가기능으로 배송지까지 나타내 보자.

import datetime
def decorator(func):
    def wrapper(*args, **kwargs):
        print(datetime.datetime.now())
        return func(**kwargs)
    return wrapper
def delivery_ok(**kwargs):
    print("배송완료")
    if 'where' in kwargs:
        print(f"배송지는 {kwargs['where']} 입니다.")
delivery_ok = decorator(delivery_ok)
delivery_ok(where='대전')     

decorator 함수도 이에 맞게 수정 해야 한다.
우선 함수를 받는다는건 동일하다.
wrapper는 Line15에서 함수가 실행될 때, 형태가 wrapper(where="송파") 와 같다.
따라서 wrapper 함수는 where라는 kwargs를 받고, 시간을 출력하고,
최종적으로 delivery_ok를 실행한다.

결과값은 다음과 같다.

2022-01-03 13:28:54.894273
배송완료
배송지는 대전 입니다.


@사용하기

본함수 = decorator_function(본함수)형태를 한 문장으로 표현하는 방식이 있다.

위의 코드 중 delivery_ok = decorator(delivery_ok)가
@decorator로 치환 가능하다.


decorator 함수에 변수넣기

바로 위에서 사용했던 코드에서 일반/등기의 구분이 너무 중요해서 함수에 넣는다고 가정한다.

import datetime
def type_decorator(type):
    def decorator(func):
        def wrapper(*args, **kwargs):
            whereis = func(**kwargs) + "인"
            print(datetime.datetime.now())
            print(f"{whereis} {type} 택배입니다.")
        return wrapper
    return decorator
@type_decorator("등기")
def delivery_ok(**kwargs):
    print("배송완료")
    if 'where' in kwargs:
        return "배송지는" + kwargs['where']
delivery_ok(where='송파' ,company="한진")    

최종 출력

배송완료
2022-01-03 13:52:28.478306
배송지는송파인 등기 택배입니다.

profile
chch_oi

0개의 댓글