본문 바로가기
Python

[Python] scope와 키워드 global, nonlocal

by ds31x 2023. 7. 15.

Python에서 scope는 namespace와 밀접하게 관련이 있는 개념이며, 이와 관련된 주요 키워드가 nonlocalglobal이 있음.

https://dsaint31.tistory.com/entry/Basic-namespace-frame-and-context

 

[Basic] namespace, frame, and context

Namespace 프로그래밍 등에서 나오는 namespace는 일종의 추상적인 개념 변수와 함수, 클래스 등이 정의되고 사용될 수 있는 범위(scope)를 지칭함. (때문에 scope 란 용어와 자주 같이 사용됨) variable(변

dsaint31.tistory.com

 

global의 경우, 개인적으로 많이 사용하지 않도록 권하는 키워드이고,

nonlocal의 경우 closure 등에서 사용되는데 그 외에 사용되는 경우가 많지 않다보니 (기본 문법만 이해해줘도 고마운 경우가 많다보니...) 생각보다 많이 다루는 개념은 아니다.

 

하지만 scope 자체는 매우 중요한 개념이라 Python이 아니더라도 제대로 프로그래밍을 하려면 반드시 이해해야한다. 매번 조각코드로 갖다 붙여사용하는 스크립트 키드가 되기 싫다면...


Scope

scope는 특정 name이 유효한 범위를 가르키는데 이는 namespace와 관련이 깊어서 namespace로 바꿔애기해도 많은 부분에서 통용된다.

 

scope가 특정 name이 유효한 범위를 가르킨다는 건, scope가 특정 name이 가르키는 실제 object가 무엇이냐를 결정하는 규칙임을 의미한다. 해당 규칙의 종류로는 lexical scope (or static scope)dynamic scope가 있는데, Python이나 C, Java, JavaScript 등의 대중적인 언어의 대부분이 lexical scope를 따른다.

lexical scope도 크게
block scope (C, C++등)와 functoin scope (Python, JavaScript등)로 나뉘지만
여기서는 Python이 사용하는 function scope 중심으로 애기한다.

 

lexical scope는 variable이나 function 등이 source code 내에서 선언 (혹은 정의)된 위치에 따라 scope (or namespace)가 결정되는 방식이다.

  • 특정 변수가 global function들과 같은 level에서 선언이 되면(Python의 경우 최초 assignment이 이루어지면) 해당 변수는 global scope가 됨.

dynamic scope는
call 및 실행 등에 따라 scope가 만들어지며
Perl과 bash 등에서 사용됨.

 

쉽게 살펴보기 위해 Python에서의 변수를 중심으로 scope를 확인하면 다음 코드를 참고하라.

# Global Scope!, g_func 와 nested_func에서 모두 접근이 가능하며 global 키워드가 적용됨.
def g_func():
    # Local Scope!, g_func의 function namespace로 g_func 의 관점에선 local이라고 불림.
    
    # Non-local Scope!, nested_func의 관점에서는 
    # nonlocal 키워드가 적용되는 Non-local scope임 (global과는 구분이 된다.).
    def nested_func():
        # nested_func 의 관점에선 local임.

자신과 같은 scope 또는 바깥쪽(현 scope를 포함하는) scope의 변수들에는 접근이 가능하지만,

안쪽에 포함된 scope에서 선언된 변수는 보이지가 않아서 접근할 수 없음.

 

단, 같은 scope와 바깥쪽 scope에 똑같은 name을 가지는 변수들이 동시에 있을 경우엔, 같은 scope의 변수가 접근시 우선권을 가진다.
(장동건이라는 이름으로 특정 배우를 지칭하지만, 만약 우리반에 장동건이라는 이름의 학생이 있을 경우에 우리반 안에서는 장동건이라는 이름이 해당 학생을 지칭하게되는 것과 유사하다.)

 

앞서 코드에 실제 변수를 선언하여 접근 가능성을 살펴본 예제가 다음과 같다.

# Global Scope!, g_func 와 nested_func에서 모두 접근이 가능하며 global 키워드가 적용됨.
g_x = 'global'

def g_func():
    # Local Scope!, g_func의 function namespace로 g_func 의 관점에선 local이라고 불림.
    l_x = 'local of g_func'

    # Non-local Scope!, nested_func의 관점에서는 
    # nonlocal 키워드가 적용되는 Non-local scope임 (global과는 구분이 된다.).
    non_local_x = 'non-local'

    print('global_variable : ',g_x)
    print('local_variables of g_func :', l_x, non_local_x)

    def nested_func():
        # nested_func 의 관점에선 local임.
        l_x = 'local of nested func'
        l_y = 'y : local of nested func'
        print('global_variable : ',g_x)
        print('nonlocal of nested_func :', non_local_x)
        print('local_variables of nested_func :', l_x)
    
    nested_func()
    print(l_y) # NameError 발생. l_y 는 이 곳에서는 정의가 되지 않음. 
g_func()
print(non_local_x) # NameError 발생. non_local_x 는 이 곳에서는 정의가 되지 않음.

scope는 namespace이기 때문에

같은 namespace에서만 동일한 이름이 아니라면 name collision이 발생하지 않음.


global 키워드

gloabl 키워드를 통해 전역변수를 사용하도록 지정해두면,

이후 variable은 global namespace(or global context)에 존재하게 된다.

 

다음 코드를 살펴보자.

g_x = 10

def func (i):
    tmp = i + g_x
    g_x = 20
    print(tmp)

func(77)
print(g_x)
  • g_x = 20으로 할당하는 line 이 없을 경우엔 아무 문제가 없지만.
  • 해당 line이 추가될 경우, tmp = i + g_x에서 에러가 발생함.
  • 이 코드처럼 특정 function내에서 global scope의 variable에 읽고 쓰기가 동시에 하려면, global 키워드가 필요함.
  • 또는 func안에서 g_x를 assignment하는 g_x = 20tmp = i + g_x보다 앞에서 사용된다면, local scope에 g_x라는 새로운 name이 생성된 것으로 global scope g_x와 구분이 되는 다른 local variable임.

다음 코드는 global 키워드를 통해 명확하게 global scope의 변수임을 지정해주는 방법을 보여준다.

g_x = 10

def func (i):
    global g_x    # g_x 라는 name이 global scope의 것임을 명시적으로 지정함. 이후로는 global variable g_x가 사용됨.
    tmp = i + g_x
    g_x = 20
    print(tmp)

func(11)
print(g_x) # 20이 출력된다. global scope의 g_x의 값이 20이므로.

 

위와 비슷해 보이지만, 아래의 코드는 g_x라는 이름만 같을 뿐 각기 다른 namespace에 존재함.

g_x = 10

def func (i):
    g_x = 20        # local variable g_x임. global variable g_x와 이름만 같을 뿐, 다른 variable임.
    tmp = i + g_x

    print(tmp)

func(11)
print(g_x) # func에서 local variable g_x는 20이나, global variable g_x는 그대로 10임.
  • 이와 같이 이름이 겹쳐지는 것을 variable shadowing 이라고 부르며, 가독성을 떨어뜨리기 때문에 가급적 사용하지 않는게 좋다 (그런데 시험에선 단골 주제이다. ==;;).
  • 단, 개발할 경우 사용을 피하는 것이지, 해당 경우의 동작을 명확히 이해해야 한다.

global 키워드는 global variable(전역변수) 남용으로 이어지기 쉽다.

전역변수 남용은 C언어를 비롯하여 대부분의 프로그래밍 언어에서 modularity를 떨어뜨리고 가독성을 해치기 때문에 가급적 사용하지 않기를 권하는 방식이다.


nonlocal 키워드

global키워드와 유사하지만,

nested function에서 자신을 둘러싸고 있는 enclosed function의 scope의 변수임을

확실히 가르키기 위해서 사용된다.

 

closure란
enclosed scope (= non-local scope)에 속하는 variable들을
기억(=상태를 기억한다고도 표기)하고 있는 nested function임.  
non-local scope가 보통 lexical scope로 결정되므로
lexcial environment의 상태를 기억한다고도 표현한다.

 

앞서 살펴봤을 때, global scope와 g_funclocal scope가 nested_func의 바깥쪽에 있는 scope들이 되므로, 둘 중 어느 scope의 variable인지를 명확히 하기 위해서 globalnonlocal 키워드가 사용된다.

 

다음은 특정 closure가 몇번 호출되었는지를 non-local variable로 체크하는 것을 구현한 코드이다.

def counter():
    call_cnt = 0

    def _closure_func():
        nonlocal call_cnt
        call_cnt += 1
        print(f'호출 횟수: {call_cnt}') 
        return call_cnt

    return _closure_func

c_func = counter()

c_func()
c_func()
c_func()
c_func()

 

nonlocal의 경우, closure와 같은 디자인패턴을 사용하는 경우에 많이 사용되지만,

일반적으로는 가독성이 좋지 못한 편이기 때문에 특정 디자인패턴을 구현하는 경우 외에서는 많이 사용하는 것을 권하지 않음.


더 읽어보면 좋은 자료

https://ds31x.tistory.com/122

 

[Python] Collections

collections.abc 2024.04.15 - [분류 전체보기] - [Python] collections.abc [Python] collections.abc 2023.10.06 - [Pages] - [Python] Collections collections.abc 와 Python의 DataStructure. Python의 Data structure는 실제적으로 collections.abc 라

ds31x.tistory.com

 

https://shoark7.github.io/programming/python/closure-in-python

 

Python의 Closure에 대해 알아보자

Python에서 유용한 Closure에 대해 살펴봅니다.

shoark7.github.io

https://www.daleseo.com/python-global-nonlocal/

 

파이썬의 global과 nonlocal 키워드 사용법

Engineering Blog by Dale Seo

www.daleseo.com

 


 

728x90

'Python' 카테고리의 다른 글

[Python] List's methods  (0) 2023.07.17
[Python] Closure  (0) 2023.07.15
[Python] Nested Function  (0) 2023.07.15
[Python] first-class object (일급객체)  (0) 2023.07.15
[Python] Callback function  (0) 2023.07.13