Python에서 scope는
- namespace와 밀접하게 관련이 있는 개념이며,
- 이와 관련된 주요 키워드가
nonlocal
과global
이 있음.
https://dsaint31.tistory.com/entry/Basic-namespace-frame-and-context
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++등)와 function scope (Python, JavaScript등)로 나뉘지만
여기서는 Python이 사용하는 function scope 중심으로 애기한다.
Lexical scope는
- variable이나 function 등이 source code 내에서 선언 (혹은 정의)된 위치에 따라
- scope (or namespace)가 결정되는 방식이다.
특정 변수가
- global function들과 같은 level에서 선언이 되면(Python의 경우 최초 assignment이 이루어지면)
- level이 같다는 애기는 indent level이 같음을 의미.
- 해당 변수는 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 # Error occured
g_x = 20 # assignment와 read가 동시에 안됨: global 키워드 필요.
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 = 20
이tmp = i + g_x
보다 앞에서 사용된다면, local scope에g_x
라는 새로운 name이 생성된 것으로 global scopeg_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_func
의 local scope가 nested_func
의 바깥쪽에 있는 scope들이 되므로,
둘 중 어느 scope의 variable인지를 명확히 하기 위해서 global
과 nonlocal
키워드가 사용된다.
다음은 특정 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와 같은 디자인패턴을 사용하는 경우에 많이 사용되지만,
일반적으로는 가독성이 좋지 못한 편이기 때문에
특정 디자인패턴을 구현하는 경우 외에서는 많이 사용하는 것을 권하지 않음.
더 읽어보면 좋은 자료
2023.07.15 - [Python] - [Python] Closure
2023.07.15 - [Python] - [Python] Nested Function
2023.10.06 - [Pages] - [Python] function 과 Scope, Name Space 정리
https://shoark7.github.io/programming/python/closure-in-python
https://www.daleseo.com/python-global-nonlocal/
'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 |