5. 함수#

프로그램은 명령문의 합성으로 구현된다. 많은 프로그램은 몇 백 또는 몇 천 줄의 명령문으로 구성되며, 경우에 따라 몇 십만, 몇 백만 줄 이상의 명령문으로 구성된 프로그램도 있다.

몇 백 줄 이상의 명령문으로 구성된 프로그램을 구현하다 보면 특정 기능을 수행하는 명령문을 반복해서 사용하곤 한다. 또한 프로그램의 소스코드를 여러 개의 소스코드 파일로 나누어 작성하고 관리할 필요성도 대두된다. 이때 함수와 모듈을 활용한다.

여기서는 먼저 함수에 대해 이야기하고 이어지는 장에서 모듈을 소개한다.

5.1. 함수 호출과 표현식#

함수function는 간단히 말해 명령문에 붙인 이름이다. 예를 들어 아래 코드를 실행하면 "함수란 특정 명령문을 대신한다." 라는 문장이 화면에 출력된다.

print('함수란', '특정 명령문을 대신한다.')
함수란 특정 명령문을 대신한다.

print() 함수가 대신하는 명령문, 즉 print라는 이름이 붙은 명령문이 작동하여 인자로 지정된 아래 두 문자열을 공백을 사이에 두고 연속적으로 화면에 출력한다.

  • 첫째 인자: 함수란

  • 둘째 인자: 특정 명령문을 대신한다.

print라는 이름이 붙은 명령문이 정확히 어떻게 구현되었는지 사용자는 쉽게 알지 못하고 또 굳이 알 필요도 없다. 화면에 문자열 등의 값을 출력하고 싶을 때 print() 함수를 사용한다는 사실만 알면 된다.

함수는 아래 형식의 함수 호출function call을 통해 활용된다.

함수이름(인자1, 인자2, ..., 인자n)

함수 호출에 필요한 인자의 수는 함수의 정의에 의존한다. print() 함수는 한 개 이상의 인자를 사용할 수 있고, int(), float() 등의 형변환 함수는 보통 하나의 인자를 사용한다.

int("3")
3
float("3.14")
3.14

인자가 전혀 필요 없는 경우도 있는데 그런 경우에도 함수 호출은 열고닫기 소괄호 ()를 반드시 사용해야 한다. 예를 들어 int() 함수와 float() 함수를 인자 없이 호출하면 각각 0과 0.0을 계산한다.

int()
0
float()
0.0

반면에 print() 함수를 인자 없이 호출하면 아무 것도 출력되지 않는다.

print()

함수 호출 표현식

함수 호출 표현식이 값을 표현하는 대표적인 표현식(4.3절)이다. 예를 들어 int(3.14)는 정수 3을, float('3.14')는 부동소수점 3.14type(3.14)는 부동소수점의 자료형인 floattype('3.14')는 문자열의 자료형인 str을 계산하는데 이렇게 함수 호출에 의해 계산된 값이 바로 함수 호출 표현식이 표현하는 값이다.

int(3.14)
3
float('3.14')
3.14
type(3.14)
float
type('3.14')
str

값의 종류

파이썬과 같은 프로그래밍 언어는 3, 3.14 등의 수 이외에 문자열, 리스트 등 다양한 종류의 값을 사용한다. 심지어 부동소수점 3.14의 자료형인 float, 문자열 '3.14'의 자료형인 str 조차 값으로 취급된다. 하지만 프로그래밍 언어마다 값의 종류가 다르며, 파이썬 이외의 언어를 학습할 때 언어 고유의 값이 어떻게 다른지를 이해하면 보다 빠르게 해당 언어의 특징을 파악할 수 있다.

함수에 의해 계산된 값은 저장되어 필요에 따라 활용될 수 있다. 예를 들어 아래 코드는 int() 함수가 계산한 결과를 제곱한다.

y = int(3.14)
y**2
9

