0. Introduction
coroutin
을 공부하면서 global variable과 local variable 라는 것이 Scoping Rule과 연관이 되어있다는 것과,Stack
과Heap
이라는 데이터 임시 저장 자료 구조와 연관된 걸 확인하고 이에 대해 정리해보겠다.
1.LEGB rules(Scoping rules)
변수(variable)의 생존 범위(lifetime)에 관련된 규칙으로, 이 변수가 적용되는 범위를 말하는 것으로 이해했다.
Python scope 개념을 알아야 하는 이유:
- 신뢰성 있고, 유지보수성이 좋은 프로그램을 작성할 수 있다.
- name 충돌을 방지할 수 있고, 버그를 줄일 수 있다.
- 이와 관련된 tool인
Closure
에 대해 알 수 있다.
1.1 초기 Scope의 부재로 인한 문제
- 초기 프로그래밍은 global만 있었기 때문에, 변수를 수정해야할 때 모든 코드를 동시에 염두에 둬야했다. 그래서 이런 문제를 피하기 위해 scope을 사용했다.
- scope을 사용한 후, 프로그램의 어디에서든지 해당되는 scope에서 벗어나 있는 변수들에 함부로 접근할 수 없다. (out of scope)
- name들의 scope은 이 name들을 정의한 코드의 block scope과 동일하다. (in scope)
1.2 파이썬의 이름(name) 만들기
- 파이썬에서의 변수들에 값이 할당될 때, 즉 파이썬 names을 다음과 같은 방법들로 만들 때 변수들은 존재하게 된다.
- 변수(variable): 변수에 값을 할당하면, 변수는 만들어진다.
- function , classes: 예약어
def
,classes
를 사용하여 정의하면 이용할 수 있다. - modules:
import
하여 사용할 수 있고, as를 통해서 별칭으로 정의할 수 있다.
1.3 Reference operations과 Assignment operations의 차이
operations | Reference operations | Assignment operations |
---|---|---|
참조 / 할당 | name을 ‘참조’ 한다 | name을 ‘할당’ 한다 |
구체적인 의미 | name에 담겨진 value를 ‘단지 가져온다’ | name을 ‘새롭게’ 만들거나, ‘수정’ 한다 |
그리고, 할당한다는 건 특정 scope이 결정된다는 걸 말한다.
1.4 Python scope와 namespace의 관계
Namespace 설명은 [TIL] Python basic 14: class을 참고한다.
namespace는 각각 다른 지점에서 만들어지기 때문에, 다른
수명 시간(life time)
을 가지고 있다. 어느 위치에서 사용할 수 있는 지가 결정되어 있다. (from Python-course.eu: Namespaces)__dir__
을 통해서 할당된 name이 가지는 scope을 보여준다..__dict__.keys()
로 key value로 indexing하여 확인할 수 있다.
1.5 Python이 name을 찾는 규칙: LEGB rules
Scope | Local | Enclosed(or Non local) | Global(or Module) | Built-in |
---|---|---|---|---|
해당 이름 | function의 body (= code block)에 정의한 이름 | 지역 범위에 있는 중첩 함수를 둘러싼 범위에 있는 name | 최고 수준으로(Top level) 정의한 이름 | python 안에 내장된 예약어들 |
확인 범위 | name이 정의된 function의 코드에서만 확인 가능 | name이 정의된 function의 코드에서만 확인 가능 | 어느 코드에서든지 확인 가능 | 어느 코드에서든지 확인 가능 |
수명 시간(life time) | 정의한 function이 종료되면 소멸 | 중첩 함수가 있는 외부 function이 종료되면 소멸 | script가 끝날 때까지 지속 | 인터프리터가 시작되면 만들어져 소멸 X |
Local(or function) scope: 지역 범위
- Python function의 body 또는 code block 부분이 local scope이다.
- function이 호출될 때, 이 function에 대한 namespace가 생성된다.
- 지역 함수 내에 로직을 해결하는 값을 사용한다.
Enclosed(or nonlocal or free) scope: 자유 영역
중첩함수(nested functions)를 위해서만 존재하는 scope
enclosing function 안에서 정의된 names만 포함한다.
이
enclosing function의 코드에서만
enclosing scope에 있는 name을 확인할 수 있다.
Global(or module) scope: 전역 범위
- Python program, script, module 안에서 Top level의 scope으로, 이 scope에는 주로 변하지 않는 고정값을 사용한다.
Built-in scope: 내장 범위
- script를 run할 때마다 만들어지는 특별한 scope
python은 name의 존재유무를 확인하기 위해서, 여러 scope levels(or namespace)를 찾아보는데, 찾는 순서는 다음과 같다.
- local -> global -> global or module -> built-in namespace
|
|
- Inside inner_func(): local scope 이지만, number 변수는 존재하지 않는다.
- Inside outer_func(): the enclosing scope 이다. number 변수가 정의되지 않았다.
- In the module scope(or global scope): number 변수를 찾을 수 있어서 출력할 수 있다.
- 만약 number 변수가 the global scope에서 정의되지 않는다면, 파이썬은 built-in scope에서 찾을 것이다.
2. LEGB rules를 code로 이해해보기
2.1 LEGB rules: The Local Scope
그러면 code를 보면 이해해보자.
1 2 3 4 5 6 7 8 9 10 11 12
> def square(base): > result = base ** 2 > print(f'The square of {base} is : {result}') > square(10) The square of 10 is : 100 > result NameError: name 'result' is not defined > base NameError: name 'base' is not defined
위 코드에 대해 알아보자.
- square 함수를 호출 시, 파이썬은 base와 result를 포함하는 local scope을 만든다.
square(10)
으로 호출할 때, base에는 10을, result에는 100을 취한다.
- 다시 호출할 때는 첫 번째 호출 시 취한 값들은 기억하지 않는다.
result
와base
는square()
호출에 의해 만들어진local scope
에만 존재한다. 그래서 square fuction 종료 후 접근한다면NameError
을 얻는다.
- square 함수를 호출 시, 파이썬은 base와 result를 포함하는 local scope을 만든다.
그러면 추가로 square function의 local scope에 정의한 변수 이름과 동일한 변수 이름을 가진 function 정의해보자.
1 2 3 4 5 6
> def cube(base): > result = base ** 3 > print(f'The cube of {base} is : {result}') > cube(30) The cube of 30 is : 27000
- local scope에 동일한 변수이름을 사용했지만, 프로그램 충돌이 일어나지 않은 이유는
local scope
에만 살아있는local variable(지역 변수)
이기 때문에, 함수 실행이 끝나면 local scope에서 벗어나 지역 변수의 수명은 끝난다. - 이러한 장점 때문에, 디버깅과 수정이 쉽고 가독성이 좋아진다.
- local scope에 동일한 변수이름을 사용했지만, 프로그램 충돌이 일어나지 않은 이유는
2.2 LEGB rules: The Enclosing Scope (Nested Functions)
|
|
outer_func()
이 호출될 때,outer_func()
의 local scope이 만들어진다.- 이 scope은 동시에
inner_func()
의enclosing scope
이기도 한다.global scope
과local scope
둘 다 아니고, 이 사이에 놓여있는 특별한 scope을 의미.
- 또한,
inner_func()
은 enclosing function인 outer_func이 실행되는 동안에만 유지되는 일시적인 함수로서,outer_func()
의 code에서만inner_func()
을 찾을 수 있다.outer_func()
의 실행이 종료되면inner_func()
은 사라진다.
2.3 LEGB rules: Modules - The Global Scope
프로그램을 실행한 순간부터
global scope
에 있는 것이다.이
global scope
은module scope
이라고도 한다.그리고 현재 실행되는 script 또는 module이 entry point 역할을 한다면
__main__
module의 범위가 된다.namespace를 확인하기 위해서
dir()
을 사용할 때, 아무런 인자 없이 사용하면main global Python scope
에서 이용가능한 name list를 얻는다.프로그램 실행할 때 단 하나의
global Python scope
만이 존재한다. 그리고, 프로그램 실행이 끝나야 scope이 종료된다.local scope에 있는 global 변수를 참조할 수 있지만, local scope에서 global variable에 값을 할당할려고 하면 Error가 발생된다.
|
|
global variable(전역 변수)를 할당하려고 시도했지만, local scope(지역 범위) 내에서는 global variable에 값을 할당할 수 없다. (6번)
그래서 전역 변수가 지역 변수와 이어지지 않기 때문에, 할당 없이 지역 변수 ‘var’을 참조하여 Error가 발생했다.
그러면 이렇게 코드를 다시 짜보자.
1 2 3 4 5 6 7 8 9 10
# 전역 변수 > var = 100 > def func(): # 동일한 이름으로 새로운 지역 변수를 정의한다. > var = 200 # 전역 변수인 var를 참조하는 게 아닌, 지역 변수인 var를 참조한다. > print(var)
- 전역 변수를 업데이트한 것이 아닌 function의 body 부분에 있기 때문에 지역 변수를 새로 만든 것이다.
즉, 다음 사실을 알 수 있다.
Python은 global variable과 동일한 이름으로 function body에 선언해도 local variable로 인식한다.
2.4 Local variable 또는 global variable 찾아보기
locals()
과globals()
function을 통해서 지역 변수와 전역 변수를 출력해보자.
2.4.1 locals()
locals(): Return a dictionary containing the current scope’s local variables.
|
|
- var :
func()
함수를 호출하기 위해 인자로 넘겼던 ‘Hi’ 또한 지역변수임을 알 수 있다. - x : enclosing scope에 있는 것 또한 지역변수임을 확인할 수 있다.
- printer : outer function의 local scope에 정의했기 때문에 printer 또한 지역 변수로 확인할 수 있다.
2.4.2 globals()
- globals는 이 코드를 실행할 때 입력한 모든 전역 변수가 입력되기 때문에, 다음과 같이 하여 알아본다.
globals()
는 global 영역의 변수를 입력할 때 호출된다.
|
|
globals()
를 사용한 변수 자동화 생성: 지역 -> 전역 변수로 작성한다.
|
|
1.12 LEGB rules: Built-in scope
- Built-in scope은
builtins
라 불리는 표준 라이브러리 모듈로서 실행되는 특별한 파이썬 scope이다. - 파이썬은 LEGB 에서 마지막으로 built-in을 찾는다.
- 이 scope에서는 어느 모듈이든지 import할 필요 없이 names을 사용할 수 있다.
builtins
안에 있는 name들은 언제나 Python의 global scope에,__builtins__
로 담겨진다. 밑에 예제를 보자.
|
|
dir()
의 첫 호출에서__builtins__
을 확인할 수 있다.- 그리고
__builtins__
를 dir로 내부를 들여다보면, 파이썬의 built-in names의 전체 목록을 얻을 수 있다.
- 또 한 가지 특징은 global scope에서 어떠한 built-in names이든 오버라이드할 수 있다.
- 하지만 우연히 또는 부주의하게 이렇게 오버라이드가 된다면 위험하며, 버그를 찾기 어렵다. 그래서 이런 종류의 실행은 최대한 피하는 게 낫다.
|
|
2. Python memory structure
2.1 코드 영역
실행할 프로그램의 코드가 저장되는 영역 (text 영역이라고도 한다)
2.2 데이터 영역
프로그램의 global variable과 정적(static) variable가 저장되는 영역
- 프로그램이 시작하고, 끝날 때까지 메모리에 계속 남아 있는다.
2.3 Stack
- 데이터를 임시 저장할 때 사용하는 자료구조로, 데이터의 입력과 출력 순서는 후입선출(Last In First Out, LIFO) 방식
- 지역 변수 와 매개변수가 저장된다.
push(푸쉬)
: stack에 데이터를 넣는 작업pop(팝)
:stack에서 데이터를 꺼내는 작업- 데이터를 넣고 꺼내는 작업에서 윗 부분을
top
, 아랫 부분을bottom
이라 한다.
- stack 영역은
함수의 호출과 함께 생성
되고, 함수의 호출이 완료되면 소멸한다. 스택 프레임(stack frame)
: 스택 영역에 저장되는 함수의 호출 정보- 메모리의 높은 주소에서 낮은 주소의 방향으로 할당된다.
- 한계가 있어서, 한계를 초과하도록 삽입할 수 없다.
- Stack overflow: 함수는 변수를 저장하기 위해 stack을 만드는데, 만들어진 stack이 메모리 용량을 넘어서면
Stack overflow
가 발생한다.
2.2 Heap
사용자가 직접 관리할 수 있는 영역으로, 객체가 생성된다.
- 사용자에 의해 메모리 공간이 동적으로 할당되고, 해제된다.
- heap 영역은
런타임 시
에 크기가 결정된다 (메모리가 할당된다) - 메모리의 낮은 주소에서 높은 주소로 할당된다.