데코레이터(decorator) - 장식해주는 역할을 한다.
- @staticmethod,@classmethod 등 에사용하는 @를 의미함
- 함수를 장식하는 용도
- 함수를 변경하지않고 그 함수에 기능을 추가하고 싶거나 디버깅할때 사용된다.
데코레이터를 만들때 자주 사용하는 이름
- trace : 추적자를 의미함 데코리에터 함수이름으로 자주사용한다.
- wrapper : 감싸는 무언가를 의미하는데 데코레이터 함수 내부에 작동하는 함수의 이름으로 사용
데코레이터 함수 만들기
예제
def hello():
print('hello 함수 시작')
print('hello')
print('hello 함수 끝')
def world():
print('world 함수 시작')
print('world')
print('world 함수 끝')
hello()
world()
결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝
함수의 시작과 끝을 알려주는 문자열을 프린트하려고 함수 내부에 작성하였다.
새함수를 만들때마다 계속 시작과 끝을 알리는 문자열을 사용해야하는데 이럴때 데코레이터를 사용하면 좋다.
데코레이터 만들기
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(): # 호출할 함수를 감싸는 함수
print(func.__name__, '함수 시작') # __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() # 반환된 함수를 호출
결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝
해석해보기
def trace(func):
def wrapper():
print(func.__name__, '함수 시작')
func()
print(func.__name__, '함수 끝')
return wrapper
- def trace(func) : 이 함수의 매개변수는 함수가 된다 괄호안에 함수를 넣는것이다.
- func.__name__ : 함수의 이름을 출력하는 메서드이다.
- func() : 함수를 호출 이경우 아래에는 print('hello')와 같이 print()역할을 해놨음으로 그냥 함수이름을 출력한다고 보면된다.
- return wrapper : trace 함수안에 여러가지 기능이 구현되어있지만 만들기만했을뿐 그대로 놓으면 아무 일도 일어나지 않는다.
- return으로 함수를 반환하여 기능을 사용하는건데 간단히 말하면 trace함수를 실행시키면
- wrapper 도 실행된다고 보면된다.
사용하기
trace_hello = trace(hello) # 데코레이터에 호출할 함수를 넣음
trace_hello() # 반환된 함수를 호출
trace_world = trace(world) # 데코레이터에 호출할 함수를 넣음
trace_world()
함수를 변수에 할당하고 즉시 실행 시켰다.
- hello()함수는 print('hello')즉 wrapper내부에 func()의 기능으로써 작동할 수 있고
- wrapper()내부에 func이란 trace의 매개변수인 hello()가 된다
결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝
@로 데코레이터 사용하기
이전까지 데코레이터 함수를 만들고 함수내에 매개변수를 함수를 넣어서 사용하였다.
같은 방식이지만 @데코레이터 로 더 깔끔 하게 사용해보자
@데코레이터
def 함수이름():
코드
**위에 만들어놓은 코드 trace를 사용
@trace
def hello():
print('hello')
@trace
def world():
print('world')
hello()
world()
데코레이터 동작 방식
데코레이터를 여러개 지정하기
@데코레이터1
@데코레이터2
def 함수이름():
코드
def decorator1(func):
def wrapper():
print('decorator1')
func()
return wrapper
def decorator2(func):
def wrapper():
print('decorator2')
func()
return wrapper
# 데코레이터를 여러 개 지정
@decorator1
@decorator2
def hello():
print('hello')
hello()
결과
decorator1
decorator2
hello
분명 각 데코레이터에 func()이 하나씩 존재하는데 print('hello')는 한번만 출력되었다 왜그럴까?
@을 사용하지 않을 때의 코드 동작 알아보기
decorated_hello = decorator1(decorator2(hello))
decorated_hello()
이러면 이해가 조금편하다
def decorator1(func):
def wrapper():
print('decorator1')
func()
return wrapper
decorator1(func)에서 매개변수에 해당하는 코드는 decorator2이기때문이다 결국 풀어서 본다면
def decorator1(decorator2):
def wrapper():
print('decorator1')
decorator2()
return wrapper
이렇게 보면 된다.
def decorator1(func):
def wrapper():
print('decorator1')
func()
return wrapper
매개변수와 반환값을 처리하는 데코레이터 만들기
만들기
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))
결과
add(a=10, b=20) -> 30
30
흐름을 알아보자 return r을 왜 해야할까?
print(add(10,20))의 결과는 왜 위와같이 두줄일까??데코레이터때문이다.
그렇다면 데코레이터를 작성했다면 add(10,20)함수는 우리가 작성한 return a + b가 아니게 된다.
데코레이터의 작동의 매개변수와 함수로써 작동한다.
그렇기에 데코레이터 내부에서도 func(a,b)로써 값을 한번 더 반환해야한다.
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 wrapper
데코레이터를 이렇게 만들고 출력하면 결과는 아래와같이 나온다
add(a=10, b=20) -> 30
None
add 함수에 a+b가 있더라도 데코레이터 자체적으로 add함수를 반환하지 않았기에 이런 결과가 나왔다.
가변인수 함수 데코레이터 만들기
만들기
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())
print(get_max(10, 20))
print(get_min(x=10, y=20, z=30))
결과
get_max(args=(10, 20), kwargs={}) -> 20
20
get_min(args=(), kwargs={'x': 10, 'y': 20, 'z': 30}) -> 10
10
해석해보기
- get_max는 튜플(리스트도가능)이고 get_min은 딕셔너리기에 각각 언패킹을 해줬다.
- 데코레이터에서 받는함수인 wrapper()함수 내에 두종류 다 대입
- func()함수 역시 두종류 다 대입하였다.
- 이렇게 하면 리스트,튜플,딕셔너리 모든 가변인수,위치인수를 받을수 있다.
- 왜 컴마로 구분해서 두종류의 가변인수를 선택적으로 받을 수 있을까?(자문)
메서드에 데코레이터 사용하기
만들기
def trace(func):
def wrapper(self, a, b):
r = func(self, a, b)
print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r))
return r
return wrapper
class Calc:
@trace
def add(self, a, b):
return a + b
c = Calc()
print(c.add(10, 20))
결과
add(a=10, b=20) -> 30
30
해석하기
사실 함수로 만드는것과 메서드로 만드는것에 대해선 단하나의 차이를 제외하곤 별것 없다.
그 단 하나의 차이는 메서드의 가장 첫 매개변수는 항상 self라는것만 명심하면 된다.
'Python > 기초문법' 카테고리의 다른 글
(36) 정규표현식 (접근시 피곤함) (0) | 2022.05.07 |
---|---|
(35)데코레이터 사용하기 - 2 (0) | 2022.05.05 |
(33)코루틴(coroutine) 사용하기 (0) | 2022.05.03 |
(32)제너레이터 & yield (0) | 2022.04.25 |
(31)iterator & iterable 반복가능객체 (0) | 2022.04.24 |