A namespace is a mapping from names to objects.
파이썬의 모든 것은 객체로 되어있다. 변수나 함수를 선언하면 객체가 name(변수이름, 함수이름)에 매핑된다. 이러한 매핑 또는 매핑 테이블을 namespace라고 한다.
# 변수, 함수들 선언
a = 3
b = 3
c = [1, 2, 3]
d = c
def e():
pass
f = e
위와 같이 변수, 함수들을 선언했을 때, 아래의 방법들로 매핑을 직접 확인할 수 있다.
id()를 이용한 매핑 확인id()함수는 객체를 인자로 받아 객체의 수명동안 유일하고 변하지 않는 것이 보장된 고유값을 정수로 반환해준다. 즉 id(객체)의 값이 같으면 같은 객체라는 말이다.
# 각각 같은 값이 출력되는 것으로 매핑을 확인할 수 있다
print(id(a), id(b))
print(id(c), id(d))
print(id(e), id(f))
globals() 또는 locals()를 통한 매핑 확인globals()와 locals()는 각각 함수가 선언된 scope의 전역 심볼 테이블, 지역 심볼 테이블을 리턴한다.
심볼 테이블(Symbol Table) 🤔
# 출력된 딕셔너리로 각 name이 어떤 object를 매핑하고 있는지 볼 수 있다
print(globals())
print(locals())
namespace는 built-in namespace, global namespace, local namespace 세 종류로 나뉜다.

A scope is a textual region of a Python program where a namespace is directly accessible.
파이썬 프로그램에는 수많은 namespace들이 존재할 수 있고, 현재 scope에 따라 어떤 namespace에 접근할 수 있는지 여부가 결정된다.

scope는 위 그림과 같이 4개로 나뉜다.
namespace 종류에서 enclosed만 추가된 모양새이다. 파이썬에서는 함수안에 함수를 선언할 수 있기 때문에 이런 경우, 외부 함수의 scope와 내부 함수의 scope를 구분하기 위함이다.
scope는 계층적으로 분리되며 아래와 같은 규칙들을 가진다.
# 예시 1
def f1():
def f2():
print(f"local 'a' = {a}")
f2()
print(f"enclosed 'a' = {a}")
a = 10
f1()
print(f"global 'a' = {a}")
위의 예시 1에서 enclosed scope, local scope에서 global scope의 변수 a에 접근하고 있다.
# 예시 2
def f1():
a = 20
def f2():
a = 30
print(f"local 'a' = {a}")
f2()
print(f"enclosed 'a' = {a}")
a = 10
f1()
print(f"global 'a' = {a}")
위의 예시 2에서 global scope에서 변수 a를 할당한 뒤 enclosed scope, local scope에서도 새로 변수 a를 각각 할당하였지만, 상위 scope에서는 하위 scope에서 할당한 변수에 접근할 수 없다.
규칙 3에서 하위 scope에서 상위 scope의 이름들에 접근할 수 있다고 하였다. 여기서 접근은 읽기 전용 접근을 말한다. 즉, 별다른 처리가 없으면 상위 scope의 이름에 할당된 객체를 변경할 수 없다.
하지만 global, nonlocal statement를 활용하면 상위 scope의 이름에 할당된 객체를 변경 가능하다.
# global, nonlocal statement 사용 예시
def f1():
global a # global scope의 a 사용 가능
a += 1
b = "abc"
def f2():
global a
nonlocal b # enclosed scope의 b 사용 가능
a += 1
b += "d"
print(f"local 'a' = {a}")
print(f"local 'b' = {b}")
f2()
print(f"enclosed 'a' = {a}")
print(f"enclosed 'b' = {b}")
a = 1
f1()
print(f"global 'a' = {a}")
그런데 global, nonlocal이 어떤 케이스일 때 유용할지는 잘 모르겠다. 여태 안쓰고 코딩해왔기도 하고...
각 모듈이나 패키지, 함수, 메서드 별로 유니크한 namespace를 이용하면서 name들의 충돌을 피할 수 있고, 서로 다른 scope에서 같은 name을 사용하여 좋은 코드를 작성할 수 있다고 한다.
Why are namespaces such a great thing in Python? << 여기서 좀 더 상세한 내용을 볼 수 있다.
# 객체 변경하기
def f1():
li.append(4) # 여기서는 문제가 없는데
def f2():
num += 1 # 여기서 문제가 발생한다
num = 1
li = [1, 2, 3]
f1()
f2()
>>> UnboundLocalError: cannot access local variable 'num' where it is not associated with a value
위 예제에서 하위 scope에서 상위 scope의 변수 num, li의 값을 변경하려고 하고있다. li의 값을 변경하는 것은 되는데 num의 값을 변경하는 것은 안된다. 왜일까?
li는 되고 num은 안되는 이유 (mutable, immutable)global, nonlocal 파트에서 global, nonlocal statement 없이는 상위 scope의 이름에 할당된 객체는 수정할 수 없다고 하였다. 여기서 말하는 수정은 이름이 가리키는 객체가 다른 객체로 바꾸는 것을 말한다.
id()를 통해 객체 수정하기 예시의 행위들이 어떤 영향을 미치는지 알아보자
li = [1, 2, 3]
print(id(li))
li.append(4)
print(id(li)) # append 전과 id 값이 같다. 즉 append는 객체를 변경시키지 않는다.
num = 1
print(id(num))
num += 1
print(id(num)) # id 값이 달라졌다. 더하기 연산으로 인해 num에 '객체 1' 대신 '객체 2'가 할당된 것이다.
list는 mutable 객체로서 append()등을 통해 값을 변경시켜도 할당되었을 때 생성된 객체는 변하지 않는다. 객체의 형태만 변할 뿐 같은 객체인 것이다.
int는 immutable 객체로서 더하기 연산 등을 통해 값을 변경시키는 행위가 곧 할당되는 객체 자체를 바꾸는 것이 된다. 연산 후의 결과값 객체를 새로 할당하는 것이다.
# mutable, immutable 또다른 비교 예시
li = [1, 2, 3]
li2 = [1, 2, 3]
num = 3
num2 = 3
print(id(li), id(li2)) # 같은 값(?)을 가진 list를 할당했지만 id가 다르다
print(id(num), id(num2)) # 같은 정수면 id가 같다