-
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을 사용시 주의사항.
- 여러 안티패턴과 마찬가지로, 어떠한 경우에도 nonlocal을 사용하지 말라고 경고한다.
- nonlocal을 사용시, 지정한 변수와 대입이 이뤄지는 변수간의 거리가 너무 멀 경우, 코드를 수정하기 어렵다.
- 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. 기억해야할 내용
- 클로저 함순느 정의된 영역 외 (외부함수)에서 정의된 변수도 참조는 가능 -> 변형은 불가
- 기본적으로 클로져 내부에 사용한 대입문은 클로저를 감싸는 영역에 영향을 끼칠 수 없다.
- 왠만하면 nonlocal 쓰지 말아라.
반응형'뜯고 또 뜯어보는 컴퓨터 > 파이썬 (Python)' 카테고리의 다른 글
Efficient python Ch24. 동적인 디폴트 인자 관리법 (0) 2021.07.31 Efficient Python 23. 키워드 인자 사용법 (0) 2021.07.30 Efficient Python 22. 변수위치 인자(*Args) 사용법 (0) 2021.07.30 Effective python ch20. None보다는 예외를 (0) 2021.07.29