파이썬에서 변수 호출과, 변수 할당이 어떤 영역에서 일어나는지 정확히 이해하자
숫자로 이루어진 list를 정렬하고 싶지만, 정렬한 리스트의 앞쪽에는 우선순위를 부여한 몇몇 숫자를 위치시켜야 하는 상황이다. 이러한 패턴은 사용자 인터페이스를 표시하면서 중요한 메시지나 예외적인 이벤트를 다른 것보다 우선해 표시하고 싶을 때 유용하다.
이를 해결하기 위해 리스트의 sort method 에 key 인자로 도우미 함수를 전달하면 된다. 예시의 도우미 함수 sort_priorirt
는 주어진 원소가 중요한 숫자 그룹에 들어 있는지 검사해서 정렬 기준값을 적절히 조정해준다.
def sort_priority(values, group):
def helper(x):
if x in group:
return (0, x)
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]
이 함수가 작동하는 방식에는 중요한 세 가지가 있다.
helper
함수가 외부의 group
인자에 접근할 수 있다.위 함수에 더불어, 리스트에 우선순위가 높은 원소가 존재하는지의 여부를 반환하게 만들어서 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
found = sort_priority2(numbers, group)
print('발견:', found)
print(numbers)
# 발견: False
# [2, 3, 5, 7, 1, 4, 6, 8]
정렬 결과는 맞지만, found 값이 True이여야 할 것 같은데 이상하다. 왜 이럴까?
파이썬은, 식 안에서 변수를 참조할 때 다음 순서로 영역을 뒤진다.
해당하는 변수가 이 네 가지 영역에 없으면 NameError 예외가 발생한다.
하지만 변수에 값을 대입하는 것은 다른 방식으로 작동한다. 변수가 현재 영역에 이미 정의돼 있다면 그 변수의 값만 새로운 값으로 바뀐다. 하지만 변수가 현재 영역에 정의돼 있지 않다면 파이썬은 변수 대입을 변수 정의로 취급한다. 결정적으로 이렇게 새로 정의된 변수의 영역은 해당 대입문이나 식이 들어 있던 함수가 딘다.
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 문을 사용하면 클로저 밖으로 데이터를 끌어낼 수 있다. 아래 예제 코드를 보자
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
found = sort_priority2(numbers, group)
print('발견:', found)
print(numbers)
# 발견: True
# [2, 3, 5, 7, 1, 4, 6, 8]
클래스 형태로 구현할 수도 있다. 이는 길이는 조금 더 길 수 있지만, 훨씬 더 이해하기 편하다.
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)
return (1, x)
sorter = Sorter(group)
numbers.sort(key=sorter)
assert sorter.found is True
a = 10
def f0():
a = 0
def f1():
a = 1
def f2():
a = 2
print("f2", a)
f2()
print("f1", a)
f1()
print("f0", a)
f0()
print("global", a)
위 코드에서 global, nonlocal 선언을 적절히 넣어보면 global, nonlocal이 어떤 역할을 하는지 이해할 수 있다.
global 문은 해당 변수를 전역 변수로 취급하라는 의미이고, nonlocal 문은 비지역 변수, 말 그대로 해당 영역 바로 밖의 변수로 취급하라는 의미로 보인다.