1. 함수(Function)

오늘은 함수(Function)에 대해 알아보도록 하겠습니다. 파이썬의 함수에 대해 알아보기 전에, 이미 어디선가 함수란 말을 들은 기억이 있지 않나요? 중학교 수학시간에 나온 '함수'는, 우리가 배울 파이썬의 '함수'와 비슷합니다. x의 값을 넣으면, y의 값이 정해지듯이 파이썬의 함수 역시도 값을 함수에 집어넣으면, 함수는 결과값을 되돌려줍니다. 예를 들어서, 어느 특정한 수를 넣으면 그 수의 절댓값을 돌려주는 신비의 상자가 있다고 합시다. 우리가 만약 -2라는 값을 이 상자에 넣으면, 꺼낼때는 2가 되어 나오는 것입니다. 그리고 4라는 값은 역시, 4 그대로 꺼내집니다. 이를 그림으로 나타내보도록 하겠습니다.

위의 박스는 어느 수를 넣으면 그 수의 절댓값을 돌려주는 기능을 합니다. 파이썬의 함수도 위에 나와있는 박스와 비슷합니다. 말하자면 특정한 기능을 수행하는 코드의 집합이며, 쉽게 말하면 여러 문장을 하나로 묶는 기능을 하는 녀석입니다. 우리는 이미, 7편을 읽기 전에 수많은 함수를 보아왔으며 간단하게나마 파이썬의 내장 함수를 사용하면서 함수가 어떤 녀석인지 알고 계시는 분들도 있으실 겁니다. 우선은 파이썬 내에서 함수를 정의하는 방법을 알려드리도록 하겠습니다.

def 함수명(인자1, 인자2, ...):
	문장
	...

위에서는 인자(또는 매개변수, parameter)가 보조변수 같은 개념이며, 우선은 함수에 필요한 값을 저장해두는 임시 변수라고 기억하시면 되겠습니다. 이 말이 이해가 되지 않아도, 계속 함수를 정의하다보면 인자가 어떠한 역할을 하는지 감이 오실겁니다. (인자가 아에 존재하지 않을 수도 있고, 여러개가 존재할 수도 있습니다) 우선은 절댓값을 되돌려 주는 상자의 기능을 파이썬의 함수로 정의해보도록 하겠습니다.

>>> def absolute(n):
	if n < 0:
		n = -n
	return n

위의 코드를 잠시 살펴보면, 1행에서 함수명은 absolute이며 인자는 인자 n으로 1개를 가진다는 것을 알 수 있습니다. 2~3행에서는 n이 음수일 경우 양수로 만들어버리고 n을 반환합니다. 여기서 return문은 반환 즉, 돌려준다는 의미를 지니고 있습니다. return문을 만나면 함수가 끝나고, 함수를 호출한 곳으로 전달합니다. 이제 함수를 정의해봤으니, 정상 동작을 하는지 테스트를 한번 해봐야겠죠?

>>> absolute(3)
3
>>> absolute(0)
0
>>> absolute(-1)
1

정상적으로 값을 넘겨주면 절댓값이 돌아온다는 사실을 알 수 있습니다. 갑자기 문득 궁금해진건데, 만약에 값을 돌려주는 return문이 존재하지 않는다면 어떻게 될까요? absolute 함수에서 return문이 들어간 행만 지우고 다시 함수를 불러보세요. 그렇게 한다면, 아래와 같은 결과를 보실 수 있을겁니다.

>>> absolute(3)
>>> absolute(-1)

위 결과를 보시면, 아무런 값이 돌아오지 않는 것을 보실 수 있습니다. 함수 내에서 반드시 return문을 사용하지 않아도 되며, return문이 없을 경우에는 함수 내의 마지막 문장을 실행하고 함수를 호출한 곳으로 되돌아갑니다. 이 때, 함수는 None을 반환합니다. 한가지 예제만 더 보고, return문을 다시 보도록 합시다. 아래에 보여지는 예제에서는, 정의한 함수를 통해 두 수를 더한 결과를 돌려줍니다.

>>> def total(a, b):
	return a + b
>>> total(1, 5)
6
>>> total(100, 20000)
20100