또한 함수가 계산한 값을 바로 이용할 수도 있다. 예를 들어 아래 코드는 type() 함수에 의해 계산된 자료형에 따라 다른 명령문을 실행하도록 하였다. 변수 x가 가리키는 값이 문자열이기에 type(x) == str이 참이되어 print(x * 2)가 실행된다.

x = '3.14 '

if type(x) == str:
    print(x * 2)
else:
    print(x / 2 )
3.14 3.14 

5.2. 함수 정의#

함수는 아래 형식으로 선언된다.

def 함수이름(매개변수1, 매개변수2, ...):
    명령문

키워드 def 로 시작하는 줄은 함수의 기본 정보를 담은 헤더header이고 나머지는 함수의 본문body이다. 함수의 본문은 함수가 호출되었을 때 실행되어야 하는 명령문을 포함한다.

함수를 정의할 때 다음 세 가지 사항에 주의한다.

  • 함수 이름과 매개 변수 이름: 변수 이름 짓기와 동일한 조건을 따른다.

  • 매개 변수의 종류와 개수는 함수를 정의하는 사용자가 정해야 한다.

  • 함수 헤더의 끝에 콜론 :을 작성하고, 함수 본문에 사용되는 명령문은 들여쓴다. 들여쓰기는 보통 Tab 키를 이용한다.

5.2.1. 매개 변수와 인자#

매개 변수parameter함수의 본문에서만 사용되는 변수이며 함수를 호출할 때 사용하는 인자argument를 함수 본문의 명령문에 전달한다. 예를 들어 아래 코드는 정수 또는 부동소수점 두 개가 주어지면 두 수의 합을 계산하는 함수 myAdd를 정의한다. 두 개의 수를 인자로 받아 합을 계산해야 하기에 두 개의 매개 변수를 사용한다.

  • left 매개 변수: 덧셈 연산의 첫째 인자

  • right 매개 변수: 덧셈의 연산의 둘째 인자

def myAdd(left, right):
    sum_ = left + right
    return sum_

이제 myAdd() 함수를 두 개의 인자와 함께 호출하면 두 인자의 합을 계산한다. 예를 들어 아래 코드는 -2와 5의 합을 계산한다.

myAdd(-2, 5)
3

5.2.2. 함수의 반환값과 함수 호출 표현식#

myAdd(-2, 5) 가 호출되었을 때 파이썬 실행기 내부에서 실제로 실행되는 명령문은 두 단계로 구성된다.

첫째, 매개 변수 각각에 대해 주어진 인자의 순서대로 변수 할당 명령문이 실행된다.

left = -2
right = 5

leftright 두 매개 변수가 함수의 본문에서 사용될 때 이렇게 할당된 값을 이용한다. 이런 의미에서 매개 변수가 인자를 함수의 본문에 전달한다고 말한다

둘째, myAdd() 함수의 본문에 사용된 명령문이 차례대로 실행된다.

sum_ = left + right
return sum_

sum_ = left + right 명령문이 실행되어 sum_ 변수에 정수 3이 할당된다. 끝으로 return sum_ 명령문이 실행된 후에 바로 함수 호출의 실행 과정이 종료된다.

함수 반환값

함수는 호출되면 앞서 설명한 대로 매개 변수에 지정된 인자를 할당된 다음에 본문 명령문이 차례대로 실행된다. 그러다가 아래 모양의 반환 명령문이 실행되는 순간 바로 함수 호출의 실행을 종료한다.

return 표현식

그런데 함수 호출의 실행을 완전히 종료하기 전에 return 명령문에 지정된 표현식이 표현하는 값을 반환값으로 지정한다. 예를 들어 myAdd(-2, 5)가 호출되면 sum_ 변수에 할당된 3이 함수의 종료와 함께 반환값으로 지정된다.

반환값으로 지정된 값은 다른 계산에 사용되거나 변수에 할당될 수 있다. 예를 들어 아래 코드는 myAdd(-2, 5) 함수 호출의 반환값을 변수 할당에 활용한다.

