2.9.4 더 알면 좋은 것들
프로그래밍에서 함수 사용은 가히 필수적이라고 할 수 있습니다. 지금까지 배운 것을 충분히 이해한 것만으로도 훌륭하고, 이 책 프로젝트를 진행하는 데는 지장이 없습니다. 하지만 알아 두면 좋은 함수에 대한 몇 가지 이야기를 덧붙이고자 합니다.
먼저 매개변수에 관한 내용입니다. 함수에서 매개변수 사용은 필수는 아니지만 선언해 놓고 호출할 때 전달하지 않으면 에러가 발생한다고 설명했습니다. 다음 예에서는 arg1, arg2까지 총 두 개의 매개변수를 만들었지만 arg2를 전달하지 않아 에러가 발생합니다.
> def func1(arg1, arg2):
> print(arg1, arg2)
>
> func1("매개변수1")
(...)
TypeError: func1() missing 1 required positional argument: 'arg2'
이렇게 사용자 실수로 매개변수를 전달하지 못하거나 호출할 때 딱히 전달할 값이 없을 때도 있기 때문에 매개변수 기본값 설정이 필요합니다. 매개변수 기본값 설정이란 함수를 선언할 때 매개변수가 전달되지 않으면 사용할 값을 미리 정해 놓는 것입니다. 예를 들어 보겠습니다.
> def func1(arg1, arg2="Default"):
> print(arg1, arg2)
>
> func1("매개변수1")
매개변수1 Default
함수를 선언할 때 arg2="Default"라고 정의하면 arg2 값이 전달되지 않았을 때 미리 정의한 값을 사용합니다. 물론 이 값은 전달되지 않을 때만 사용되고, 값을 전달하면 전달된 값을 사용합니다.
> def func1(arg1, arg2="Default"):
> print(arg1, arg2)
>
> func1("매개변수1", "매개변수2")
매개변수1 매개변수2
매개변수 기본값을 정의할 때 주의할 점은 뒤에서부터 등장하는 매개변수 순서로 설정해야 한다는 것입니다. 앞 예처럼 arg2는 기본값을 설정할 수 있지만, 다음 예처럼 arg2를 제외하고 arg1에 기본값은 설정할 수 없습니다.
> def func1(arg1="Default", arg2):
> print(arg1, arg2)
>
> func1("매개변수2")
(...)
SyntaxError: non-default argument follows default argument
따라서 다음과 같이 매개변수 1, 매개변수 2, 매개변수 3을 사용하는 함수에서 기본값을 설정할 매개변수는 매개변수 3 → 매개변수 2 → 매개변수 1 순서로 정할 수 있습니다. 매개변수 3부터 기본값을 설정하지 않고 매개변수 2나 매개변수 1부터 설정하려고 하면 에러가 발생합니다.
def 함수명(매개변수1, 매개변수2="기본2", 매개변수3):
함수로직
그러므로 매개변수 1까지 기본값을 설정하고 싶다면 매개변수 3·2·1을 모두 설정해야 합니다.
def 함수명(매개변수1="기본1", 매개변수2="기본2", 매개변수3="기본3"):
함수로직
다음은 매개변수를 지정해서 사용하는 방법입니다. 지금까지는 매개변수를 선언한 순서대로 사용해야 했습니다. 예를 들어 func1() 함수에서 arg1, arg2라는 매개변수를 선언하면 함수를 호출하는 데 사용한 매개변수 1, 매개변수 2가 함수의 arg1과 arg2에 순서대로 전달되었습니다.
> def func1(arg1, arg2):
> print(arg1, arg2)
>
> func1("매개변수1", "매개변수2")
매개변수1 매개변수2
전달할 매개변수를 다음과 같이 지정한다면 함수를 호출할 때 매개변수 전달 순서는 고려하지 않아도 됩니다.
> def func1(arg1, arg2):
> print(arg1, arg2)
>
> func1(arg2="매개변수2", arg1="매개변수1")
매개변수1 매개변수2
하지만 함수를 선언할 때 사용한 매개변수명인 arg1, arg2를 그대로 사용해야 한다는 점을 기억하길 바랍니다.
다음은 여러 값을 반환(return)하는 방법입니다. return 키워드를 사용하여 함수 내부의 결과를 외부로 전달할 수 있었습니다. 다음 코드는 param1과 param2의 값을 더해서 반환했기에 result에는 숫자 4가 저장됩니다.
> def add(param1, param2):
> return param1 + param2
>
> result = add(1, 3)
> print(result)
4
하지만 return이 꼭 하나의 값만 반환할 수 있는 것은 아닙니다. return A처럼 하나의 값을 반환하는 것이 아니라 return A, return B처럼 코드를 작성하면 A와 B를 묶어 하나의 튜플을 반환합니다. 따라서 다음 코드에서 result에는 param1 + param2, param1 / param2 값이 튜플로 저장됩니다.
> def add_and_divide(param1, param2):
> return param1 + param2, param1 / param2
>
> result = add_and_divide(6, 2)
> print(result)
(8, 3.0)
print(result) 결과는 (8, 3.0)이며 값을 두 개 가지고 있으므로 변수를 두 개 사용하여 언패킹할 수 있습니다.
> def add_and_divide(param1, param2):
> return param1 + param2, param1 / param2
>
> result = add_and_divide(6, 2)
> A, B = result
> print(A, B)
8 3.0
물론 add_and_divide의 결과 자체가 튜플이니 result 변수를 사용하지 않고도 바로 언패킹할 수 있습니다.
> def add_and_divide(param1, param2):
> return param1 + param2, param1 / param2
>
> A, B = add_and_divide(6, 2)
> print(A, B)
8 3.0
마지막으로 함수끼리 호출 순서를 알아보겠습니다. 함수 안에서 또 다른 함수를 사용하는 예입니다.
> def greeting():
> print("greeting starts")
> print("greeting ends")
>
> def say_hello():
> print("say_hello starts")
> greeting()
> print("say_hello ends")
>
> say_hello()
say_hello starts
greeting starts
greeting ends
say_hello ends
greeting()이라는 함수를 만들고 say_hello() 함수 안에서 greeting() 함수를 호출했습니다. 그러면 say_hello를 실행하자마자 say_hello starts가 출력되고 그다음 greeting() 함수가 호출됩니다. greeting() 함수를 호출한 결과인 greeting starts, greeting ends가 나온 후에야 say_hello ends가 나옵니다. 여기서 알 수 있는 사실은 함수 A 안에서 또 다른 함수 B를 호출하면 함수 B가 종료되고 나서야 함수 A가 종료될 수 있다는 것입니다. 간단히 그림으로 살펴보겠습니다.
▲ 그림 2-26 함수 호출 순서
프로그램을 실행시키면 메모리에 say_hello()가 적재된 후 해당 함수가 동작합니다(그림 2-26의 왼쪽 그림). say_hello()가 실행되던 중 greeting() 함수를 호출하면 그림 2-26의 가운데 그림과 같이 greeting()도 메모리에 적재되는 구조가 됩니다. 이때 greeting() 함수의 종료 없이는 say_hello() 함수가 먼저 종료될 수 없습니다. 따라서 오른쪽 그림과 같이 greeting() 함수가 종료된 후에야 say_hello()가 종료될 수 있습니다.
한마디로 다시 요약하면, 함수 A가 실행되면서 다른 함수 B를 호출하고 나면 함수 A가 바로 종료되는 것이 아니라 함수 B가 정상적으로 종료된 후에야 함수 A를 비로소 종료할 수 있습니다. 함수 B에서 또 다른 함수 C를 호출한다면 함수 C가 먼저 종료된 후 순서대로 함수 B, 함수 A가 종료될 수 있습니다.