위 예제에서는 total이란 이름을 지니고 인자 2개를 가지는 함수를 정의하고, 이 함수는 넘겨받은 두 인자의 값을 서로 더해 돌려주는 기능을 합니다. 3~6행을 보시면 정상적으로 두 값이 서로 더해져서 돌아온다는 것을 보실 수 있습니다. 함수에 대해 약간이라도 이해가 가셨나요? 한번 자기가 생각하는 간단한 기능의 함수를 직접 정의해보세요. 앞서 말한대로, 이어서 return문에 대해 추가적인 설명을 하도록 하겠습니다.

 

2. 2개 이상의 값 반환

return문은 위에서 이미 말했듯이, 값을 반환하며 함수를 종료하는 기능을 합니다. 우리는 함수를 통해 한개의 값을 반환해왔지만, 두 개 이상의 값을 반환할 수 있습니다. 사실을 말하자면, 반환값은 하나지만 튜플 형식으로 값을 돌려주는 것입니다. 한번 아래의 예제를 보도록 합시다. 

>>> def mul_div(a, b):
	return a * b, a / b
>>> r = mul_div(4, 5)
>>> type(r)
<class 'tuple'>
>>> r
(20, 0.8)

위 예제에서는, a와 b를 곱한 값과 나눈 값을 튜플 형태의 값으로 반환하는 것을 보실 수 있습니다. 반환되는 튜플 값을 따로따로 저장하려면 아래와 같이 호출하시면 됩니다.

>>> def mul_div(a, b):
	return a * b, a / b
>>> mul, div = mul_div(4, 5)
>>> print(mul, div)
20 0.8

 

3. 가변 인자 목록

우리는 지금까지 고정되어 있는 갯수의 인자만 받아올 수 있었습니다. 하지만, 인자의 갯수가 정해지지 않은 가변 인자를 전달하는 방법이 존재합니다. 그것은 바로 함수 인자 앞에다가 *를 붙이면 됩니다. 우선 바로 형식을 보도록 하겠습니다.

def 함수명(*인자):
	문장
	...

위 예제에서의 가변 인자 목록은 튜플의 형태로 저장이 됩니다. 우선 예제를 보도록 합시다.

>>> def sum(*args):
	result = 0
	for i in args:
		result += i
	return result

>>> sum(1)
1
>>> sum()
0
>>> sum(1, 2, 3, 4)
10

위에서 args가 튜플 형태로 처리되는 가변 인자 목록이며, 이를 for문으로 순회하여 요소에 접근하여 출력하도록 하는 함수 func를 정의했습니다. 주목하셔야 할 부분은 7행부터인데, 함수 func에 전달되는 인수(argument)의 수가 고정되어 있는게 아니라 유동적이라는 것을 보실 수 있습니다. 그리고 파이썬에서는 함수에 인수를 전달할 때, 아래와 같이 각각의 인수에 이름을 붙여주고 넘기는 방법도 제공합니다. 바로 함수 인자의 앞에 **를 붙이면 됩니다.

>>> def func(**kwargs):
	for key, value in kwargs.items():
		print(key, ":", value)

		
>>> func(one='1', three='3', two='2')
one : 1
three : 3
two : 2

함수에 인수를 넘길 때 '키=값'의 형태로 넘기는 것을 보아 짐작하신 분들도 있으시겠지만, 우리가 넘긴 인수들이 사전으로 구성되어 함수 func() 내부에서 사전 다루듯이 처리가 되는 것을 볼 수 있습니다. *args와 **kwargs를 함께 쓰고 싶다면 어떻게 함수 원형을 작성해야 할까요? 아래와 같이 *args, **kwargs의 순서로 넘기시면 됩니다.

def func(*args, **kwargs): ...
def func(arg, *args, **kwargs): ...

참고로 예제에는 가변 인자의 이름으로 args나 kwargs를 계속 쓰고있는데, 자기가 임의로 붙여도 상관이 없습니다.


4. 기본 인자값

함수의 인자에 기본 값을 지정해 줄 수 있습니다. 이는 우리가 직접 인수를 넘겨주지 않으면, 기본으로 설정된 값을 사용하는 것입니다. 아래의 예제를 우선 보도록 합시다.

>>> def mul(a, b = 10):
	return a * b
>>> mul(3)
30
>>> mul(4, 5)
20

