객체
를 참조
할 시에는 스택namespace
에 저장된 주소를 참조(레퍼런싱)한다.직접 힙을 참조하지 않는다.
할당
할 시에는 해당 scope, namespace
스택에 주소를 push
한다.a= 10
b= [4,5,6]
def func1():
a = 20
b = [1,2,3]
print(a) # 20
print(b) # [1,2,3]
print(a) # 10
print(b) # [4,5,6]
func1()
- 값 할당
a
에10
이라는 값을 할당하여 전역스택에 push한다.b
에리스트
가 저장된 주소를 전역스택에 push한다.함수
라는 객체가 저장된 주소를 전역스택에 push한다.- 변수에 변수를 할당할 때에는 스택에 저장된 값이 할당된다. (리스트의 경우 주소가 할당된다.)
- 함수 호출
print()
: 전역스택에 저장된a
와b
가 가장 가까우므로 전역스택에 값을 참조한다. (scope
개념)func1()
함수 : 함수가 호출 되면func
자체의namespace(스택)
이 새로 만들어진다.- 함수 내부에서 할당된 값을 함수스택에 push 한다.
- 함수는 호출이 종료되면 본인의 스택을
지운다
.Garbage Collector
func2()
의 경우 호출시 문제가 되지 않지만error()
의 경우 호출시 에러를 발생시킨다a
를 push
하지 않았으므로 전역변수를 참조
할 것 같지만,error()
을 호출하면 지역변수
를 참조하고 함수스택에는 a
가 없으므로 에러가 발생한다.a=10
b=[4,5,6]
def func2():
print(a) # 10
print(b) # [4,5,6]
print("test function2:", sep='\t')
dis.dis(func2)
# 호출시 에러
def error():
print(a)
print(b)
a = 30
b = [1,2,3]
print("error function: ", sep='\t')
dis.dis(error)
error()
UnboundLocalError: cannot access local variable 'a' where it is not associated with a value
func2
의 바이트 코드dis
라이브러리를 이용해 디스어셈블된 바이트 코드를 확인해볼 수 있다. https://docs.python.org/ko/3.8/library/dis.html#opcode-BUILD_TUPLE
LOAD_GLOBAL
즉 전역
space 에서 a
변수를 참조하려 한다.test function2:
28 0 RESUME 0
29 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_GLOBAL 2 (a)
22 CALL 1
30 POP_TOP
30 32 LOAD_GLOBAL 1 (NULL + print)
42 LOAD_GLOBAL 4 (b)
52 CALL 1
60 POP_TOP
62 RETURN_CONST 0 (None)
error
의 바이트 코드확인해보면 LOAD_FAST_CHECK
이번에는 전역 space가 아닌 local space에서 해당 변수를 참조한다.
a
를 초기화한적이 없기 때문에 오류를 발생시킨다.a
와 b
를 초기화하기 때문에 해당 변수가 함수스택 내부에 있을 것
이라고 판단한다.push
한적이 없기 때문에 함수스택을 참조할 때 에러를 발생시킨다.error function:
35 0 RESUME 0
36 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_FAST_CHECK 0 (a)
14 CALL 1
22 POP_TOP
37 24 LOAD_GLOBAL 1 (NULL + print)
34 LOAD_FAST_CHECK 1 (b)
36 CALL 1
44 POP_TOP
38 46 LOAD_CONST 1 (30)
48 STORE_FAST 0 (a)
39 50 BUILD_LIST 0
52 LOAD_CONST 2 ((1, 2, 3))
54 LIST_EXTEND 1
56 STORE_FAST 1 (b)
58 RETURN_CONST 0 (None)
global
키워드를 이용해 지역변수가 아닌 전역변수를 참조할 수 있다.a= 10
b= 30
c=[1,2,3,4]
def func3():
# 함수 내부에서 전역스택을 참조할 수 있다.
global a,b,k
print(a)
print(b)
d = a
k = 1
# string은 스택에 저장된 변수
b = b[::-1]
# 스택에 어떤 값을 할당하는 것이 아니므로 global 키워드가 필요 없다.
# c의 주소를 참조하여 힙에 리스트를 바꿀수 있다!
c.extend([5,6,7,8])
print("test function3 with global keyword:", sep = '\t')
dis.dis(func3)
LOAD_FAST
가 아닌 LOAD_GLOBAL
로 전역스택을 참조한다.test function3 with global keyword:
43 0 RESUME 0
46 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_GLOBAL 2 (a)
22 CALL 1
30 POP_TOP
47 32 LOAD_GLOBAL 1 (NULL + print)
42 LOAD_GLOBAL 4 (b)
52 CALL 1
60 POP_TOP
49 62 LOAD_GLOBAL 2 (a)
72 STORE_FAST 0 (d)
50 74 LOAD_CONST 1 (1)
76 STORE_GLOBAL 3 (k)
52 78 LOAD_GLOBAL 4 (b)
88 LOAD_CONST 0 (None)
90 LOAD_CONST 0 (None)
92 LOAD_CONST 2 (-1)
94 BUILD_SLICE 3
96 BINARY_SUBSCR
100 STORE_GLOBAL 2 (b)
55 102 LOAD_GLOBAL 8 (c)
112 LOAD_ATTR 11 (NULL|self + extend)
132 BUILD_LIST 0
134 LOAD_CONST 3 ((5, 6, 7, 8))
136 LIST_EXTEND 1
138 CALL 1
146 POP_TOP
148 RETURN_CONST 0 (None)
인터프리터형 언어라고는 하지만, 컴파일이 아예 이루어지지 않는것은 아니다. 파이썬은 c언어
와 달리 직접적으로 cpu
가 이해할 수 있는 기계어로 변환하지 않고, 해당 과정을 거친다.
크게 cpython
이라는 인터프리터 안에 compiler
가 자체적으로 존재하고 PVM
이라는 부분이 존재한다.
우선 compiler
는 소스코드(.py
)를 byte code
(.pyc
)로 변환하는 과정을 거친다. (크게 4가지 단계로 구성되어 있다)
이 과정에서 파이썬 내에서 컴파일이 발생하긴 하지만, 아직 cpu
가 이해할 수 있는 기계어는 아니다.
이 바이트코드를 기계어
로 바꾸기 위해서는 PVM
이라는 인터프리터가 필요하다.
PVM
은 python 바이트 코드를 기계에서 실행 가능한 코드로 변환하고 이 인터프리터에서 주어진 파일을 줄별
로 읽고 기계어로 해석해준다. 해석중에 오류가 발생하면 변환이 중단되고 오류 메세지
가 이 단계에서 해석된다.
줄별로 해석된다는게 장점이 될 수도 단점이 될 수도 있다.
c언어
의 경우 한번에 모든 코드를 한번에 변환하고 실행시키기 때문에python
보다 빠르다.
c언어
의 경우 컴파일 단계에서 오류를 감지하고,python
의 경우 컴파일을 한 이후에 줄별로 실행되는 특징 때문에 디버깅이 쉽다는 장점이 있고, 적절한PVM
이 있다면 모든cpu
에서 작동한다.
https://www.geeksforgeeks.org/internal-working-of-python/
https://indianpythonista.wordpress.com/2018/01/04/how-python-runs/