sum_of_minus2_5 = myAdd(-2, 5)

반면에 아래 코드는 반환값을 바로 곱셈 연산에 활용한다.

myAdd(-2, 5) * 3
9

이처럼 myAdd(-2, 5)과 같은 함수 호출 표현식은 함수의 반환값을 표현한다.

return ... 명령문의 위치

함수를 호출하면 함수의 본문이 실행되는 도중에 return ... 명령문이 실행되는 순간 반환값을 지정하고 반환한다. 어떤 경우에도 두 개의 return ... 명령문이 실행되지 않으며, 결국 함수는 하나의 값만 반환한다.

하지만 그렇다고 해서 함수 본문에 return ... 명령문이 한 번만 사용된다는 의미는 아니다. 예를 들어, 아래 myAddProd() 함수는 두 인자의 크기를 비교한 결과에 따라 덧셈 또는 곱셈을 실행한 결과를 반환값으로 지정한다.

def myAddProd(left, right):
    if left < right:
        sum_ = left + right
        return sum_
    else:
        prod_ = left * right
        return prod_

하지만 어떤 경우에도 return sum_return prod_ 가 동시에 실행되지는 않는다. 이런 의미에서 함수의 반환값은 항상 하나다.

myAddProd(-2, 5)
3
myAddProd(5, -2)
-10

심지어 아래 useless() 함수처럼 return ... 명령문을 연속해서 작성하더라도 마찬가지다. 먼저 실행된 return sum_ 명령문에 의해 sum_에 할당된 값이 반환되면 바로 함수의 실행을 멈춘다. 즉, return prod_ 는 어떤 경우에도 실행되지 않는다.

def useless(left, right):
    sum_ = left + right
    prod_ = left * right
    return sum_
    return prod_          # 절대 실행되지 않음
useless(-2, 5)
3

5.2.3. None 반환값#

아래 코드에 정의된 double_int() 함수의 정의는 return ... 명령문을 사용하지 않는다. 즉, 반환값을 지정하지 않는다.

def double_int(num_param):
    double_param = num_param * 2

return ... 명령문이 없는 함수를 호출하면 반환값이 지정되지 않는다. 예를 들어 double_int(5)를 실행해도 아무런 값이 표시되지 않는다.

double_int(5)

아래 코드처럼 double_int(5)에 의해 계산된 값을 화면에 출력하려 하면 None 이라는 이상한 값이 출력된다.

print(double_int(5))
None

앞서 설명한 대로 double_int(5)가 호출되면 아래 코드가 실행된다.

num_param = 5
double_param = num_param * 2

따라서 double_param 변수에 10이 할당되는데 이후에 실행할 아무런 명령문도 없기에 반환값이 지정되지 않은 채 함수의 실행이 종료된다. 파이썬 실행기는 이런 경우에 반환값을 None으로 강제 지정한다.

따라서 double_int() 함수는 실제로는 아래 코드처럼 정의된 것으로 간주된다.

def double_int(num_param):
    double_param = num_param * 2
    return None                  # None 반한값 지정

None 값

None도 하나의 값이라서 변수 할당에 사용되어 저장될 수 있다. 하지만 예를 들어 연산에 사용하면 허용되지 않는 인자라는 의미의 TypeError 오류가 발생한다.

x = double_int(5)
print(x)
None
y = x + 1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[27], line 1
----> 1 y = x + 1

TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

숫자 0이 하나도 없다를 표현하는 숫자인 것처럼 None은 아무런 의미도 없는 값을 값을 가리킨다. None은 앞서 본 것처럼 함수의 반환값이 필요하지 않은 경우에 주로 사용된다. None의 다른 용도는 앞으로 필요에 따라 다룰 것이다.

print() 함수의 반환값

반환값이 None인 대표적인 함수는 print() 함수이다. 그런데 print() 함수를 아래와 같이 활용하면 마치 인자를 그대로 반환하는 것처럼 보인다.