위에서 보시면 함수 mul에서 인자 b의 기본값이 10으로 지정되어 있습니다. 만약 인자 b를 넘겨주지 않으면, 이 인자 b의 값은 기본값을 따른다는 것입니다. 3행처럼 3만 넘겨주면, a에 3이 들어가고 b는 기본값인 10이 들어가서 30이란 값이 돌아오는 것입니다. 5~6행은 두 인자 모두 넘겨주었기 때문에 기본값을 따를 필요 없이 두 인자의 값이 곱해져서 20이란 결과가 나온 것입니다. 간단하죠? 한가지 주의하실 점이 있다면, 기본값을 사용하려는 인자 뒤에 기본값을 사용하지 않는 인자가 올 수 없다는 것입니다. 이게 어떤 경우인지 예제를 통해 알아보도록 합시다.

>>> def mul(a = 10, b):
	return a * b
SyntaxError: non-default argument follows default argument

위의 예제에서 에러가 발생한 이유는, 기본값을 지정한 인자 a 뒤에 기본값을 지정하지 않은 인자 b가 존재하기 때문입니다. 이는 인자 a에 기본값을 지정하려면, 인자 b에도 기본값을 지정하여야 한다는 것입니다. 이점만 잘 알아두시면 될 것 같습니다.


5. 스코핑 룰(Scoping rule)

여기서 스코핑 룰(Scoping rule)을 잠깐 설명을 드릴까 합니다. 이 스코핑 룰이 뭐냐면, 변수의 생존 범위에 관련된 규칙이라고 말할 수 있습니다. 말이 조금 이상하죠? 갑자기 스코핑 룰을 설명드리는 이유는 혹여나 나중에 변수를 사용하실 때 혼동을 겪으실 수 있기 때문입니다. 변수를 사용하기 위해서는 반드시 알아야 하는 규칙이라고도 할 수 있습니다.

 

파이썬에서의 함수는 별도의 이름공간(namespace)을 가지며, 이 이름공간이라는 것은 말 그대로 이름이 모여있는 공간을 말하는 것입니다. 예를 들어서, 변수를 선언하면 그 변수의 이름이 이름공간에 생성됩니다. 파이썬에서 변수명을 가지고 값을 얻어낼수 있던 것은 사실 이름공간에 있는 이름을 가지고 특정 객체에 접근하여 얻어오는 것이였습니다. 

이름 공간은 위처럼 총 3가지의 공간으로 나뉩니다. 함수 내부의 공간은 지역(Local) 영역이라 하고, 함수 외부의 공간은 전역(Global) 영역이라고 하고, 파이썬 자체에서 정의된 공간은 내장(Built-in) 영역이라고 할 수 있습니다. 여기서 이름을 검색하는 규칙은 지역, 전역, 내장 순서로 검색하게 됩니다. 이것을 첫 글자를 하나씩 따서 LGB 규칙이라고도 합니다. 우선은 아래 예제를 하나 보도록 합시다. 

>>> n = 10
>>> def func(n):
	n = n * 10
>>> func(n)
>>> print(n)
10

위 예제에서 n을 10으로 초기화하고, 그 후에 func라는 함수를 정의하였는데 인자 목록에도 n이란 이름을 지닌 인자가 존재함을 알 수 있습니다. 4행에서 함수 func에 n의 값을 넘겨주고, 함수 print로 n의 값을 확인하였더니 여전히 10인걸 보실 수 있습니다. 이는 지역 영역에 묶여있는 n이란 변수와 전역 영역에 묶여있는 변수 n은 서로 다르기 객체를 가리키기 때문입니다. 함수 내부의 인자 n의 값을 아무리 수정해도 외부에 있는 변수 n의 값은 변하지 않는거죠. 


만약에, 전역 영역에 묶여있는 변수 n에 접근하고 싶다면, 아래와 같이 global이라는 키워드를 통해서 전역 영역에 묶여있는 변수 n의 값을 수정할 수 있습니다. 아래의 예제를 하나 더 보도록 합시다.

>>> n = 10
>>> def func():
	global n
	n = n * 10
>>> func()
>>> print(n)
100

위 예제에서 global로 변수 n을 선언하여, 전역 영역에 있는 변수 n의 값에다 10을 곱해버리는 함수 func를 정의한 것을 보실 수 있습니다. 함수 func를 호출하고 나서 내장 함수인 print로 n의 값을 출력하여 보았더니 10이 곱해진 결과를 얻어낼 수 있었습니다. 이해가 되시나요? 스코핑 룰을 이해하지 못하고 넘어간다면, 나중에 함수를 정의할 때 혼란이 올 수 있으니 반드시 이해를 하시고 넘어가시길 바랍니다.