Python 라이브러리 공부 기록 06. 클로저, 데코레이터, 이터레이터, 제너레이터
카테고리: Python
06. 클로저, 데코레이터, 이터레이터, 제너레이터
01. 클로저와 데코레이터
어떤 수에 항상 3을 곱해서 리턴하는 함수, 항상 5를 곱해서 리턴하는 함수를 만들려면 일일이 함수를 만들기보다 아래처럼 클래스를 형성하는 것이 효율적이다!
class Mul:
def __init__(self, m):
self.m = m
def mul(self, n):
return self.m * n
if __name__ == "__main__":
mul3 = Mul(3)
mul5 = Mul(5)
print(mul3.mul(10)) # 30 출력
print(mul5.mul(10)) # 50 출력
하지만 클로저를 활용하면 더 간편하게 동일한 함수를 만들 수 있다.
클로저는 간단히 말해 함수 내에 내부 함수(inner function)를 구현하고 그 내부 함수를 리턴하는 함수를 말한다.
def mul(m):
def wrapper(n):
return m * n
return wrapper
if __name__ == "__main__":
mul3 = mul(3) #3은 m에 저장하는 인수
mul5 = mul(5) #5는 m에 저장하는 인수
print(mul3(10)) #10은 n
print(mul5(10)) #10은 n
#결과
30
50
여기서 mul()
이 외부함수, wrapper()
이 내부함수이다. mul()
함수에서 호출한 m은 wrapper()
함수에 그대로 내장되어 활용된다.
import time
def elapsed(original_func):
def wrapper():
start = time.time()
result = original_func()
end = time.time()
print("함수 수행시간: %f 초"%(end-start))
return result
return wrapper
def myfunc():
print("함수가 실행됩니다.")
decorated_myfunc = elapsed(myfunc)
decorated_myfunc()
#결과
함수가 실행됩니다.
함수 수행시간: 0.000120 초
위 코드는 elapsed
함수로 만든 클로저이다. 외부함수인 elapsed()
는 함수를 인수로 받는다. 내부함수인 wrapper()
는 함수의 수행시간을 알려주면서 외부함수의 인수 함수도 리턴해준다.
클로저를 이용하면 기존 함수에 뭔가 추가적인 부가 기능을 덧붙이기가 아주 편리하다. 이렇게 기존 함수의 변경 없이 추가적인 기능을 덧붙일 수 있도록 해 주는 elapsed
함수와 같은 클로저를 데코레이터(Decorator)라고 한다.
아래 코드는 위 코드에서 @elapsed
라는 어노테이션을 추가한 것이다. 이처럼 함수명 바로 위에 어노테이션을 넣으면 이를 데코레이터 함수로 인식한다. 따라서 myfunc()
함수는 elapsed()
함수의 호출 없이도 데코레이터로 기능한다.
import time
def elapsed(original_func):
def wrapper():
start = time.time()
result = original_func()
end = time.time()
print("함수 수행시간: %f 초"%(end-start))
return result
return wrapper
@elapsed
def myfunc():
print("함수가 실행됩니다.")
myfunc()
#결과
함수가 실행됩니다.
함수 수행시간: 0.000120 초
만약 myfunc()
에 인수를 입력하고 싶다면 아래와 같이 *args
, **kwargs
를 사용해야 한다. *args
는 모든 입력 인수를 튜플로 변환해 주는 매개변수이고 **kwargs
는 모든 key=value 형태의 입력 인수를 딕셔너리로 변환해 주는 매개변수이다.
import time
def elapsed(original_func):
def wrapper(*args, **kwargs):
start = time.time()
result = original_func(*args, **kwargs)
end = time.time()
print("함수 수행시간: %f 초"%(end-start))
return result
return wrapper
@elapsed
def myfunc(text):
print("'%s'을 출력합니다."%text)
myfunc("I love you")
#결과
'I love you'을 출력합니다.
함수 수행시간: 0.000135 초
02. 이터레이터와 제너레이터
반복구문으로 돌릴 수 있는 리스트와 같은 객체를 iterable
이라 한다.
이터레이터(iterator)란 next함수 호출 시 그 다음 값을 리턴해주는 객체이다.
리스트는 iterable하지만, 이터레이터는 아니다. 따라서 리스트를 이터레이터로 만들려면 iter()
함수로 감싸야 한다.
a = [1,2,3]
ia = iter(a)
print(next(ia))
print(next(ia))
print(next(ia))
#결과
1
2
3
이터레이터는 한번 값을 읽어오면 다시는 그 값을 읽어올 수 없다는 특징이 있다. 따라서 이터레이터에 대해 for 반복문을 한 번 작동시킨 후에 다시금 반복문을 작동하면 값을 읽어올 수 없다.
보통 함수는 하나의 값을 리턴한다. 그 값은 정수, 리스트, 딕셔너리등이 될 수 있을 것이다. 그런데 만약 함수가 하나의 값을 리턴하는 것이 아니라 연속된 값을 순차적으로 리턴할 수 있다면 어떨까?
이러한 개념에서 만들어진 것이 바로 제너레이터(generator)이다.
def mygen():
yield 'a'
yield 'b'
yield 'c'
g = mygen()
print(g) #'a'
print(g) #'b'
print(g) #'c'
print(g) #오류
yield
구문은 여러 값을 리턴해주는 기능을 하고, yield
가 포함된 함수를 제너레이터라고 한다.
이터레이터처럼 순서대로 출력이되고, 한번 읽어온 요소는 다시 불러올 수가 없어서 오류가 발생한다.