Byeonguk Kim

안녕하세요. 29살의 조금은 늦은 나이로 새롭게 개발자로 시작하는 신입 개발자입니다. 포트폴리오 [https://deaguowl.github.io]

파이썬 04. function기본

06 Mar 2019 » Python

###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한 객체는 기존의 객체를 변경하여 할당한다.