print(3.14)
3.14

하지만 화면에 출력되는 문자열은 반환값이 아니다. 다만 print() 함수의 본문에 포함된 명령문 중에서 지정된 값을 화면에 출력하는 명령문이 실행된 결과에 불과하다. 앞서 설명한 대로 print(3.14)의 반환값은 None임을 아래 코드가 확인해준다.

먼저 print(3.14) 가 가리키는 값을 변수 x에 할당하자. 아래 코드를 실행했을 때 보여지는 3.14는 반환값이 아니라 지정된 인자가 화면에 출력된 결과이다.

x = print(3.14)
3.14

x에 할당된 값을 확인하려 하면 아무 것도 보여지지 않는다.

x

화면에 출력하도록 해야 겨우 None으로 확인된다.

print(x)
None

5.2.4. 람다 함수#

특별한 목적으로 한 번만 사용되는 함수가 필요할 때, 그리고 함수의 본문이 매우 간단할 때 아래 형식으로 이름을 지정하지 않으면서 함수를 정의할 수 있다.

lambda 매개변수1, ..., 매개변수n : 표현식

그리스어 알파벳 람다lambda\(\lambda\)를 이용하기에 위와 같이 정의된 함수를 람다 함수라 부른다.

예를 들어 두 인자의 곱을 반환하는 람다 함수는 아래와 같이 정의된다.

  • ab: 매개 변수 2개

  • 콜론 기호 : 뒤의 표현식 a * b: 람다 함수의 반환값

lambda a, b : a * b
<function __main__.<lambda>(a, b)>

람다 함수는 자신을 가리키는 이름이 없기에 람다 함수를 호출하려면 항상 람다 함수 정의 자체를 사용해야 해서 조금 불편하다. 아래 코드는 2와 5의 곱을 계산하는 람다 함수 호출이다.

(lambda a, b : a * b)(2, 5)
10

이렇듯 람다 함수는 함수 자체를 활용하기에는 많이 불편하지만 람다 함수 고유의 기능이 따로 있기에 여기서 간단하게 소개해 둔다. 예를 들어 14장에서 람다 함수가 사용된 코드를 살펴볼 것이다.

5.3. 위치 인자와 키워드 인자#

함수의 인자는 위치 인자키워드 인자 두 종류로 구분된다.

  • 위치 인자positional argument: 반드시 지정해야 하는 인자

  • 키워드 인자keyword argument: 필요에 따라 추가로 지정할 수 있는 인자

함수를 정의할 때 한 종류의 인자만 사용하도록 해도 되고 두 종류의 인자를 섞어서 사용하도록 해도 된다. 단, 두 종류의 인자를 모두 사용하도록 하려면 위치 인자를 먼저, 키워드 인자를 나중에 오도록 해야 한다. 파이썬 프로그래밍에서 매우 유용하게 활용되는 print() 함수를 이용하여 위치 인자와 키워드 인자의 활용법을 소개한다.

5.3.1. print() 함수#

여러 개의 값을 화면에 출력하려면 print() 함수에 여러 개의 인자를 지정하여 호출한다. 그러면 인자들이 공백으로 구분되어 함께 한 줄에 출력된다. 예를 들어 아래 코드는 안녕, 파이썬, ! 세 개의 문자열을 공백으로 구분하면서 한 줄에 출력한다.

print('파이썬', '안녕', '!')
파이썬 안녕 !

출력 대상으로 지정된 세 개의 인자가 공백으로 구분되어 한 줄에 출력되는 이유는 sep이라는 숨겨진 키워드 인자때문이다. 위 코드를 실행하면 파이썬 실행기는 실제로는 아래 코드를 실행한다.

print('파이썬', '안녕', '!', sep=' ')
파이썬 안녕 !

변수 할당 명령문 형식처럼 생긴 sep=' '은 화면에 출력해야할 인자들을 공백으로 구분하여 한 줄에 출력하도록 한다.

