프로그래머스 70129. 이진 변환 반복하기 문제를 풀 때 재귀함수를 사용하다가 nonlocal 키워드를 사용하며 기록해두고 싶어 포스팅했습니다.
global
과 nonlocal
키워드에 대해서 이해하려면 먼저 변수의 범위(scope)
에 대한 개념을 간단히 짚고 넘어가야할 것 같습니다.
비단 파이썬 뿐만 아니라 대부분의 프로그래밍 언어에서 변수의 범위라는 것은 해당 변수를 어디에서 선언하느냐에 따라서 결정이 됩니다. 아주 단순하게 두 구역으로 나누면 함수 외부를 전역(global/module)
범위라고 하고, 함수 내부를 지역(local/function)
범위라고 합니다. 또한 함수를 중첩했을 때 외부 함수와 내부 함수의 사이에서 생겨나는 비지역(nonlocal/enclosing)
범위라는 것도 있습니다.
# outer(), inner() 함수 입장에서 전역(global) 범위
def outer():
# outer() 함수 입장에서 지역(local) 범위
# inner() 함수 입장에서 비지역(nonlocal) 범위
def inner():
# inner 함수 입장에서 지역(local) 범위
같은 범위 내에서는 자유롭게 변수에 접근이 가능하지만 다른 범위에서 선언된 변수에 접근할 때는 정해진 제약을 따르게 되는데요. 기본적으로 바깥 쪽 범위 내에서 선언된 변수를 안 쪽 범위에서는 접근할 수 있지만, 반대로 안 쪽 범위 내에서 선언된 변수를 바깥 쪽 범위에서 접근하는 것은 불가능합니다. 예를 들어, outer()
함수 밖에서 선언한 전역 변수는 outer()
함수 내부와 inner()
함수 내부에서 접근이 가능합니다. 하지만 outer()
함수 안에서 안에서는 선언한 outer()
함수 외부에서는 접근할 수 없으며, outer()
함수와 inner()
함수 안에서는 접근 가능이 가능합니다.
global_var = "전역 변수"
print(global_var) # 가능
def outer():
nonlocal_var = "비전역 변수"
print(global_var) # 가능
print(nonlocal_var) # 가능
def inner():
local_var = "지역 변수"
print(global_var) # 가능
print(nonlocal_var) # 가능
print(local_var) # 가능
print(local_var) # 불가능 (NameError: name 'local_var' is not defined)
print(nonlocal_var) # 불가능 (NameError: name 'nonlocal_var' is not defined)
print(local_var) # 불가능 (NameError: name 'local_var' is not defined)
변수의 범위의 다른 중요한 특성은 서로 다른 범위에서는 변수 이름 충돌이 발생하지 않으며 안 쪽 범위에서 바깥 쪽 범위에서 선언된 변수와 똑같은 이름의 변수를 생성할 수 있다는 것입니다. 예를 들어, 아래에 선언된 3개의 var
변수는 이름만 같을 뿐 서로 다른 값을 저장할 수 있는 엄연히 다른 변수입니다.
var = "전역 변수"
print(var)
def outer():
var = "비지역 변수"
print(var)
def inner():
var = "지역 변수"
print(var)
inner()
outer()
## 출력 결과
전역 변수
비지역 변수
지역 변수
함수 안에서 변수 앞에 global
키워드를 붙여주면 해당 변수는 함수 내에서 값을 변경하더라도 새로운 지역 변수가 되지 않고 함수 밖에서 이미 선언된 전역 변수를 가리키게 됩니다.
num = 0
def change_num():
global num
num = 100
print(num)
change_num()
print(num)
## 출력 결과
100
100
nonlocal
키워드도 global
키워드와 같이 동일한 이름의 새로운 변수가 생성되는 것을 방지하기 위해서 사용됩니다. 이 두 키워드의 차이점은 global
키워드는 일반 함수 내에서 전역(global/module) 변수를 대상으로 사용하는 반면에 nonlocal
키워드는 중첩 함수 내에서 비지역(nonlocal/closing) 변수를 대상으로 사용한다는 것입니다.
def print_num():
num = 0
def change_num():
nonlocal num
num = 100
print(num)
change_num()
print(num)
print_num()
## 출력 결과
100
100
변수 앞에 nonlocal
키워드를 붙여주면 해당 변수는 중첩 함수 내에서 값을 변경하더라도 새로운 지역 변수가 되지 않고 함수 밖에서 이미 선언된 비전역 변수를 가리키게 됩니다.
def solution(s):
ans = []
total = 0 # 이진 변환의 횟수
ln = 0 # 길이 담아줄 변수
cnt = 0 # 제거된 모든 0의 개수 담을 변수
def replay(st):
nonlocal cnt, total
if st == "1": # 최종 탈출 조건
return
for x in st: # 제거할 0의 개수 세기
if x == "0":
cnt += 1
st = st.replace("0","") # 0 제거
st = bin(len(st))[2:] # 길이 담아주기
total += 1 # 이진 변환 횟수 증가
ln = len(st)
replay(st) # 재귀
replay(s)
ans.append(total)
ans.append(cnt)
return ans