Decorator를 사용하기 위해 공부했던 내용을 정리해두고자 한다. Decorator를 사용하기 위해 이해해야 할 개념은 다음과 같다.
- 일급함수(first-class function)
- 클로저(Closure)
- 데코레이터(Decorator)
1. 일급 함수
(1) 일급 함수의 개념
일급 함수는 컴퓨터 프로그래밍 언어 디자인에서 일급 객체(first-class object)의 함수 버전 개념이다. 위키피디아에 따르면 일급 객체는 '일반적으로 적용 가능한 연산을 모두 지원하는 객채'를 의미하며, 객체에 대한 구체적인 정의는 다음과 같다.
- 모든 요소는 함수의 실제 매개변수가 될 수 있다.
- 모든 요소는 함수의 반환 값이 될 수 있다.
- 모든 요소는 할당 명령문(assignments statements)의 대상(subject)가 될 수 있따.
- 모든 요소는 동일 비교의 대상이 될 수 있다.
일급 객체들의 예시는 다음과 같다.
위 표에서 확인할 수 있는 것 처럼 파이썬은 일급 함수를 지원한다.
(2) 일급 함수 예제
def square(x):
return x**2
f = square #1
print(square(5))
print(square) #2
print(f) #3
>>>> 25
>>>> <function square at 0x7faf2a31e510>
>>>> <function square at 0x7faf2a31e510>
#1,#2,#3에서 볼 수 있듯 함수를 정의하여 변수에 할당할 수 있다. 이때, 함수 call 결과가 아닌 함수 오브젝트를 할당하는 것이다.
def square(x):
return x**2
def cube(x):
return x**3
functions = [square, cube] #1
values = [1,2,3]
for function in functions:
print(list(map(function, values)))
함수 객체를 리스트에 할당하여 다른 함수의 argument로 넣어주는 것도 가능하다.
3) 일급 객체가 아닌 것(!)
구세대 언어에서 array, string은 일급 객체로 취급받지 못했다. 예를 들어 C에서 array를 assign하거나 인자로 넘겨줄 때, 첫번째 요소의 위치만 반환되고 크기 정보는 전달되지 않는다.
또한 대부분의 언어에서 data type은 일급 객체로 취급받지 않는다. 즉, data type으로 위에서 정의한 연산들을 수행하지 못한다.
2. 클로저(Closure)
(1) 클로저 정의
위키피디아에서 closure의 정의는 다음과 같다.
In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions. Operationally, a closure is a record storing a function together with an environment. The environment is a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope) with the value or reference to which the name was bound when the closure was created. Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.
핵심은 어떤 environment(variable)에 binding 되어있는 함수라는 것이다. 여기서의 environment는 free variable등을 의미한다. free variable은 local-variable에 포함되어 있지 않은 변수를 뜻하며, 보다 자세한 설명과 예시는 이상화님의 글에서 확인할 수 있다.
참고로 클로저는 람다 표현식($\lambda-calculus$)에서 기계적인 평가를 위해 1960년대에 개발된 개념이라고 한다.
the term closure to refer to a lambda expression whose open bindings (free variables) have been closed by (or bound in) the lexical environment, resulting in a closed expression, or closure.
(2) 클로저 예제
위키피디아 예시를 보자.
def f(x):
def g(y):
return x + y
return g # Return a closure.
def h(x):
return lambda y: x + y # Return a closure.
# Assigning specific closures to variables.
func1 = f(1)
func2 = h(1)
# Using the closures stored in variables.
assert func1(5) == 6
assert func2(5) == 6
# Using closures without binding them to variables first.
assert f(1)(5) == 6 # f(1) is the closure.
assert h(1)(5) == 6 # h(1) is the closure.
func1,func2는 모두 클로저인데, 그 이유는 두 경우 모두 nested function과 (wrapper function으로부터 받은) free variable이 binding 되어 있기 때문이다. 즉, func1의 $g(y)$, func2의 $lambda$ 모두 x=1이 부여되어 있다. 두 함수 모두 동일한 함수이며, 다만 하나는 nested function으로, 하나는 lamba로 정의되어 있을 뿐이다.
함수와 free variable가 binding 되어있다고 하는데 이를 직접 확인해볼 수 있다.
print(dir(func1), '\n')
print(dir(func1.__closure__[0]), '\n')
print(func1.__closure__[0].cell_contents, '\n')
>>>> ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>>> ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>>> 1 # wrapper function에서 정의된 x=1이 바인딩되어 있다.
3) 클로저가 아닌 것(!)
함수와 변수가 같은 환경에서 정의되고 실행된다면 클로져라 할 수 없다. 아래 예시처럼 free variable x가 정의된 곳이랑 실행되는 곳이랑 같은 경우에는 클로져인 함수가 아니다.
x = 1
nums = [1, 2, 3]
def f(y):
return x + y
map(f, nums)
map(lambda y: x + y, nums)
3. 데코레이터(Decorator)
(1) 데코레이터 정의
데코레이터의 위키피디아 정의는 다음과 같다.
A decorator is any callable Python object that is used to modify a function, method or class definition. A decorator is passed the original object being defined and returns a modified object, which is then bound to the name in the definition.
데코레이터는 다음과 같은 상황 등에서 많이 쓰인다.
- logging
- authentication, access control
- timing functions
- rate-limiting
(2) 데코레이터 예제
데코레이터는 함수를 인자로 받고, 해당 함수에 기능을 추가하여 리턴해준다. 다음과 같이 환영인사를 해주는 welcome_message라는 wrapping function이 있다고 할 때,
def welcome_message(myfunc):
def inner_func(*args, **kwargs):
print('welcome to our restaurant')
myfunc()
return inner_func
아래 두 함수는 decorate된 menu_item 함수를 반환해준다.
# 1
@welcome_message
def menu_item():
print("steak")
# 2
def menu_item():
print("steak")
menu_item = welcome_message(menu_item)
이상화님의 글에도 데코레이터를 이해하기 위한 좋은 예시가 있다. 먼저 데코레이터를 사용하지 않고 wrapping만 하여 사용하는 예시이다.
### just wrapping
def decorator_function(original_function): #1
def wrapper_function(): #5
print('{} 함수가 호출되기전 입니다.'.format(original_function.__name__)) #6
return original_function() #7
return wrapper_function
def display(): #2
print('display 함수가 실행됐습니다.') #8
decorated_display = decorator_function(display) #3
decorated_display() #4
>>>> 'display 함수가 호출되기전 입니다.'
>>>> 'display 함수가 실행됐습니다.'
먼저 데코레이터를 사용하지 않을 때 실행 순서는 다음과 같다.
- #1, #2: decorator function, display function 정의
- #3: display function을 인자로 갖는 decorator_function을 실행한 리턴 값인 wrapper_function을 변수에 할당
- #4, #5: decorated_display 실행되면 wrapper_function가 call됨
- #6: wrapper function에 의해 추가된 기능이 실행됨
- #7, #8: original function인 display()가 실행됨
이제 데코레이터로 만들어서 실행시켜 보자. 데코레이터 함수 정의와 사용법은 아래와 같이 틀이 정해져있다.
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
print('{} 함수가 호출되기전 입니다.'.format(original_function.__name__))
return original_function()
return wrapper_function
@decorator_function
def display():
print('display 함수가 실행됐습니다.')
display()
>>>> 'display 함수가 호출되기전 입니다.'
>>>> 'display 함수가 실행됐습니다.'
decorator_function으로 display()를 감싸게 되면 wrapper_function에 의해 추가된 기능이 실행되고, 그 다음에 display()가 실행이 된다.
[references]
'python 메모' 카테고리의 다른 글
[pandas] apply + custom function을 사용한 다중 입력 및 출력 (0) | 2021.06.10 |
---|---|
[python] datetime 사용하기 (0) | 2021.06.06 |
[matplotlib] subplot 그리기 (0) | 2021.05.19 |
[python] regex 메모 (0) | 2021.05.07 |
[python] 봐두면 유용할 수도 있는 문자열 built-in functions (0) | 2021.05.06 |