Python 프로그래밍

[Python] Closer

DayOff 2022. 5. 2. 16:42

 

Closer

  클로저는 어떤 함수의 내부 함수가 외부 함수의 변수(프리변수)를 참조할 때, 외부 함수가 종료된 후에도 내부 함수가 외부 함수의 변수를 참조할 수 있도록 어딘가에 저장하는 함수를 의미합니다. 클로저 함수는 아래의 조건을 충족해야합니다.  (프리변수(free variable)는 어떤 함수에서 사용되지만 그 함수 내부에서 선언되지 않은 변수를 의미합니다.)

  • 어떤 함수의 내부 함수일 것
  • 그 내부 함수가 외부 함수의 변수를 참조할 것
  • 외부 함수가 내부 함수를 리턴할 것

 

↘ Closer에 대해 자세히 알아보자

def hello(msg):
    message = "Hi, " + msg

    def say():
        print(message)

    return say

  여기 hello라는 함수가 하나 있습니다. 매개변수로 msg를 받아 message라는 변수에 문자열로 저장하고 내부함수인 say가 message를 print로 출력해줍니다. 그리고 마지막으로 say함수를(함수 실행값이 아닌 함수 자체를)리턴하고 끝납니다. 정말 단순하기 짝이없는 함수지만, 자세히 보시면 say 함수가 클로저의 조건을 모두 만족한다는 사실을 알 수 있습니다.

  1. 어떤 함수의 내부 함수일 것 - say함수는 hello함수의 내부 함수

  2. 그 내부 함수가 외부 함수의 변수를 참조할 것 - say함수는 외부 함수의 message를 참조

  3. 외부 함수가 내부 함수를 리턴할 것 - hello함수는 say함수를 리턴

즉, say함수는 클로저가 될 수 있습니다.

def hello(msg):
    message = "Hi, " + msg

    def say():
        print(message)

    return say

f = hello("Fox") # 클로저 생성
f() # 실행 결과 : "Hi, Fox" 출력

  f( ) 함수를 실행했을 때 "Hi Fox"라는 문자열이 출력되기까지의 과정을 확인해보자면

1. hello함수에 "Fox"를 매개변수값으로 넘겨주며 실행
2. message변수에 매개변수를 이용해 "Hi, Fox"라는 문자열을 저장
3. say함수가 message변수를 참조
4. say함수 리턴
5. f변수가 say함수를 참조
6. f변수 실행(say함수 실행)
7. f변수는 message변수를 출력

 위의 4번에서 hello 함수는 역할을 마치고 종료되어 메모리에서도 삭제되었을 것이다. 그러므로 hello 함수 영역의 message 변수도 삭제되었어야 한다. 하지만 어떻게 f함수가 message변수를 참조해 값을 출력한다는 것일까.... 

  이것을 가능케 한 것이 바로 클로저입니다. 중첩 함수인 say가 외부 함수인 hello의 변수 message를 참조하기에 message변수와 say의 환경을 저장하는 클로저가 동적으로 생성되었고 f가 실행될때는 해당 클로저를 참조하여 message값을 출력할 수 있는 것입니다. 이 클로저는 f변수에 say함수가 할당될 때 생성됩니다.

  이 클로저(Closure)의 영역을 찾아본다면 f.__closure__[0].cell__contents 로 찾을 수 있다. 참고로 저 __closure__ 튜플은 모든 함수 객체가 가지고 있다. 그렇기 때문에 조건을 만족하지 않아 클로저가 생성되지 않았다면, 그 값은 None으로 고정된다.

 

↘ Closer의 장점

클로저의 가장 큰 장점은 무분별한 전역변수의 남용 방지라고 생각된다. 단순히 생각하면 클로저를 쓰는 대신 전역변수를 선언해 상시 접근 가능하게 만들 수 있지만 이렇게 하면 변수가 섞일수도 있고 변수의 책임 범위를 명확하게 할 수 없는 문제가 생깁니다. 하지만 클로저를 사용하면 각 스코프가 클로저로 생성되므로 변수가 섞일 일도 없고 각 스코프에서 고유한 용도로 이용되므로 책임 범위 또한 명확해지죠.

 위의 예시에서는 내부 함수가 1개밖에 없지만 만약 내부 함수가 여러개라면? 그 여러개의 내부 함수에서 접근할 수 있게 각각 전역변수를 만들어 값을 지정한다면 코드가 지저분해지고 변수명도 점점 알아보기 힘들어지겠죠? 이를 방지해주는게 가장 큰 장점이라고 생각합니다.