관리 메뉴

클라이언트/ 서버/ 엔지니어 " 게임 개발자"를 향한 매일의 공부일지

파이썬 날아오르기 2 - 클로저와 데코레이터 본문

프로그래밍 언어/파이썬

파이썬 날아오르기 2 - 클로저와 데코레이터

huenuri 2024. 10. 5. 07:55

데코레이터를 이해하려면 먼저 클로저를 알아야 한다. 클로저를 먼저 알아보고 데코레이터를 살펴볼 것이다.


 

 

 

클로저란?

클로저란 간단히 말해 함수 안에 내부 함수를 구현하고 그 내부 함수를 리턴하는 함수를 말한다. 이때 외부 함수는 자신이 가진 변수값 등을 내부 함수에 전달할 수 있다. 다음 예제를 통해 확인해 보자.

어떤 수에 항상 3을 곱해 리턴하는 함수를 생각해 보자. 

 

mul3() 함수는 입력으로 받은 수 n에 항상 3을 곱하여 리턴한다. 이번에는 항상 5를 곱하여 리턴하는 함수를 생각해보자.

 

 

하지만 이렇게 필요할 때마다 mul6(), mul(7), ...과 같은 함수를 만드는 것은 매우 비효율적이다. 이 문제를 해결하려면 다음과 같은 클래스를 사용하면 된다.


 

 

 

 

클래스를 이용해 특정 값을 미리 설정하고 그다음부터 mul() 메서드를 사용하면 원하는 형태로 호출할 수 있다. __call__ 메서드를 이용하여 다음과 같이 개선할 수 있다.

 

 

__call__ 메서드를 이용하면 mul3 객체를 mul3(10)처럼 호출할 수 있다. 이것보다 더 간편한 방법이 있다.


 

 

 

외부 함수 mul 안에 내부 함수 wrapper를 구현했다. 함수가 함수를 리턴하는 것은 파이썬에서만 가능하다. mul 함수에서 wrapper 함수를 리턴할 때 mul 함수 호출 시 인수로 받은 m값을 wrapper 함수에 저장하여 리턴한다.

이것은 마치 클래스가 특정한 값을 설정해 객체를 만드는 과정과 매우 비슷하다. 이런 mul과 같은 함수를 파이썬에서는 클로저라고 한다.


 

 

 

 

데코레이터란?

함수의 실행 시간을 측정해야 한다면 어떻게 해야 할까? 함수 실행 시간은 함수가 시작하는 순간의 시간과 함수가 종료되는 순간의 시간 차이를 구하면 알 수 있다. 다음과 같이 코드를 작성하면 함수의 실행 시간을 측정 할 수 있다.

 

 

하지만 실행 시간을 측정해야 하는 함수가 myfunc 말고도 많다면 이런 코드를 모든 함수에 마찬가지로 적용하는 것은 너무 비효율적이다. 이때 클로저를 이용하면 좀 더 효율적인 방법을 찾을 수 있다.


 

 

 

elapsed 함수로 클로저를 만들었다. 이 함수는 함수를 인수로 받는다. 파이썬은 함수도 객체이므로 함수 자체를 인수로 전달할 수 있다.

decorated_myfunc = elapsed(myfunc)로 생성한 decorated_myfunc를 실행하면 elapsed 함수 내부의 wrapper 함수가 실행되고, 이 함수는 전달받은 myfunc 함수를 실행하면서 실행 시간을 함께 출력한다.

클로저를 이용하면 기존 함수에 기능을 덧붙이기가 매우 편리하다. 이렇게 기존 함수를 바꾸지 않고 기능을 추가할 수 있게 만드는 elapsed 함수와 같은 클로저를 데코레이터라고 한다.

 

 

파이썬 데코레이터는 다음처럼 @ 문자를 이용해 함수 위에 적용하여 사용할 수 있다.

 

파이썬은 함수 위에 @+함수명이 있으면 데코레이터 함수로 인식한다. 따라서 이제 myfunc 함수는 'elapsed 데코레이터'를 통해 수행될 것이다.

 

 

결과가 잘 출력된다.


 

 

 

 

문자열을 입력받아 출력하도록 myfunc 함수를 수정했다. 하지만 코드를 수정하면 다음과 같은 오류가 발생한다. 이 오류는 myfunc 함수는 입력 인수가 필요하지만, elapsed 함수 안의 wrapper 함수는 전달받은 myfunc 함수를 입력 인수에 상관없이 호출하기 때문에 발생한다.

그러므로 데코레이터 함수는 기존 함수의 입력 인수에 상관없이 동작하도록 만들어야 한다. 데코레이터는 기존 함수가 어떤 입력 인수를 취할지 알 수 없기 때문이다. 이렇게 전달받아야 하는 기존 함수의 입력 인수를 알 수 없는 경우에는 *args와 **args와 **kwargs 매개변수를 이용하면 된다.

 

처음에는 start에 * 매개변수를 넣어 오류가 발생했다. 이제 잘 된다.


 

 

 

*args와 **kwargs

*args는 모든 입력 인수를 튜플로 변환하는 매개변수, **kwargs는 모든 키=값 형태의 입력 인수를 딕셔너리로 변환하는 매개변수이다.

 

func 함수가 입력 인수의 개수와 형태에 관계없이 모든 입력을 처리하려면 어떻게 해야 할까?

 

 

이렇게 하면 일반 입력은 args 튜플에, 키=값  형태의 입력은 kwargs 딕셔너리로 저장한다.

 


 

 

 

학습을 마치고

어제부터 공부가 조금 하기 싫은 마음이 있어서 천천히 하고 있다. 내가 좋아하는 방송을 들으면서 하다 보면 어느새 다시 공부하는 시간이 즐거워진다.

이제 세 번째 단원을 학습하러 가야겠다.