Python: 변수와 스코프 (variable&scope)

dev-swd·2020년 10월 26일
1

python

목록 보기
10/23
post-thumbnail

1. namespace 에 따른 변수 접근

파이썬 초보에서 중수로 넘어가기 위한 관문인 변수와 스코프에 대해서 정리를 해보았다.
이걸 제대로 이해한다고 해도 아직 중수라고 하긴엔 무리가 있을 듯 싶지만..

변수와 스코프의 개념에 대한 자료를 볼 때 아래와 같은 코드를 보게된다.

# test.py
x = 1
def test():
	x = 2

test()
print(x)

위의 코드를 실행해보면 x 는 당연히 2가 나와야 한다고 생각할 수 있지만, 결과는 1이다.
이유는 파이썬이 변수를 찾는 과정에 있고, 그 과정에서 namespace 라는 개념이 있기 때문이다.
x 가 1이 되는 이유를 말하자면,

test() 함수 내부에 있는 xtest()라는 함수가 가지고 있는 지역 네임스페이스(local namespace)에 있기 때문에, 전역 변수 x 에 접근하지 못하기 때문이다.

즉, 전역에 선언된 변수 x 는 test.py 라는 모듈에 속해있다.
test() 함수 안에 선언된 xtest() 함수 안에 속해있는 지역변수이다.


2. 파이썬이 변수를 찾는 순서

  1. 지역 네임스페이스(local namespace) 의 공간에 선언된 변수
  2. 전역 네임스페이스(global namespace) 의 공간에 선언된 변수
  3. 빌트인 네임스페이스(built-in namespace) 의 공간에 있는 변수

아래 코드의 실행 결과를 보면 30, 20, 10 이다.

x = 10

def test():
    x = 20
    def test2():
        x = 30
        print(x)
    test2()
    print(x)

test()
print(x)
  1. 전역 변수 x = 10, test() 함수가 선언되어있고, 단순 선언이기 때문에 건너 뛴다.
  2. test() 함수가 실행되서 내부 처리가 시작된다.
  3. test() 함수 내부에서 x = 20, test2() 함수 단순 선언이기 때문에 건너 뛴다.
  4. test2() 함수 실행되고 내부 처리 시작된다.
  5. x = 30 선언 건너뛰고, print(x) 실행. 변수 x 를 찾는다. test2() 라는 함수의 지역 네임스페이스에서 변수 x 를 찾는다. (만약 없다면 상위 스코프로 넘어간다)
    변수 x = 30 이 있기 때문에 30을 출력한다.
  6. print(x) 가 실행되고, 변수 x 를 test() 함수의 네임 스페이스에서 찾는다. (만약 없다면 상위 스코프 - 여기서는 전역 스코프 - 로 변수 x 를 찾으러 가는데, 있기 때문에 20 을 출력한다.
  7. 전역 스코프에서 print(x) 가 호출되고, 전역 스코프인 10을 출력한다.

파이썬이 변수를 찾는 순서는 이렇게 지역 네임스페이스의 공간 > 전역 네임스페이스의 공간 > 빌트인 네임스페이스 가 된다.

빌트인 네임스페이스 (built-in namespace)
내장 변수 혹은 함수로 int, str 등이 있는데 일단 여기서는 건너 뛰기로 한다.


3. 섀도우잉 (Shadowing)

섀도우잉이란 특정한 스코프 내에서 선언된 이름이 그 외부 스코프와 중첩되는 것 을 말한다. 네임 마스킹이라고도 한다.
로컬 이름 공간이 우선적으로 참조되며 전역 변수는 엑세스되지 않는다.
간단하게 예를 들어보자.

x = 10

def test(x):
    x = 20
    def test2():
        x = 30
        print(x)
    test2()
    print(x)

test()
print(x)

위의 코드에서 전역 변수로 선언된 x 를 test() 함수의 파라미터로 넘겼을 때, 함수 내부에서 파라미터 x 가 사용되지 못한다. 특정한 스코프 test() 에서 사용하고 있는 x 라는 변수의 이름이 전역 변수의 이름과 중첩되기 때문이다.

  1. 현재 함수의 이름 공간에서 로컬 변수 이름을 찾는다. 등록된 이름이 있다면 이 이름을 사용한다.
  2. 현재 이름공간에서 해당 이름이 발견되지 않았다면 상위 이름 공간을 찾아본다. 만약 중첩된 함수라면 상위 함수의 로컬 이름 공간 찾아본다. 이런 식으로 이름이 발견될 때까지 상위로 올라가서 전역 이름 공간을 찾는다.
  3. 모듈의 전역 이름 공간에서 발견되지 않는 이름은 내장 이름 공간에서 찾아본다.
  4. 이 과정에서 최초로 발견된 스코프의 이름을 사용하며, 이름이 발견되지 않았다면 NameError 예외가 발생한다.

global, nonlocal

전역 변수, 혹은 지역 변수를 특정한 스코프 내에서 사용해야 할 때가 있는데, 이 때 globalnonlocal 키워드를 사용한다.

  • global : 전역 네임스페이스에 선언된 변수를 참조하겠다는 키워드
  • nonlocal : 현재 네임스페이스에 선언된 변수가 nonlocal 하다는 키워드 (상위 스코프에서 찾아주세요 라고 하는 키워드)

테스트1

# 테스트 1 
x = 10

def test():
    nonlocal x # 여기서 에러. x 는 글로벌 전역이기 때문에 nonlocal 과 맞지 않음.
    x = 20
    def test2():
        nonlocal x
        x = 30
        print(x)
    test2()
    print(x)

test()
print(x)

테스트2

x = 10

def test():
    global x 
    x = 20
    def test2():
        nonlocal x 
        # 여기서 에러. x 를 nonlocal 로 선언해서 상위 스코프의 x 를 찾는데
        # 찾고보니 x 가 global 변수임
        x = 30
        print(x)
    test2()
    print(x)

test()
print(x)

테스트3

x = 10

def test():
    global x
    x = 20
    def test2():
        global x
        x = 30
        print(x)
    test2()
    print(x)

test()
print(x)

# 실행 결과
30
30
30

변수 참조를 위한 규칙

  1. 함수 내에서 전역 변수를 업데이트 하지 말 것. (global 키워드는 사용하지 않는다고 생각하자)
  2. 함수 외부의 값을 제어해야 할 필요가 있다면, 직접 변경하지 말고 리턴 을 통해서 재할당 하는 식으로 한다.

가급적이면 전역 변수를 내부에서 참조하지 않을 것을 권장한다. 전역 변수를 직접 읽기 보단 인자값으로 받아서 처리하게 되면 함수의 내부는 상위 스코프와 암시적인 영향을 주고 받지 않는 순수한 함수가 된다.


참조 링크

profile
개발을 취미로 할 수 있는 그 때 까지

0개의 댓글