betterway21 클로저

김승환·2021년 7월 11일

코딩의 기술

목록 보기
12/36

변수 영역과 클로저의 상호작용 방식을 이해하라

  • 숫자로 이뤄진 list를 정렬하되, 정렬한 리스트의 앞쪽에는 우선순위를 부여한 몇몇 숫자를 위치시켜야 한다고 가정하자.
    - 리스트의 sort 메서드에 key인자로 도우미 함수를 전달하는 것
    - list는 각 원소를 정렬할 때 이 도우미 함수가 반환하는 값을 기준으로 사용
#입력이 간단하면 잘 작동한다.
def sort_priority(values, group):
    def helper(x):
        if x in group:
            print((0,x))
            return (0, x)
        print((1,x))
        return (1, x)
    values.sort(key=helper)


#helper2=[(1, 8), (0, 3), (1, 1), (0, 2), (0, 5), (1, 4), (0, 7), (1, 6)]
#numbers.sort(key=helper2)


numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
sort_priority(numbers, group)
print(numbers)

(1, 8)
(0, 3)
(1, 1)
(0, 2)
(0, 5)
(1, 4)
(0, 7)
(1, 6)
[2, 3, 5, 7, 1, 4, 6, 8]

numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = [2, 3, 5, 7]
numbers.sort()
group.sort()
numbers= group+numbers
print(numbers)

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

def helper(x):
    if x in group:
        return (0, x)
    return (1, x)

File "", line 2
return (0, x)
^
SyntaxError: 'return' outside function

numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
helper2=[(1, 8), (0, 3), (1, 1), (0, 2), (0, 5), (1, 4), (0, 7), (1, 6)]
print(helper2[0])
print(numbers)
numbers.sort(key=helper2)
for i in range(8):
    numbers.sort(key=helper2[i])

print(numbers)

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

TypeError Traceback (most recent call last)
in
4 print(helper2[0])
5 print(numbers)
----> 6 numbers.sort(key=helper2)
7 for i in range(8):
8 numbers.sort(key=helper2[i])

TypeError: 'list' object is not callable

def sort_priority(values):
    for i in range(8):
        values.sort(key=helper2[i])
sort_priority(numbers)
print(numbers)

TypeError Traceback (most recent call last)
in
2 for i in range(8):
3 values.sort(key=helper2[i])
----> 4 sort_priority(numbers)
5 print(numbers)

in sort_priority(values)
1 def sort_priority(values):
2 for i in range(8):
----> 3 values.sort(key=helper2[i])
4 sort_priority(numbers)
5 print(numbers)

TypeError: 'tuple' object is not callable

helper2=[(1, 8), (0, 3), (1, 1), (0, 2), (0, 5), (1, 4), (0, 7), (1, 6)]
print(helper2)
helper2.sort()
print(helper2)

[(1, 8), (0, 3), (1, 1), (0, 2), (0, 5), (1, 4), (0, 7), (1, 6)][(0, 2), (0, 3), (0, 5), (0, 7), (1, 1), (1, 4), (1, 6), (1, 8)]

def sss(values,group):
    def helper3(x):
        for x in group:
            print(x)
            return x
    values.sort(key=helper3)
group=[(0, 2), (0, 3), (0, 5), (0, 7), (1, 1), (1, 4), (1, 6), (1, 8)]
numbers = [8, 3, 1, 2, 5, 4, 7, 6]   
sss(numbers,group)
print(numbers)

(0, 2)
(0, 2)
(0, 2)
(0, 2)
(0, 2)
(0, 2)
(0, 2)
(0, 2)
[8, 3, 1, 2, 5, 4, 7, 6]

위 함수가 작동하는 세가지 이유(나는 잘 모르겠음)

  • 파이썬 클로저를 지원 : 클로저란 자신이 정의된 영역 밖의 변수를 참조하는 함수.
  • 파이썬 함수는 일급 시민객체 : 일급 시민 객체라는 말은 이를 직접 가리킬 수 있고, 변수에 대입하거나 다른 함수에 인자로 전달 할 수 있으며, 식이나 if 문에서 함수를 비교하거나 함수에서 반환하는 것 등이 가능하다. sort메서드는 클로저 함수를 key인자로 받을 수 있다.
  • 파이썬에는 시퀀스를 비교하는 구체적인 규칙이 있음 : 파이썬은 시퀀스를 비교할 때 0번 인덱스에 있는 값을 비교한 다음, 이 값이 같으면 다시 1번 인덱스에 있는 값을 비교. 이런식으로 순서대로 원소를 비교해 두 값이 같으면 다음 원소로 넘어가는 작업을 시퀀스의 모든 원소를 다 비교하거나 결과가 정해질 때까지 계속한다.