키워드 인자 변경

그런데 키워드 인자에 할당된 값은 임의로 바꿀 수 있다. 예를 들어, 출력 대상으로 지정된 각각의 인자를 하이픈 기호'-'로 구분하려면 즉, 인자와 인자를 하이픈으로 연결하려면 아래 코드에서처럼 키워드 인자 sep에 문자열 '-'이 할당한다.

print('파이썬', '안녕', '!', sep='-')
파이썬-안녕-!

위 코드에서 print() 함수를 호출할 때 사용된 위치 인자와 키워드 인자는 다음과 같다.

  • 위치 인자: 화면 출력에 사용되는 세 개의 인자, '파이썬', '안녕', '!'

  • 키워드 인자: sep 매개 변수에 할당된 문자열 '-'

print() 함수의 키워드 인자

print() 함수의 위치 인자는 0개 이상 원하는 만큼 사용할 수 있다. 반면에 키워드 인자는 sep 이외에 end, file, flush 매개 변수에 할당될 수 있다. 실제로 print() 함수의 헤더는 다음과 같다.

print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

매개 변수 각각의 역할은 다음과 같다. 키워드 인자들은 특별한 기능을 수행하며, 그 기능을 그대로 사용하는 경우 함수 호출 과정에서 굳이 언급할 필요가 없다.

  • value: 위치 인자. 출력에 사용할 값 하나를 인자로 받을 수 있음.

  • 말줄임표(...): 여러 개의 위치 인자를 사용할 수 있음을 의미함.

  • sep=' ': sep 매개 변수에 공백 문자 하나로 구성된 문자열이 기본 키워드 인자로 지정됨.

  • end='\n': end 매개 변수에 줄바꿈 문자 하나로 구성된 문자열이 기본 키워드 인자로 지정됨.

  • file=sys.stdout: file 매개 변수에 sys.stdout이 기본 키워드 인자로 지정됨.

  • flush=False: flush 매개 변수에 False가 기본 키워드 인자로 지정됨.

앞서 설명한 것처럼 sep 매개 변수의 키워드 인자는 출력해야할 값들을 구분하는 방식을 지정한다. end 매개 변수의 키워드 인자는 값들을 출력한 다음에 할 일을 지정한다. 기본 키워드 인자가 줄바꿈이기에 print() 함수를 호출한 다음엔 항상 줄바꿈이 실행된다. 만약에 빈 문자열 ''end 매개 변수에 할당하면 줄바꿈을 하지 않는다. 아래 두 코드를 비교하면 end 매개 변수의 역할을 확인할 수 있다.

  • 기본 키워드 인자를 그대로 사용한 경우: 첫째 print() 함수 호출에 의해 '파이썬 안녕 !'이 출력된 후에 줄바꿈이 이뤄지고 그 다음에 둘째 print() 함수 호출에 의해 '===' 문자열이 출력됨.

print('파이썬', '안녕', '!')
print('===')
파이썬 안녕 !
===
  • end 매개 변수의 인자를 빈 문자열로 지정한 경우: 첫째 print() 함수 호출에 의해 '파이썬 안녕 !'이 출력된 후에 줄바꿈을 하지 않은 채로 둘째 print() 함수 호출에 의해 '===' 문자열이 출력됨.

print('파이썬', '안녕', '!', end='')
print('===')
파이썬 안녕 !===

print() 함수의 fileflush 두 매개 변수는 특별한 경우에 활용되며 여기서는 다루지 않는다. 두 매개 변수의 간단한 예제는 이곳에서 확인할 수 있다.

5.3.2. 키워드 인자 사용 함수 정의#

아래 코드는 myAdd10() 함수를 키워드 인자를 이용하여 정의하는 방식을 보여준다. 사용된 두 매개 변수의 역할은 다음과 같다.

  • left: 덧셈의 왼쪽 인자로 사용될 값을 할당받을 매개 변수

  • right: 덧셈의 오른쪽 인자로 사용될 값을 할당받을 매개 변수. 10이 기본 키워드 인자로 지정됨.

