Corgi Dog Bark

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective Python 21. 클로져란
    뜯고 또 뜯어보는 컴퓨터/파이썬 (Python) 2021. 7. 30. 00:17
    반응형

    1. 파이썬의 클로저와 변수영역의 이해


    - 클로저를 보기 전, 먼저 숫자로 이루어진 List 에서 몇개의 변수(group 데이터)만을 앞으로 오고자 하는 경우를 생각해보자.

    def sort_priority(values, group): 
        """
        values 의 값들 중, group에 표현되어 있는 값들만 정렬해서 앞으로 빼오고 싶다,
        """
        def helper(x):
            if x in group:
                return (0,x)
            else:
                return (1,x)
        values.sort(key=helper)
    numbers = [8,3,1,2,5,4,7,6]
    group = [2,3,5,7]
    sort_priority(numbers, group)
    print(numbers)
    더보기

    [2, 3, 5, 7, 1, 4, 6, 8]

    - 위 보기와 같이 sort_priority 함수가 가능한 이유를 보게 되면 다음과 같은 3가지의 파이썬의 특징을 살펴볼 수 있다.

    1. 파이썬이 클로져(Closure)를 지원 : 파이썬에서의 클로저란 자신이 정의된 영역 밖의 변수를 참조하는 것인데, 여기서 Closure라는 개념때문에 key=helper함수가, group에 접근을 가능할 수 있었다.(즉 내부의 함수가 외부의 변수를 참조 가능케 하는 개념.)
    2. 파이썬의 일급 시민(first-clas citizen) 객체 : 일금시민 객체는 이 객체를 직접 가르키기도 하고, 다른 인자로 전달(여기서는 함수의 인자)로도 가능하다는 의미이다. 이 성질로 인해 sort 메서드는 클로저 함수(=helper())를 인자로 받을 수 있었다.
    3. 파이썬에서는 시퀀스를 비교하는 구체적 규칙. : key = helper()가 되었을시, 투플이 반환되는데, 이를 sort 시킬때, 0번 인덱스 -> 1번 인덱스 순으로 넘어가기에 원하는 순서로 정렬 가능.

     

     

    2.  다음으로 우선순위가 높은 원소(=group) 안의 원소가 numbers에 포함되어 있는지를 살피는 함수를 살펴보자

    def sort_priority2(numbers, group):
        found = False
        def helper(x):
            if x in group:
                found = True #--> 손쉽게 True로 반환이 될 거 같다.
                return (0,x)
            else:
                return (1,x)
        numbers.sort(key=helper)
        return found
    found = sort_priority2(numbers, group)
    print("발견",found)
    print(numbers)
    더보기

    발견 False

    [2, 3, 5, 7, 1, 4, 6, 8]

     

    2-1.  found = True로 반환이 되어야 하는데 False로 반환이 되었다. 왜 그럴까. 다음과 같은 이유를 생각해 볼 수 있다. 파이썬은 밑의 4가지 영역을 순서대로 변수를 찾고, 만약 원하는 변수가 없을시, NameError가 발생한다.

    • 현재 함수의 영역
    • 현재 함수를 둘러싼 영역(현재 함수를 둘러싸고 있는 함수 등등)
    • 현재 코드가 들어 있는 모듈의 영역(전역 영역(global space)이라고도 부름)
    • 내장 영역(built-in-scope) 의 영역

     

    2-2. 하지만 변수에 값을 대입하는 것은 다른 방식으로 작동한다.

    • 변수가 현재 영역에 이미 존재한다면, 변수의 값을 바꾼다.
    • 하지만 변수가 현재 영역에 존재하지 않는다면, 새로운 변수로 취급하게 된다. 따라서 위의 예제에서는 found 라는 helper() 영역 안에서의 found 를 만들게 되었다.
    • 이 문제는 초보 파이썬 프로그래머를 종종 당황하게 만들기 때문에 영역 지정 버그 라고도 부른다.
    • 파이썬에서는 클로져 밖으로 데이터를 끌어내는 특별한 구문이 존재한다. ---> nonlocal 문.(nonlocal 또한 전역영역으로는 올라가지 못한다,)
    def sort_priority_2(numbers, group):
        found = False   # ---> 영역 : sort_prioriry_2()
        def helper(x):
            if x in group:
                found = True # ----> 영역 : helper() 영역에서 새로운 변수를 만든다.
                return (0,x)
            else: 
                return (1,x)
        numbers.sort(key = helper)
        return found
    # nonlocal 을 통한 개선된 코드
    def sort_priority_2(numbers, group):
        found = False   # ---> 영역 : sort_prioriry_2()
        def helper(x):
            nonlocal found # ---> nonlocal 문은 대입할 데이터가 클로져 밖에 있다는 사실을 알려준다.
            if x in group:
                found = True 
                return (0,x)
            else: 
                return (1,x)
        numbers.sort(key = helper)
        return found

    2-3. Nonlocal을 사용시 주의사항.

    1. 여러 안티패턴과 마찬가지로, 어떠한 경우에도 nonlocal을 사용하지 말라고 경고한다.
    2. nonlocal을 사용시, 지정한 변수와 대입이 이뤄지는 변수간의 거리가 너무 멀 경우, 코드를 수정하기 어렵다.
    3. nonlocal을 사용시, 복잡해지는 형태일때, 도우미 함수로 감싸는 것이 낫다. 다음의 코드는 같은 결과를 달성하는 클래스이다.
    class Sorter:
        def __init__(self,group):
            self.group = group
            self.found = False
            
        def __call__(self, x):
            if x in self.group:
                self.found = True
                return (0,x)
            else:
                return (1,x)
            
    sorter = Sorter(group)
    numbers.sort(key=sorter)
    print(sorter.found == True)
    더보기

    True

    0. 기억해야할 내용

    1. 클로저 함순느 정의된 영역 외 (외부함수)에서 정의된 변수도 참조는 가능 -> 변형은 불가
    2. 기본적으로 클로져 내부에 사용한 대입문은 클로저를 감싸는 영역에 영향을 끼칠 수 없다.
    3. 왠만하면 nonlocal 쓰지 말아라.
    반응형

    댓글

Designed by Tistory.