리마인드 !
데코레이터
===========================================================
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()
===========================================================
elapsed 함수로 클로저를 만들었다. elapsed 함수는 함수를 인수로 받는다. 파이썬은 함수도 객체이기 때문에 함수 자체를 인수로 전달하는 것이 가능하다. 이제 decorated_myfunc = elapsed(myfunc)로 생성된 decorated_myfunc를 decorated_myfunc() 처럼 실행하면 실제로는 elapsed 내부의 wrapper 함수가 실행되고 wrapper 함수는 전달 받은 myfunc 함수를 수행하고 수행시간도 함께 출력할 것이다. 클로저를 이용하면 기존 함수에 뭔가 추가적인 부가 기능을 덧붙이기가 아주 편리하다. 이렇게 기존 함수의 변경 없이 추가적인 기능을 덧붙일 수 있도록 해 주는 elapsed 함수와 같은 클로저를 데코레이터(Decorator)라고 한다.
이것이 가능한 이유는 함수도 객체이기 때문이다.
파이썬 데코레이터는 @를 이용한 어노테이션(annotation=주석)도 사용할수도 있다.
===========================================================
@elapsed
def myfunc():
print("함수가 실행됩니다.")
# decorated_myfunc = elapsed(myfunc) # @elapsed 어노테이션으로 인해 더이상 필요하지 않다.
# decorated_myfunc()
myfunc() # 바로 이렇게 실행가능
===========================================================
지금은 myfunc()함수가 입력인수를 필요로하지 않아서 아무런 문제가 없다. 그러나 myfunc 함수를 문자열을 입력받아 출력하도록 수정하고 실행하면 오류가 발생한다.
오류의 원인은 myfunc 함수는 입력인수를 필요로 하지만 elapsed 함수 내의 wrapper 함수는 전달받은 myfunc 함수를 입력인수 없이 호출하기 때문이다.
데코레이터 함수는 기존 함수의 입력 인수에 상관없이 동작하도록 만들어야 한다. 왜냐하면 데코레이터는 기존함수가 어떤 입력 인수를 취할지 알 수 없기 때문이다. 따라서 이렇게 전달받아야 하는 기존 함수의 입력 인수를 알 수 없는 경우에는 *args와 **kwargs 기법을 이용하여 해결해야 한다.
*args는 모든 입력 인수를 튜플로 변환해 주는 매개변수이고 **kwargs는 모든 key=value 형태의 입력 인수를 딕셔너리로 변환해 주는 매개변수이다.
===========================================================
def func(*args, **kwargs):
print(args)
print(kwargs)
func(1, 2, 3, name='foo', age=3) # 의 결과는 다음과 같다.
(1, 2, 3)
{'age': 3, 'name': 'foo'}
===========================================================
사용은 다음과 같다
===========================================================
import time
def elapsed(original_func): # 기존 합수를 인수로 받는다.
def wrapper(*args, **kwargs): # *args, **kwargs 매개변수 추가
start = time.time()
result = original_func(*args, **kwargs) # 전달받은 *args, **kwargs를 입력파라미터로 기존함수 수행
end = time.time()
print("함수 수행시간: %f 초" % (end - start)) # 수행시간을 출력한다.
return result # 함수의 결과를 리턴한다.
return wrapper
@elapsed
def myfunc(msg):
""" 데코레이터 확인 함수 """
print("'%s'을 출력합니다." % msg)
myfunc("You need python")
===========================================================
이터레이터
이터레이터는 next 함수 호출 시 계속 그 다음 값을 리턴해 주는 객체이다. 그리고 더 편하게 iter()함수를 쓰면 이터레이터로 만들수 있다. 호출 시
for문을 이용하면 next함수를 따로 호출할 필요도 없고(for문이 자동으로 호출해 준다) StopIteration 예외에 신경 쓸 필요도 없다.
또한 이터레이터는 for문을 이용하여 반복하고 난 후에는 다시 반복하더라도 더 이상 그 값을 다시 가져오지 못한다. 이터레이터는 next로 그 값을 읽어 들이면 다시 그 값을 읽을 수 없는 특징이 있다.
===========================================================
>>> a = [1, 2, 3]
>>> ia = iter(a)
>>> for i in ia:
... print(i)
...
1
2
3
>>> for i in ia:
... print(i)
===========================================================
iter함수를 이용하지 말고 직접 이터레이터를 만드는 방법도 있다.
이터레이터는 클래스에 __iter__ 와 __next__ 라는 두개의 메서드를 구현하면 만들 수 있다.
하지만, 필요성을 느낄 때 다시와서 작성해보겠다. 아직까진 직접 구현해야할 상황의 필요성이 느껴지지 않는다.
제너레이터
보통 함수는 하나의 값을 리턴한다. 그 값은 정수, 리스트, 딕셔너리등이 될 수 있을 것이다. 그런데 만약 함수가 하나의 값을 리턴하는 것이 아니라 연속된 값을 순차적으로 리턴할 수 있다면 어떨까?
이러한 개념에서 만들어진 것이 바로 제너레이터(generator)이다.
제너레이터는 이터레이터와 마찬가지로 next 호출시 그 값을 순차적으로 얻어 낼 수 있다. 제너레이터는 순차적인 결과값을 리턴하기 위해 return 대신 yield 키워드를 사용한다.
간단한 제너레이터는 다음과 같다.
===========================================================
def mygen():
for i in range(1, 1000):
result = i * i
yield result
gen = mygen()
print(next(gen))
print(next(gen))
print(next(gen))
=
1
4
9
===========================================================
특징으로 제너레이터 객체 g에 next함수를 실행하면 mygen 함수의 첫번째 yield 문에 의해서 'a' 값이 리턴된다. 여기서 재밌는 점은 제너레이터는 yield라는 문장을 만나게 되면 그 값을 리턴하고 현재의 상태를 그대로 기억한다는 점이다. 이건 마치 음악 플레이어를 재생하다가 포즈(pause)로 멈추어 놓은 것과 비슷한 모양새이다. 이러한 방식을 전문 용어로 코루틴(Coroutine)이라고 한다.
제너레이터는 이러한 이유로 코루틴(coroutine)이라고도 불리운다.
제너레이터는 def를 이용한 함수로 만들수 있지만 다음과 같이 튜플 표현식으로 좀 더 간편하게 만들 수 있다.
===========================================================
gen = (i * i for i in range(1, 1000))
===========================================================
위 표현식은 mygen 함수로 만든 제너레이터와 완전히 동일한 기능을 한다. 여기서 사용된 표현식은 리스트 내포(list comprehension) 구문과 비슷하다. 다만 리스트 대신 튜플을 이용한 점이 다르다.
위와 같은 표현식을 제너레이터 표현식이(generator expression)라고 부른다.
제너레이터의 가장 큰 이점 중 하나는 대량의 데이터를 처리할 때이다.
===========================================================
with open('bigdata.txt') as f:
for line in f:
# process the line
===========================================================
파이썬은 기본적으로 파일 객체를 제너레이터로 만들어 처리한다. 이렇게 제너레이터를 사용하면 파일을 모두 읽어서 메모리에 올려 놓은 후에 처리하는 것이 아니라 한줄씩 순서대로 처리하게 만들어 주기 때문에 작은 메모리만으로 대용량 파일 처리가 가능해 진다!!!!!!!!!!!!!!!!!
결론
살펴본 제너레이터는 이터레이터와 상당히 비슷하다는 것을 알 수 있다. 클래스를 이용하여 이터레이터를 작성하면 좀 더 복잡한 행동을 하도록 만들 수 있다. 반면에 제너레이터 표현식 같은 것을 이용하면 정말 간단하게 이터레이터를 만들 수 있다. 따라서 만들어 낼 이터레이터의 성격에 따라 클래스로 작성할 것인지 제너레이터로 작성할 것인지를 선택해야 한다.
보통 간단한 경우라면 제너레이터 함수나 제너레이터 표현식을 사용하는 것이 가독성 및 유지보수 측면에서 유리하다.
'취업과 기본기 잡기 > Python' 카테고리의 다른 글
| Python (0) | 2022.04.01 |
|---|