우선 순위가 높은 원소가 있는지 여부도 반환하게 만들어서 UI가 우선순위가 높은 원소가 있을 때 아닌 때를 구분 처리 하면 좋다.

  • 클로저 함수를 이용하여 표시한다.
def sort_priority2(numbers, group):
    found = False
    def helper(x):
        if x in group:
            found = True # 문제를 쉽게 해결할 수 있을 것 같다
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found

numbers = [8, 3, 1, 2, 5, 4, 7, 8]
group = {2, 3, 5, 7}
found = sort_priority2(numbers, group)
print('발견:', found)
print(numbers)

발견: False
[2, 3, 5, 7, 1, 4, 8, 8]

발견을 했다면 True인데 왜 Flase일까?

  • 식 안의 변수를 참조할 때 파이썬 인터프리터는 이 참조를 해결하기 위해 아래 순으로 영역을 찾는다.
    - 현재 함수 영역
    - 현재 함수를 둘러싼 영역
    - 현재 코드가 들어 있는 모듈의 영역
    - 내장 영역
  • 이름에 해당하는 변수가 이 네가지 영역에 없으면 NameError 예외가 발생
foo = does_not_exist * 5

NameError Traceback (most recent call last)
in
----> 1 foo = does_not_exist * 5

NameError: name 'does_not_exist' is not defined

#위 예시 증명
def test1():
    x=False
    def test2():
        nonlocal x
        x=True
    test2()
    print(x)
    

z= test1()
print(z)

False
None

  • 변수가 현재 영역에 이미 정의돼 있다면 그 변수의 값만 새로운 값으로 바뀐다.
  • 하지만 변수가 현재 영역에 정의돼 있지 않다면 파이썬은 변수 대입을 변수 정의로 취급한다.
# helper 함수의 클로저 안에서 이 대입문은 helper 영역 안에 새로운 변수를 정의하는 것으로 취급일뿐이다.
def sort_priority2(numbers, group):
    found = False  # 영역: 'sort_priority2'
    def helper(x):
        if x in group:
            found = True  # 영역: 'helper' -- 좋지 않음!
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found

초보 프로그래머 에게는 영역 지정 버그라고 부르기도 한다.

파이썬에는 클로저 밖으로 데이터를 끌어내는 특별한 구문이 있다.

  • nonlocal문이 지정된 변수에 대해서는 앞에서 설명한 영역 규칙에 따라 대입될 변수의 영역이 결정된다.
  • nonlocal의 한계점은 모듈 수준 영역까지 변수 이름을 찾아 올라가지는 못한다.
def sort_priority2(numbers, group):
    found = False
    def helper(x):
        nonlocal found       # 추가함
        if x in group:
            found = True
            return (0, x)
        return (1, x)
    numbers.sort(key=helper)
    return found
  • 위 예시에서 nonlocal 문은 대입할 데이터가 클로저 밖에 있어서 다른 영역에 속한다는 사실을 알려준다.

  • 사용은 추천하지 않는다.

  • 함수가 매우 길 경우 nonlocal 대입이 이뤄지는 위치 거리가 멀어져서 이해하기 힘들어진다.

  • nonlocal : 지역변수가 아님을 선언

  • nonlocal 이 사용된 함수 바로 한단계 바깥쪽에 위치한 변수와 바인딩을 할 수 있다.

#nonlocal의 예시
x = 20 # 전역변수 (global variable)

def f():
    x = 40
    
    def g():
        nonlocal x
        x = 80
        
    g()  # 함수 g를 실행하여 nonlocal이 적용되도록 한다.
    print(x)  # 함수 f에서의 x값이 출력된다.(함수 g에서 nonlocal 의 영향을 받아 변수가 80으로 변경되었다.)
f()
print(x)  # 모든 함수 실행이 끝나고, 변수 x를 출력한다.(출력값은 처음값인 20이다)

80
20

  • 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 #클로저가 아니기떄문에 여기 True값을 찾음
            return (0, x)
        return (1, x)

numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
    
sorter = Sorter(group)
numbers.sort(key=sorter)
assert sorter.found is True

True

profile
인공지능 파이팅!

0개의 댓글