def myAdd10(left, right=10):
    return left + right

아래 코드에서처럼 myAdd10() 함수를 호출할 때 둘째 인자가 생략되면 자동으로 10이 대신 사용되어 인자가 주어지면 10을 더한 값이 반환되는 함수처럼 사용된다.

myAdd10(5)
15

실제로는 아래 코드와 동일하게 작동한다.

myAdd10(5, 10)
15

10이 아닌 다른 값을 right 매개 변수의 키워드 인자로 지정하려면 아래 코드와 같이 함수 호출을 실행한다.

myAdd10(5, right=20)
25

5.4. 지역 변수와 전역 변수#

아래 square_return() 함수는 하나의 인자와 함께 호출되면 인자의 제곱을 반환한다.

seventeen = 17

def square_return(number):
    square = number ** 2
    return square

square_return() 함수의 매개 변수 number와 함수 본문에서 정의된 squre 두 변수는 square_return() 함수와 독립적으로 사용될 수 없다. 이는 함수가 호출되어 실행된 이후에도 마찬가지다. 이유는 함수가 실행되는 과정에서 선언된 변수들은 모두 함수의 실행이 종료되는 순간 컴퓨터 메모리에서 삭제되어 더 이상 사용될 수 없기 때문이다.

예를 들어 아래 함수 호출을 실행하자.

square_return(-5)
25

아래 두 코드를 실행하면 모두 변수가 존재하지 않는다는 의미의 NameError 오류가 발생한다.

number
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[45], line 1
----> 1 number

NameError: name 'number' is not defined
square
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[46], line 1
----> 1 square

NameError: name 'square' is not defined

즉, numbersquare 두 변수가 존재하지 않는다. 반면에 seventeen 변수는 언제나 사용할 수 있다.

print("seventeen 변수:", seventeen)
seventeen 변수: 17

numbersquare 변수처럼 함수를 선언할 때 사용되는 매개 변수와 함수 본문에서 선언되는 변수는 함수가 실행되는 동안에만 의미를 갖는다는 의미에서 지역 변수local variable라 한다. 반면에 seventeen 변수처럼 함수 밖에서도 의미를 갖는 변수는 전역 변수global variable라 부른다.

함수 호출 과정에서 계산된 값들 중에 일부를 함수 호출이 끝난 후에도 활용하려면 반환값으로 지정해야 한다. 예를 들어 17의 제곱을 계산해서 활용하고자 한다면 다음과 같이 반환값을 변수에 할당해서 활용한다.

square17 = square_return(seventeen)
print("17의 제곱:", square17)
17의 제곱: 289

지역 변수와 전역 변수의 이름은 구분해서 작성하는 게 좋다. 그렇지 않으면 아래 코드가 보여주듯이 혼란스러워질 수 있다. 아래 코드에서 square 가 전역 변수와 지역 변수로 사용된다. 하지만 두 변수는 서로 상관이 없다.

seventeen = 17
square = 2

def square_return(number):
    square = number ** 2
    return square

아래 코드에서 마지막에 호출되는 print(squre) 는 전역 변수 square에 할당된 2를 출력한다. 이유는 square_return() 함수의 본문에 사용된 지역 변수 square는 함수가 호출되어 실행되는 과정에서만 존재하고 함수의 실행이 종료되는 순간 컴퓨터 메모리에서 삭제되기 때문이다.

물론 square_return() 함수의 본문에서 사용되는 squre 변수는 항상 지역 변수를 가리키기에 return square 명령문의 square는 인자 10의 제곱인 100을 가리키며, 따라서 100이 함수의 반환값으로 지정되었다.

print(square_return(10))
print(square)
100
2

5.5. 필수 예제#

참고: (필수 예제) 함수

5.6. 연습문제#

참고: (연습) 함수