이펙티브 파이썬 Item 21. 변수 영역과 클로저의 상호작용 방식을 이해하라

    요약

    • 클로저는 자유변수를 참조하는 함수다. 
    • 파이썬의 변수 참조는 다음 순서로 이루어진다
      1. 함수가 선언된 내부 영역
      2. 함수를 둘러싼 영역
      3. 모듈
      4. 빌트인 영역(len, str 등이 선언된)
    • 변수 참조 순서를 고려해서 필요한 경우 nonlocal 키워드를 이용해 자유변수임을 알려줘야한다. 그렇지 않으면 기대하지 않은 방식으로 동작한다.
    • nonlocal 키워드는 가급적이면 적게 써야한다. nonlocal 키워드 선언 부분과 변수가 실제로 사용되는 부분이 멀어지면 이해하기 어려운 코드가 되기 때문이다.

    이펙티브 파이썬 Item 21. 변수 영역과 클로저의 상호작용 방식을 이해하라

    # helper는 클로저가 됨. 
    def sort_priority(values, group):
        def helper(x):
            if x in group:
                return 0,x
            return 1,x
        values.sort(key=helper)
    
    n = [8,3,1,2,5,4,7,6]
    group = {2,3,5,7}
    sort_priority(n, group)
    print(n)
    >>>
    [2, 3, 5, 7, 1, 4, 6, 8]

    다음 코드에서 helper 함수는 정상적으로 사용된다. 이것은 helper()가 클로저로 동작했기 때문이다. 

    • 클로저 : 자신이 정의된 영역 밖의 변수(자유변수)를 참조하는 함수다. 파이썬에서 클로저를 지원하기 때문에 helper()는 group을 참조할 수 있게 된다. 
    • 파이썬에서 함수는 일급 객체다. 변수로 전달, If 문에서 함수 객체끼리 비교하는 곳에서 사용할 수 있다. 
    def sort_priority(values, group):
        found = False
        def helper(x):
            if x in group:
                # helper 안의 found는 자유변수가 아님. 즉, 외부의 found랑 무관함.
                found = True
                return 0,x
            return 1,x
        values.sort(key=helper)
        return found
    
    n = [8,3,1,2,5,4,7,6]
    group = {2,3,5,7}
    print(sort_priority(n, group))
    >>>
    False

    클로저를 이용할 때, 변수의 범위에 주의를 기울이지 않으면 실수하기 쉽다. 

    • 만약 주어진 group 안에 원하는 수가 있다면 found = True로 설정한 후 반환해주는 함수를 만들었다고 가정해보자. 
    • 이 때, 결과는 True일 것을 기대하지만 실제로는 False가 나온다. (기대하지 않은 결과가 나옴)

    왜 이렇게 동작하는 것일까? 파이썬에서 변수를 참조하는 순서를 고려해보면 알 수 있다.

    1. 함수가 선언된 영역에서 변수를 찾음.
    2. 현재 함수를 둘러싼 영역에서 변수를 찾음.
    3. 현재 코드가 들어있는 모듈에서 변수를 찾음.
    4. 내장 영역(built-in scope) (len, str 등의 함수가 들어있는 영역) 

    found는 helper() 내부에 선언되어 있기 때문에 1번에 대응되는 케이스가 된다. 즉, sort_priority()에 선언된 found와 helper()에 선언된 found는 서로 다른 변수다. 이런 이유 때문에 기대하지 않은 결과가 나타난다. 

    # nonlocal은 가급적이면 사용하지 마라.
    # nonlocal로 선언된 부분 ↔ 실제 변수가 사용되는 시점이 멀어지면, 이해하기 어려워진다.
    def sort_priority(values, group):
        found = False
        def helper(x):
            nonlocal found # found는 지역변수가 아니라 자유변수임을 알려줌.
            if x in group:
                found = True
                return 0,x
            return 1,x
        values.sort(key=helper)
        return found
    
    n = [8,3,1,2,5,4,7,6]
    group = {2,3,5,7}
    print(sort_priority(n, group))

    이 부분을 해결하기 위해서 found가 지역변수가 아니라는 것을 알려주기만 하면 된다.

    • nonlocal 키워드를 이용해 처리할 수 있다. nonlocal 키워드는 모듈 수준 이상으로 변수가 찾아올라가도록 하지는 않는다.
    • nonlocal 키워드가 이 부분을 해결해주지만, 가급적이면 사용하지 않아야한다. nonlocal이 선언된 부분과 실제 변수가 사용되는 영역이 멀어지게 되면 이해하기 어려운 코드가 되기 때문이다. 

    댓글

    Designed by JB FACTORY