클로저
함수 안의 함수를 결과로 반환할 때, 그 내부 함수를 클로저(Closure)라고 한다. 사용되는 곳은 다음과 같다.
- 콜백(Callback) 함수에 사용
- 함수의 순차적 실행
- 데코레이터 함수
# 받은 수에 3을 곱하는 함수
def mul3(n):
return n * 3
mul3(3)
3
# 받은 수에 5를 곱하는 함수
def mul5(n):
return n * 5
mul5(3)
15
이때 비슷한 기능을 하는 mul6, mul7, mul8... 도 만들어야 할까??
방법 1. 클래스(class) 사용
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 출력
위 코드를 조금 더 간결한 코드로 수정할 수 있다.
class Mul:
def __init__(self, m):
self.m = m
# 기존 mul 함수를 __call__ 함수명으로 변경
def __call__(self, n):
return self.m * n
__call__ 함수는 클래스 인스턴스를 함수처럼 호출할 때 실행된다. 여기서는 n이라는 인자를 받아 m과 곱셈을 수행한다.
if __name__ == "__main__":
mul3 = Mul(3)
mul5 = Mul(5)
print(mul3(10)) # 30 출력
print(mul5(10)) # 50 출력
방법 2. 클로저(closure) 사용하기
클로저는 함수 안에 함수가 있는데 내부의 함수를 뜻한다.
def mul(m): # 외부 함수
def wrapper(n): # 내부 함수 (클로저)
return m * n
return wrapper
if __name__ == "__main__":
mul3 = mul(3) # m = 3 인 wrapper 함수가 mul3 에 저장
mul5 = mul(5) # m = 5 인 wrapper 함수가 mul5 에 저장
print(mul3(10)) # m = 3, n = 10 인 wrapper 함수가 실행
print(mul5(10)) # m = 5, n = 10 인 wrapper 함수가 실행
30
50
방법 3. 데코레이터(Decorator) 사용
decorate는 '꾸미다, 장식하다'라는 뜻으로 데코레이터는 함수를 꾸며주는 함수다. 함수를 인수로 받는 클로저로 이해하면 된다. 사용되는 곳은 다음과 같다.
- 반복되는 작업을 여러 함수에 적용할 경우
- 기존 함수를 수정하지 않고 추가 기능을 구현하고 싶을 경우
함수 실행 시간을 측정하는 코드
import time
def func1(a, b):
start = time.time()
print("함수가 실행됩니다.")
val = a + b
end = time.time()
print("함수 수행시간: %f 초" % (end-start))
return val
if __name__ == "__main__":
result = func1(1, 2)
print(result)
함수가 실행됩니다.
함수 수행시간: 0.000044 초
3
def func2(a, b):
start = time.time()
print("함수가 실행됩니다.")
val = a * b
end = time.time()
print("함수 수행시간: %f 초" % (end-start))
return val
if __name__ == "__main__":
result = func2(1, 2)
print(result)
함수가 실행됩니다.
함수 수행시간: 0.001003 초
2
이런 코드가 있을 때 두 함수 모드 기능을 수행하는 코드는 한 줄인데 그 외 부수적인 기능이 반복되고 있다. 이 코드를 줄이고 싶을 때 데코레이터를 사용하면 된다.
데코레이터 만들기
클로저와 같은 구조로 만든 데코레이터를 사용하여 함수의 실행 시간을 측정하는 방법에 대해 알아보자.
먼저, 기본 기능인 함수 func1과 func2를 정의한다.
def func1(a, b):
val = a + b
return val
def func2(a, b):
val = a * b
return val
실행 시간 측정 데코레이터
데코레이터는 다른 함수를 인자로 받아, 그 함수에 추가적인 기능을 부여하는 함수다. 아래는 함수의 실행 시간을 측정하는 데코레이터 elapsed이다.
def elapsed(func): # 함수를 input으로 받는다.
def wrapper(a, b):
print('함수가 실행됩니다.')
start = time.time()
result = func(a, b) # 전달받은 함수 실행
end = time.time()
print("함수 수행시간: %f 초" % (end - start)) # 함수 수행시간
return result # 전달받은 함수의 실행 결과 반환
return wrapper
elapsed 데코레이터는 다음과 같은 작업을 수행한다:
1. func 함수와 동일한 인자를 받는 wrapper 함수를 정의한다.
2. wrapper 함수가 호출될 때, 먼저 “함수가 실행됩니다.” 메시지를 출력한다.
3. start 변수에 현재 시간을 저장한다.
4. func 함수를 실행하고 결과를 result 변수에 저장한다.
5. end 변수에 현재 시간을 저장한다.
6. 함수의 실행 시간을 계산하고 출력한다.
7. 최종적으로 func 함수의 결과를 반환한다.
명시적으로 데코레이터 사용 방법
함수에 직접 데코레이터를 적용하는 방식이다.
if __name__ == "__main__":
deco1 = elapsed(func1) # func1 함수에 데코레이터 적용
result = deco1(1,2) # 데코레이터가 적용된 함수 호출
print(result)
함수가 실행됩니다.
함수 수행시간: 0.000001 초
3
if __name__ == "__main__":
deco2 = elapsed(func2) # func2 함수에 데코레이터 적용
result = deco2(1,2) # 데코레이터가 적용된 함수 호출
print(result)
함수가 실행됩니다.
함수 수행시간: 0.000001 초
2
위 방법에서는 데코레이터를 함수에 적용한 후, 다시 데코레이터가 적용된 함수를 호출한다.
@ 표기법 사용 방법
더 간편한 방법으로, @ 표기법을 사용하여 함수 정의 시점에 데코레이터를 적용할 수 있다.
@elapsed
def func1(a, b):
val = a + b
return val
@elapsed
def func2(a, b):
val = a * b
return val
이렇게 하면, 함수 정의 시점에 데코레이터가 자동으로 적용되어 별도로 함수를 감싸는 작업이 필요 없다. 이제 함수 호출만으로 데코레이터 기능이 적용된다.
if __name__ == "__main__":
result = func1(1,2)
print(result)
if __name__ == "__main__":
result = func2(1,2)
print(result)
@ 표기법을 사용하면, 함수 정의 시 데코레이터를 적용할 수 있으므로, 명시적으로 데코레이터를 적용하는 코드가 필요하지 않다.
- @ 어노테이션을 쓰면서 없어진 코드
deco1 = elapsed(func1)
result = deco1(1, 2)
이번에는 데코레이터에 인자를 전달하는 방법을 살펴보자. 위 코드에서는 elapsed 데코레이터가 추가적인 인자를 받지 않았는데 이번에는 데코레이터에 추가 인자를 받는 상황을 가정해보자.
데코레이터에 인자 전달하기
generate_power 함수는 데코레이터를 만드는 함수로, 데코레이터가 적용된 함수의 반환 값을 특정 지수로 거듭제곱하는 기능을 한다. 이 함수는 외부에서 지수(exponent)를 받아, 그 지수를 사용하여 함수의 반환 값을 거듭제곱하는 역할을 한다.
데코레이터에 인자를 전달하기 위해서는 세 단계의 함수가 필요하다:
- 인자를 받는 함수: 데코레이터가 받을 인자를 처리하는 함수
- 데코레이터 함수: 실제 데코레이터 역할을 하는 함수
- 내부 래퍼 함수: 데코레이터가 적용된 함수를 실제로 감싸는 함수
def generate_power(exponent):
def wrapper(f):
def inner(*args):
result = f(*args)
return exponent ** result
return inner
return wrapper
- generate_power(exponent): 가장 외부의 함수로, 데코레이터가 사용할 인자를 받는다. 여기서는 exponent가 인자가 된다.
- wrapper(f): 데코레이터 함수로, 데코레이터가 적용될 함수를 인자로 받는다.
- inner(*args): 내부 래퍼 함수로, 실제로 데코레이터가 적용된 함수를 감싸고, 그 함수의 실행 결과를 수정한다.
이 구조를 사용하여 함수 raise_two에 데코레이터를 적용하면 다음과 같이 동작한다.
@generate_power(2)
def raise_two(n):
return n**2
위 코드는 다음과 같이 해석된다.
- generate_power(2)가 호출되어 exponent에 2를 전달한다. 이 호출은 wrapper 함수를 반환한다.
- wrapper(raise_two)가 호출되어 raise_two 함수를 인자로 받는다. 이 호출은 inner 함수를 반환한다.
- inner 함수는 raise_two 함수의 기능을 확장하여, raise_two 함수의 결과를 2의 거듭제곱으로 변환한다.
즉, raise_two 함수는 원래의 기능인 n**2을 수행한 후, 그 결과를 2의 거듭제곱으로 변환한다.
result = raise_two(3)
print(result)
- raise_two(3) 호출
- inner(3) 함수가 실행되어 raise_two(3)의 결과를 계산한다. 이 경우 raise_two(3)은
3**2 = 9
을 반환한다. - inner 함수는 2**9 = 512을 반환한다.
출력 결과는 512가 된다.
이 예제는 데코레이터에 인자를 전달하여, 데코레이터가 함수의 결과를 추가로 처리하는 방법을 보여줬다. 이런 방식으로 데코레이터에 유연성을 추가할 수 있는 방법까지 알아봤다!
'프로그래밍 언어 > Python' 카테고리의 다른 글
[numpy] 자동 형 변환, 기본 연산 (0) | 2024.08.04 |
---|---|
[numpy] arange, linspace (0) | 2024.08.04 |
파일 압축 - zlib, gzip, zipfile, tarfile (0) | 2024.08.04 |
파일 찾기, 복사, 이동 - glob, fnmatch, shutil (0) | 2024.08.04 |
파일 읽기, 저장 - Pickle 모듈 (0) | 2024.08.04 |