###2019.03.06 TIL

(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)

대답해보기

========================

  • 전역변수, 지역변수
  • 함수에서 mmutable, immutable에 대해서

function

  • 예를 바탕으로 설명을 진행한다.
>>>    a = 10
>>>    
>>>    def func():
>>>        a = 20
>>>        def inner():
>>>            global a
>>>            a = 30
>>>
>>>    print(a)           # a = 10 출력 (func()가 실행되지 않음)
>>>
>>>    def func():
>>>        a = 20
>>>        def inner():
>>>            global a
>>>            a = 30
>>>    
>>>    func()                    # func()의 a는 global a를 참조하고 있지 않다.
>>>    print(a)            # a = 10 출력 (func()는 실행되었으나 inner()가 실행되지 않음)
>>>
>>>    def func():
>>>        global a
>>>        a = 20
>>>        def inner():
>>>            a = 30
>>>    
>>>    func()
>>>    print(a)                # a = 20 출력    (func()는 실행되었으나 inner()가 실행되지 않음)
>>>
>>>    def func():
>>>        global a
>>>        a = 20
>>>        def inner():
>>>            a = 30
>>>        inner()
>>>    
>>>    func()                    # inner의 a는 global a를 참조하고 있지 않다.
>>>    print(a)                # a = 20 출력 (inner()역시 실행되나 , global a를 참조하고 있지 않다)
>>>
>>>    def func():
>>>        a = 20
>>>        def inner():
>>>            global a
>>>            a = 30
>>>        inner()
>>>
>>>    func()                    # inner의 a는 global a를 참조하고 있다.
>>>    print(a)                 # a = 30 출력 (func()를 실행하며 inner() 역시 같이 실행 되었다.)
>>>    
>>>    
  • 참조를 위해서는 계속 namespace를 위로 이동가능하다.
  • 하지만 수정을 위해서는 단계에 따라 꼭 nonlocal 혹은 global 이라는 호출이 필요하다.
  • 네임 스페이스는 local namespace ---> global namespace ---> Built - in namespace로 올라간다.

네임스페이스에 관한 예를 더 들어보자

>>>    a = 10
>>>    def outer():
>>>        b = 20
>>>        def inner1():
>>>            b = 30
>>>            def inner2():
>>>                d = 40
>>>                b = 50
>>>                print(b)
>>>            ineer2()
>>>            print(b)
>>>        inner1()
>>>    outer()                        # 50 와 30이 출력된다.
>>>    
>>>
>>>    a = 10
>>>    def outer():
>>>        b = 20
>>>        def inner1():
>>>            b = 30
>>>            def inner2():
>>>                d = 40
>>>                nonlocal b       # local namesapce의 b를 참조한다. 
>>>                b = 50
>>>                print(b)
>>>            inner2()
>>>            print(b)
>>>        inner1()
>>>    outer()                        # 50 와 50이 출력된다.
>>>        
>>>

위의 이야기를 명확하게 이해할 수 있으면 namespace에 대한 개념이 좀 더 명확해졌다고 할 수 있다.

함수의 스텍프레임

함수를 실행하게 되면 메모리에 스텍프레임이라는 공간이 형성되고 스텍프레임 안에 변수와 값들이 저장되게 된다.
이 스텍프레임은 함수 호출이 끝나면 사라진다. 스텍프레임은 메모리를 공부한 이후에 좀 더 깊게 살펴보기로 한다.

>>>    g_var = 20
>>>    def func(val):
>>>        val += 100
>>>        return val
>>>
>>>    func(g_var)                        # 120이라는 return 값을 낸다.
>>>    print(g_var)                        # 20이 출력된다.
>>>
>>>

위의 함수를 실행함으로 g_var는 120으로 바뀌지 않는다. 왜 그럴까?

처음 g_var = 20 이라고 할당되므로서 20이라는 값 객체가 메모리 저장된다. g_var는 그 20이라는 값 객체를 가르키는 변수이다. func(g_var)를 실행하게 되면 func라는 스텍프레임이 생기게 되고 그 스텍프레임 안에서 val라는 새로운 변수가 20을 가르키게 된다. 숫자는 immutable하기 때문에 그 val에 100을 더해 120이라는 새로운 값 객체를 만들어서 가르키게 된다. 그리고 함수 실행이 끝나게 되면 func의 스텍프레임이 사라지게 되므로 val = 120이라는 값도 사라지게 된다.
따라서 g_var는 그대로 20이 출력되게 된다.

stackframe

위의 그림이 100% 정확하진 않지만 이해하는데 도움은 줄 수 있다.(파이썬에서는 값이 저렇게 들어가는 것이 아니라 가리키는 형태로 나와야한다.)

어떤 사람은 g_var가 120으로 바뀌었으면 할 것이고, 또 누군가는 g_var는 그대로 두되 저 함수를 통해서 실행된 값들을 받고 싶을 것이다.

2가지의 해결책을 보자

  1. g_var의 값을 120으로 바꾸기
>>>    g_var = 20
>>>    def func():
>>>        global g_var
>>>        g_var += 100
>>>        return val
>>>
>>>    func()                    
>>>    print(g_var)                    # 120이 출력된다.
>>>

전역 변수를 불러옴으로서 해결할 수 있다.

  1. g_var는 그대로 두되 저 함수를 통해서 실행된 값들을 받자.
>>>    g_var = 20
>>>    def func(val):
>>>        val += 100
>>>        return val
>>>
>>>    a = func(g_var)                        # 120이라는 return 값을 낸다.
>>>    print(a)                            # 120이 출력된다.
>>>    print(g_var)                        # 20이 출력된다.
>>>
>>>

해결책은 간단하다. 새로운 변수 a를 통해 func(g_var)의 값을 받아오면 된다.

그럼 여기서 또 하나의 의문이 생긴다. 과연 mutable한 list나 dictionary가 오게 되면 바뀔까?

한번 해보면 된다 ㅎㅎ.

>>>
>>>    li1 = [1,2,3]
>>>    def func(li, i):
>>>        li.append(i)
>>>        return li
>>>
>>>    func(li1, 4)
>>>    print(li1)             # [1,2,3,4]
>>>
>>>
list stackframe

너무나도 쉽게 변경되는 것을 알 수 있다.

결론을 내리자면 함수에서 immutable한 객체는 새로운 값 객체를 생성하여 할당하며, mutable한 객체는 기존의 객체를 변경하여 할당한다.

+ Recent posts