변수의 scope 개념이 확실하지 않아서
함수를 중첩하여 사용할 때
어떤 경우엔 부모 함수의 변수를 그냥 쓸 수 있고
어떤 경우엔 그냥 쓰면 에러가 나서 왜 그런지 찾아보았다.
mutable과 immutable을 구분하는 것이 핵심이었다.
mutable 예를들어 list는 값을 추가하거나 삭제하더라도 참조 ID는 유지되기 때문에 자식 함수에서 부모 함수의 mutable 변수를 접근하고 업데이트 할 수 있다.
immutable 변수는 그렇지 못하다. 새롭게 할당하게 되면 연결이 끊어진다. 부모 함수의 변수를 새로 할당하려면 nonlocal 선언을 해야한다. 아, 물론 mutable 변수도 업데이트가 아닌 새롭게 할당하면 마찬가지이다.
그리고 nonlocal과 gloabal은 다르다. nonlocal은 바로 위 부모 함수를 범위로 한다.
아래 내용은 GPT의 도움을 받아 정리한 내용이다.
# 중첩 함수에서 mutable(가변)과 immutable(불변)의 차이가 있는 이유:
# 1. 스코프 규칙: 중첩 함수에서 immutable 변수는 재할당 시 새로운 로컬 변수가 생성되며, 부모 변수와의 연결이 끊어짐.
# 이를 해결하려면 `nonlocal` 키워드를 사용해야 함.
# 2. 참조 방식 차이: mutable 객체는 함수 내부에서 직접 수정이 가능하며, 부모 함수의 변수에 영향을 미침.
# 하지만 immutable 객체는 수정이 불가능하고 재할당해야 함.
# 3. 메모리 참조: mutable 객체는 부모 함수의 메모리 주소를 공유하므로 내부 요소 수정이 가능하지만,
# immutable 객체는 새로운 객체가 생성되므로 원래 변수가 변경되지 않음.
def outer_function():
# immutable 변수 (불변 객체)
num = 10 # 정수 (immutable)
text = "hello" # 문자열 (immutable)
# mutable 변수 (가변 객체)
lst = [1, 2, 3] # 리스트 (mutable)
dct = {'a': 1} # 딕셔너리 (mutable)
def inner_function():
# 불변 객체 사용 시
print("Before changing num:", num) # Before changing num: 10
print("Before changing text:", text) # Before changing text: hello
# num = num + 1 # 주석 해제 시 UnboundLocalError 발생
# text = text + " world" # 주석 해제 시 UnboundLocalError 발생
nonlocal num # 부모 함수의 불변 변수 재할당 시 필요
num = num + 1 # 이제 정상 작동
nonlocal text
text = text + " world" # 문자열 재할당 가능
print("After changing num:", num) # After changing num: 11
print("After changing text:", text) # After changing text: hello world
# 가변 객체 사용 시
print("Before modifying lst:", lst) # Before modifying lst: [1, 2, 3]
print("Before modifying dct:", dct) # Before modifying dct: {'a': 1}
lst.append(4) # 부모 함수의 lst에 영향을 줌
dct['b'] = 2 # 부모 함수의 dct에 영향을 줌
print("After modifying lst:", lst) # After modifying lst: [1, 2, 3, 4]
print("After modifying dct:", dct) # After modifying dct: {'a': 1, 'b': 2}
inner_function()
print("Final num:", num) # Final num: 11
print("Final text:", text) # Final text: hello world
print("Final lst:", lst) # Final lst: [1, 2, 3, 4]
print("Final dct:", dct) # Final dct: {'a': 1, 'b': 2}
outer_function()
nonlocal과 gloabal# nonlocal과 global의 차이점
# 1. nonlocal:
# - 중첩 함수에서 부모 함수의 변수를 수정할 때 사용.
# - 가장 가까운 부모 함수의 변수를 수정하며, 상위 함수에 해당 변수가 없으면 오류 발생.
# - 함수 내부에서 immutable 객체(숫자, 문자열 등)를 재할당하려면 반드시 필요.
#
# 2. global:
# - 함수 내부에서 전역 변수를 수정할 때 사용.
# - 전역 범위의 변수를 직접 수정하며, 존재하지 않으면 새로운 전역 변수를 생성.
# 전역 변수
x = 100 # global 변수
def outer():
x = 50 # 부모(outer) 스코프의 변수
def inner():
nonlocal x # 가장 가까운 부모 함수(outer)의 x를 수정
x = 20 # outer()의 x가 변경됨
print("Inner x:", x) # 출력: Inner x: 20
global y # 전역 변수를 선언
y = 30 # 새로운 전역 변수 y 생성
print("Inner y:", y) # 출력: Inner y: 30
inner()
print("Outer x:", x) # 출력: Outer x: 20 (nonlocal을 사용하여 변경됨)
outer()
print("Global x:", x) # 출력: Global x: 100 (전역 x는 변경되지 않음)
print("Global y:", y) # 출력: Global y: 30 (global로 인해 전역 변수가 생성됨)
# nonlocal 사용 예제 (immutable 변수)
def counter():
count = 0 # 부모 스코프 변수
def increment():
nonlocal count
count += 1 # nonlocal 없으면 에러 발생 (UnboundLocalError)
print("Count:", count) # 출력: Count: 1 (첫 번째 호출), Count: 2 (두 번째 호출)
increment() # 첫 번째 호출 -> 출력: Count: 1
increment() # 두 번째 호출 -> 출력: Count: 2
counter()
# global 사용 예제 (전역 변수 수정)
global_var = 10
def modify_global():
global global_var
global_var += 5 # 전역 변수 변경
print("Global_var:", global_var) # 출력: Global_var: 15
modify_global()
# 정리:
# - `nonlocal`은 가장 가까운 부모 함수의 변수를 수정할 때 사용.
# - `global`은 전역 변수를 수정하거나 새로 생성할 때 사용.
# - 불변(immutable) 객체는 nonlocal/global이 필요하며, 가변 객체는 참조를 통해 수정 가능.
| 구분 | nonlocal | global |
|---|---|---|
| 적용 범위 | 가장 가까운 부모 함수의 변수 | 전역 스코프의 변수 |
| 변수 위치 | 함수 내부의 중첩된 범위 | 모듈 전체(스크립트의 전역) |
| 새로운 변수 | 상위 스코프에 없으면 에러 발생 | 전역에 없으면 새로 생성됨 |
| 사용 예시 | 중첩 함수에서 부모 변수 조작 | 전역 변수 수정 필요 시 사용 |