Namespace, Scope

bong·2024년 6월 12일

Python

목록 보기
5/9

💡 Namespace

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) 🤔

  • 정의
  • namespace와 관계? : namespace라는 추상적인 개념이 딕셔너리 형태로 구현된것
    - 참조1, 참조2
# 출력된 딕셔너리로 각 name이 어떤 object를 매핑하고 있는지 볼 수 있다
print(globals())
print(locals())

Namespace 종류

namespace는 built-in namespace, global namespace, local namespace 세 종류로 나뉜다.

  • built-in namespace : 기본 내장 함수 및 기본 예외들의 이름들을 저장
  • global namespace : 모듈별로 존재. 모듈 전체에서 통용되는 이름들을 저장
  • local namespace : 함수, 메서드 별로 존재. 함수 내 지역변수 이름들을 저장

namespace


💡 Scope

A scope is a textual region of a Python program where a namespace is directly accessible.

파이썬 프로그램에는 수많은 namespace들이 존재할 수 있고, 현재 scope에 따라 어떤 namespace에 접근할 수 있는지 여부가 결정된다.

Scope 종류 (LEGB)

python legb

scope는 위 그림과 같이 4개로 나뉜다.

  • local scope : 함수 내에 할당(assign)된 이름들
  • enclosed scope : 중첩 함수에서 외부 함수에 할당된 이름들
  • global scope : 모듈 최상위 수준에 할당된 이름들
  • built-in scope : 표준 파이썬 built-in 이름들 (e.g.> open, import, print, return ...)

namespace 종류에서 enclosed만 추가된 모양새이다. 파이썬에서는 함수안에 함수를 선언할 수 있기 때문에 이런 경우, 외부 함수의 scope와 내부 함수의 scope를 구분하기 위함이다.

Scope 규칙

scope는 계층적으로 분리되며 아래와 같은 규칙들을 가진다.

  1. 순서대로 최상위 scope는 built-in scope, 최하위 scope는 local scope이다.
  2. 하위 scope에서는 항상 상위 scope의 이름들에 접근 가능하다.
  3. 상위 scope에서는 절대 하위 scope의 이름들에 접근할 수 없다.
  4. 이름이 겹칠때 별다른 명시적 선언이 없으면 하위 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에서 할당한 변수에 접근할 수 없다.

global, nonlocal

규칙 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, Scope 왜 필요할까

각 모듈이나 패키지, 함수, 메서드 별로 유니크한 namespace를 이용하면서 name들의 충돌을 피할 수 있고, 서로 다른 scope에서 같은 name을 사용하여 좋은 코드를 작성할 수 있다고 한다.
Why are namespaces such a great thing in Python? << 여기서 좀 더 상세한 내용을 볼 수 있다.

❓ 하위 scope에서 상위 scope의 변수 값을 변경할 수 없다?

# 객체 변경하기
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가 같다

📚 참조

0개의 댓글