Python

파이썬 스터디 7장 - 파이썬 날아오르기 (1)

공대생철이 2023. 7. 6. 22:00
728x90

이번에 살펴볼 것은 파이썬의 고급 개념들이다.

 

클로저와 데코레이터

 

클로저 closure는 함수 안에 내부 함수를 inner function을 구현하고 그 내부 함수를 리턴하는 함수를 말한다. 이 때 외부 함수는 자신이 가진 변숫값 등을 내부 함수에 전달할 수 있다. 

 

class Mul:
    def __init__(self,m):
        self.m = m
    def __call__(self,n):
        return self.m * n
        
if __name__== "__main__":
    mul3 = Mul(3)
    mul5 = Mul(5)
    
    print(mul3(10)) // 30
    print(mul5(10)) // 50

Mul이라는 클래스를 만들고 m 값을 하나 할당해준다.

__call__이라는 메서드는 클래스로 만든 객체에서 인수를 전달하여 바로 호출되는 함수이다.

 

즉, mul3(10)은 m이 3으로 정해진 객체에서 10이라는 인수를 받아 __call__에 있는 함수를 실행하게 된다. 이 때 10은 n에 할당되어 최종적으로 3 곱하기 10인 30이 출력되게 된다.

mul5는 m이 5인 객체이므로 5 곱하기 10을 하게 된다.

def mul(m):
    def wrapper(n):
        return m * n
    return wrapper
    
if __name__ == "__main__":
    mul3= mul(3)
    
    print(mul3(10)) // 30

하나씩 코드를 따라가보자.

mul(3)이 실행되면 m에 3이 들어가게 된다. 그러면 mul 안에 있는 wrapper라는 함수는 3 * n 의 결과를 출력하게 될 것이다. 이런 값을 기능을 가진 함수를 리턴하는 것이 mul의 리턴값이다. 이 때 주의해야하는 것은 인수로 받은 m의 값이 wrapper 함수에 저장되어 리턴된다는 것이다. 

 

그래서 mul3(10)을 하게 되면 wrapper(10)을 하는 것과 똑같은데 이 wrapper는 mul3를 선언할 때 3이 저장되어 들어왔으므로 30의 값을 리턴하게 된다.

 

이게 클로저다. 함수가 내부에서 선언된 함수를 리턴하고 그 내부 함수는 외부 껍데기에서 받아온 매개변숫값을 전달 받을 수 있다.

 

import time

def elapsed(original_func):
    def wrapper():
        start = time.time()
        result = original_func()
        end = time.time()
        print('함수 수행시간 : {} 초'.format(end-start))
        return result
    return wrapper
    
    
def myfunc():
    print("함수가 실행됩니다.")
    
decorated_myfunc = elapsed(myfunc)
decorated_myfunc()

// 함수가 실행됩니다.
// 함수 수행시간 : 0.00머시기 초

먼저 위의 함수는 함수의 실행 시간을 측정하기 위한 함수이다.

 

또 하나씩 살펴보자.

elapsed는 함수를 인자로 받는다.

그 안에 wrapper라는 내장함수는 start 시간을 측정하고 인자로 받은 함수를 실행항 후 결과값을 result에 담는다.

그 다음 end 시간을 측정하고 그 시간차를 print하고 result를 리턴하는 함수이다.

elapsed는 위의 기능을 가진 wrapper 함수를 리턴한다.

 

임의의 함수 myfunc를 하나 불러온 다음 decorated_myfunc를 선언해주었다.

decorated_myfunc는 myfunc 함수를 인자로 받은 elapsed 함수이다.

 

그리고 함수를 실행하게 되면

1. wrapper 함수 안에서 original_func 대신 myfunc가 들어가서

2. start랑 end 사이에서 실행 -> 이 때 "함수가 실행됩니다." 출력

3. 그리고 시간차를 나타내는 print 실행 -> 이 때 "함수 수행시간 : ~ 초" 출력

4. return 값은 따로 없으므로 그대로 함수 종료

 

클로저를 이용하여 기존 함수에 기능을 덧붙인 것이다. 기존 함수를 바꾸지 않고 기능을 추가할 수 있게 만드는 elapsed 같은 함수를 데코레이터 decorator 라고 한다. 말 그래도 함수를 꾸미는 함수이다.

 

위의 코드를 @문자를 사용해서 작성할 수도 있다.

import time

def elapsed(original_func):
    def wrapper():
        start = time.time()
        result = original_func()
        end = time.time()
        print('함수 수행시간 : {} 초'.format(end-start))
        return result
    return wrapper
    
@elapsed
def myfunc():
    print("함수가 실행됩니다.")
    
myfunc()

// 함수가 실행됩니다.
// 함수 수행시간 : 0.00머시기 초

@+함수명을 사용하면 데코레이터 함수로 인식하여 elapsed 안에 myfunc가 들어간 형태로 인식한 후 실행한다. 결과는 똑같다.

 

myfunc가 매개변수를 필요로 하면 어떨까?

import time

def elapsed(original_func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = original_func(*args, **kwargs)
        end = time.time()
        print('함수 수행시간 : {} 초'.format(end-start))
        return result
    return wrapper
    
@elapsed
def myfunc(msg):
    print("{} 출력되었습니다.".format(msg))
    
myfunc("You need Python")

// You need Python 출력되었습니다.
// 함수 수행시간 : 0.00머시기 초

예전에 배웠던 *args, **kwargs를 활용하면 된다.

*args는 모든 인수를 묶어서 튜플로 변환해주고, **kwargs는 "키=값" 형태의 인수를 딕셔너리로 변환해준다.

 

여기서는 "You need Python" 문자열이 *args로 전달되어 실행된 것이다.

 

 

728x90