본문 바로가기

파이썬

함수

1. 함수

■ 입력값이 들어오면, 어떤 처리 단계를 거쳐 그 결과를 반환하는 것이 바로 함수가 하는 일이다. 

■ 예를 들어, \( y = 2x + 3 \)는 입력 \( x \)에 따라 출력 \( y \) 값이 변하는 함수이다. 파이썬의 print(), abs() 와 같은 함수들도 입력값을 받아 결과를 출력하는 함수의 대표적인 예이다.

- '-10'을 print()의 입력으로 넣으면 -10을 출력한다. -10을 abs()의 입력으로 넣으면 절댓값 10을 반환한다.


1.1 함수의 필요성

■ 함수를 사용하면 동일한 내용을 반복하는 처리 단계를 하나로 묶어, 필요할 때 호출하여 사용할 수 있다. 특정 기능을 수행하는 코드의 묶음이라고 볼 수 있다. 이는 코드를 재활용하는 것으로 볼 수 있다.

■ 예를 들어, 어떤 색을 알려주는 프로그램이 있다고 했을 때, 아래의 첫 번째 코드는 파란색, 두 번째 코드는 빨간색임을 출력한다. 색의 이름만 다르고 다른 메시지는 동일하다. 

print('Hello')
print('This color is blue.')
```#결과#```
Hello
This color is blue.
````````````

print('Hello')
print('This color is red.')
```#결과#```
Hello
This color is red.
````````````

■ 이러한 경우에 유용하게 사용할 수 있는 도구가 함수이다. 예시의 처리 단계를 하나로 모아서 필요할 때 언제든지 호출하여 사용할 수 있다.

def msg(color):
    print('Hello')
    print(f'This color is {color}.')
msg('blue')
```#결과#```
Hello
This color is blue.
````````````

msg('red')
```#결과#```
Hello
This color is red.
````````````

- 위와 같이 함수를 사용하는 것을 함수를 호출(call)한다고 한다. 


1.2 사용자 정의 함수의 구조

■ 파이썬의 내장 함수 외에 값을 반환하는 자체 함수를 다음과 같이 정의할 수 있다. 

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

 

■ 함수는 크게 헤더(header)와 몸체(body)로 나누어진다. 헤더는 def 키워드로 시작하는 부분이다. 

1.2.1 함수 헤더(header)

■ 'def'는 함수를 만들 때 사용하는 예약어(키워드)이다. 함수 이름은 사용자가 임의로 만들 수 있다. 

■ 함수이름과 함께 매개변수(파라미터)를 적어준다. 함수 이름 뒤 괄호 안의 매개변수는 함수에 입력으로 외부에서 전달되는 값을 받는 변수이다. 

■ 매개변수를 적어준 다음, 콜론(:)을 찍어준다. 함수 헤더는 콜론으로 끝나야 한다. 

1.2.2 함수 몸체(body)

■ 함수 몸체에는 함수가 수행해야 하는 명령문들이 똑같은 들여쓰기 간격으로 들어간다. 

■ 함수 몸체에 return 키워드를 이용하여 명령문들이 계산한 결과를 반환할 수 있다. 함수가 유용한 것은 함수로부터 반환 값을 받을 수 있기 때문이다.

■ return은 함수 블록 내 어느 곳에나 나타날 수 있으며, 첫 번째 return 명령이 실행되는 즉시 종료한다. 

 첫 번째 retrun 명령이 실행되는 즉시 종료되는 것을 함수를 빠져나가는 방법으로 이용할 수 있다.

def greet(word):
    if word == 'abc':
        return
    print('ABC')

■ 위와 같이 if 조건문이 word == 'abc'이면 return이 실행되어 greet() 함수가 종료된다. 

■ 값을 반환할 때 주의할 점은 하나의 경우에서만 값을 반환하면 안 된다는 점이다. 모든 경우에서 값을 반환해야 한다. 예를 들어, 다음과 같이 함수 내에서 if 문을 사용할 경우

def greet(word):
    if word == 'abc':
        return True

■ word가 'abc'가 아닌 경우, 그때의 값을 반환하는 문장이 없다. 그러므로 다음과 같이 해당 경우에 대해서도 return할 값을 지정해야 한다.

def greet(word):
    if word == 'abc':
        return True
    else:
        return None

■ 혹은 다음과 같이 반환할 값을 하나의 변수에 저장했다가 마지막에 return 문장을 사용하는 방법도 있다. 

def greet(word):
    if word == 'abc':
        flag = True
    else:
        flag = False
    return flag

1.3 함수 호출과 매개변수와 인수

■ 함수를 정의하는 이유는 함수를 사용하기 위해서이며, 함수를 사용하기 위해서는 함수를 호출(call)하면 된다. 

■ 함수 호출이란 함수의 이름을 써주는 것을 말한다. 함수 안의 명령문들은 호출되기 전까지는 실행되지 않는다. 

■ 함수가 호출되면, 함수 안에 있는 명령문들이 실행되며, 실행이 끝나면 호출한 위치로 되돌아간다. 

■ 매개변수(parameter)는 함수에 입력으로 전달되는 값을 받는 변수, 인수(argument)는 함수를 호출할 때 전달하는 입력값을 의미한다. 

■ 예를 들어, 다음과 같이 두 개의 값을 더하는 add 함수가 있다고 했을 때,

def add(a, b): # a, b는 매개변수
    result = a + b
    return result
result = add(2, 3) # 2, 3은 인수
print(result)
```#결과#```
5
````````````

■ result = add(2, 3)으로 add 함수를 호출하면, add 함수 내의 명령문들이 실행된다. 이때 인자는 정수 2와 3이다.

■ 인자인 정수 2와 3은 차례대로 add 함수의 매개변수 a와 b에 들어간다. 그러므로 add 함수 내의 명령문인 result = a(2) + b(3)이 되어 result의 값은 5가 된다. 최종적으로 result(5)를 return 키워드로 반환되고, add 함수는 종료된다. 

■ 그다음, 호출한 위치로 되돌아가서 함수 외부에 있는 result 변수에는 add(2, 3)의 결과인 5가 할당된다.

이러한 함수는 작성되면 몇 번이라도 호출할 수 있다. 이것이 함수의 가장 큰 장점이다. 

■ 예를 들어, 덧셈 계산이 3번 필요하다면 다음과 같이 add() 함수를 3번 호출하면 된다.

print(add(1, 10))
print(add(3, -2))
print(add(100, 1000))
```#결과#```
11
1
1100
````````````
result = (add(1, 10) - add(3, -2)) * add(100, 1000)
print(result)
```#결과#```
11000
````````````

■ 함수 정의 및 명령문의 순서에 주의해야 한다. 파이썬 인터프리터는 소스 코드를 한 줄씩 읽는다. 함수 안의 명령문은 함수가 호출될 때까지 실행되지 않으며, 함수 외부에 있는 문장은 즉시 실행된다. 그러므로 함수를 호출하기 전에 반드시 함수를 먼저 정의해야 한다. 

■ 만약, 다음과 같이 함수를 정의하기 전에 함수를 호출하면 해당 함수가 없다는 오류가 발생할 것이다.

flag = isVowel('Every')
print(flag)

def isVowel(word):
    word = word.upper()
    vowels = ['A', 'E', 'I', 'O', 'U']
    for vowel in vowels:
        if vowel not in word:
            return False
    return True

■ 그러나 다음과 같이 함수 내에서 아직 정의되지 않은 함수를 호출할 수는 있다. 

def main():
    flag = isVowel('Every')
    print(flag)

def isVowel(word):
    word = word.upper()
    vowels = ['A', 'E', 'I', 'O', 'U']
    for vowel in vowels:
        if vowel not in word:
            return False
    return True

main()

```#결과#```
False
````````````

■ isVowel() 함수가 main() 함수 뒤에 정의되어 있어도 isVowel() 함수는 main() 함수 내에서 호출된다. 

■ 먼저, 파이썬 인터프리터는 순서대로 main() 함수와 isVowel() 함수를 읽게 된다. 

그러므로 마지막 줄에서 main() 함수를 호출하면, main() 함수 내의 문장들이 실행되고 isVowel() 함수가 문제 없이 호출될 수 있다. main() 함수가 호출되는 시점은 이미 파이썬 인터프리터가 isVowel() 함수를 읽은 후의 시점이기 때문이다.


1.4 인자(argument)를 매개변수(parameter)로 전달하는 방법

■ 인수(argument)는 함수 호출 시 함수 헤더의 매개변수(parameter)에 전달되는 값, 매개변수는 이 값을 전달받는 변수이다.  매개변수의 개수는 인수의 개수와 정확히 일치해야 한다. 

■ 인자(또는 인수)를 파라미터로 전달하는 방법에는 위치 전달(pass by position), 키워드 전달(pass by keyword), 기본값 전달(pass by defalut value)이 있다.

1.4.1 위치 인자(positional argument)

■ 기본 인수 전달 방식이다. 위치 인자는 매개변수 위치와 일치시키는 인자를 위치 인자라고 부른다.

■ 함수 호출 시 전달되는 인자의 순서는 함수 헤더에 정의된 파라미터의 순서와 일치해야 한다. 

■ 즉, 첫 번째 인자는 첫 번째 파라미터에, 두 번째 인자는 두 번째 파라미터에, 세 번째 인자는 세 번째 파라미터에 전달되는 식이다. 

def add(a, b): 
    result = a + b
    return result

add(2, 3) # 2는 매개변수 a에, 3은 매개변수 b에 전달된다.

1.4.2 키워드 인자(keyword argument)

■ 키워드 인자는 인자들 앞에 키워드를 두어서 인자들을 구분하여 인자를 전달하는 방법이다.

- 매개변수의 이름을 명시적으로 지정해서 값을 매개변수에 전달할 수 있다. 

이 방법의 장점은 인수의 위치가 매개변수의 위치와 달라도 된다는 점이다. 즉, 키워드 인수를 사용한다면 다음과 같이 인자들이 어떤 순서로 전달되어도 상관없다.

def sub(x, y, z):
    return x - y - z
sub(z = 1, y = -2, x = 5)
```#결과#```
6
````````````

1.4.3 기본값 인자(default argument)

■ 함수 정의 시 매개변수에 기본값을 지정하여, 함수 호출 시 인자를 생략할 수 있다. 즉, 함수의 매개변수가 기본값을 가질 수 있다. 이것을 기본값(디폴트) 인자라고 한다. 

예를 들어, 다음과 같은 greet() 함수에서 매개변수 msg에 기본값으로 'hello'를 설정하였다. 이것이 기본값 인자이다. 이렇게 하면 greet() 함수 호출 시, 매개변수 msg에 인자를 전달하지 않아도 된다.

def greet(name, msg='Hello'):
    print(msg, name)
greet('John')
```#결과#```
Hello John
````````````

■ 위와 같이 위치 인자와 기본값 인자를 같이 사용할 수 있다. 단, 반드시 기본값 인자는 위치 인자 뒤에 위치해야 한다.

def greet(msg='Hello', name):
    print(msg, name)
```#결과#```
SyntaxError: non-default argument follows default argument
````````````

■ 키워드 인자와 위치 인자를 같이 사용할 경우에도 위치 인자는 키워드 인자 앞에 나와야 한다. 

def sub(x, y, z):
    return x - y - z
    
sub(z = 1, 2, 5)
```#결과#```
SyntaxError: positional argument follows keyword argument
````````````

sub(1, 2, z=5)
```#결과#```
-6
````````````

■ 위치 인자는 매개변수 위치와 순서대로 매핑되기 때문이다.

1.4.4 가변 인자

■ 매개변수로 *와 **을 이용하여 임의의 개수의 인자를 받을 수 있다. *는 위치 인자를 **는 키워드 인자를 가변 인자로 받는다.

■ 예를 들어 다음과 같이 매개변수 이름 앞에 *을 사용하여 가변 길이 인자를 함수에 전달할 수 있다.

def func1(*args):
    return args
func1(1)
```#결과#```
(1,)
````````````

result = func1(1)
type(result)
```#결과#```
tuple
````````````

func1(1, (2, 3), '4', 5, [6, 7], 8)
```#결과#```
(1, (2, 3), '4', 5, [6, 7], 8)
````````````
def func3(*args, x):
    return args, x
a, b = func3(1, 2, 3, 4, 5, x = 'A')
print(a); print(b)
```#결과#```
(1, 2, 3, 4, 5)
A
````````````

- func3에서 args와 x라는 두 개의 값을 반환하므로, 함수 호출 부분에서 두 개의 반환값을 두 개의 변수에 받아주면 된다.

■ *args 매개변수가 위치 인자들을 튜플 형태로 받아주는 것을 볼 수 있다. 그래서 반환 결과가 튜플이 나오는 것이다.

■ 매개변수 이름 앞에 **를 사용하여 가변 길이 키워드 인자를 나타낸다. 인자는 딕셔너리 형태로 전달된다. 

def func2(**kwargs):
    return kwargs
func2(age = 30, name = 'John', c = 2)
```#결과#```
{'age': 30, 'name': 'John', 'c': 2}
````````````

■ 위의 **kwargs처럼 매개변수 이름 앞에 **을 붙이면 함수 외부의 Key=Value 형태의 입력값은 함수를 호출하면, 함수 내의 딕셔너리 kwargs에 저장되는 것을 볼 수 있다. 


1.5 입력값과 리턴값에 따른 함수의 형태

■ 다음과 같이 입력값과 반환값이 있는 함수가 일반적인 형태의 함수 정의이다. 

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

함수 정의에서 파라미터와 return 명령은 선택사항이다. 

1.5.1 리턴값이 없는 함수

■ 값을 반환하지 않는 함수는 return 명령을 포함하지 않는 함수를 말한다. 

def msg(color):
    print('Hello')
    print(f'This color is {color}.')
msg('black')
```#결과#```
Hello
This color is black.
````````````

■ 위와 같은 함수는 사실 None 값을 반환하지만, 해당 값으로 수행할 수 있는 작업은 없다. 

result = msg('black')
```#결과#```
Hello
This color is black.
````````````

print(result)
```#결과#```
None
````````````

- result에 None 값이 할당된 것을 볼 수 있다. 

- None을 리턴한다는 것은 리턴값이 없다는 것이다.

1.5.2 입력값이 없는 함수

■ 입력값이 없는 함수도 존재할 수 있다.

def msg2():
    return 'white'
msg2()
```#결과#```
'white'
````````````
result = msg2()
print(result)
```#결과#```
white
````````````

1.5.3 입력값 & 리턴값이 없는 함수

■ 1.3의 예시 중 main() 함수는 매개변수와 return 명령이 없는 함수이다.

def main():
    flag = isVowel('Every')
    print(flag)

def isVowel(word):
    word = word.upper()
    vowels = ['A', 'E', 'I', 'O', 'U']
    for vowel in vowels:
        if vowel not in word:
            return False
    return True

main()

```#결과#```
False
````````````

■ 이러한 형식은 마지막 줄에서 main() 함수를 호출하여, 실행될 작업을 초기화한다. 

■ 예를 들어, msg1, msg2, msg3라는 3개의 함수가 있을 때, 이 3개의 함수를 위와 같은 형식으로 main() 함수 내에 넣으면 가독성이 좋아진다. 

def msg1():
    ...

def msg2():
    ...

def msg3():
    ...

def main():
    ...
    msg1()
    msg2()
    msg3()
    ...

main()

이러한 방법을 구조화 프로그래밍이라고도 한다. 구조화 프로그래밍은 큰 작업을 계속 나누어 설계하는 기법이며, 나누어진 부분이 너무 간단해서 함수로 구현될 수 있을 때까지 나눈다. 

즉, 각 함수들은 특징적인 한 가지 기능만을 수행할 때까지 나누는 것이다. 하나의 함수가 여러 작업을 하면 안 된다. 


1.6 순환(recursion)

■ 순환은 어떤 알고리즘이나 함수가 자기 자신을 호출하여 문제를 해결하는 프로그래밍 기법이다. 

■ 순환은 본질적으로 정수의 팩토리얼과 같이 순환적인 문제에 적합하다. 

\( n! = 
\begin{cases}
1 & \text{if } n = 0 \\
n \cdot (n-1)! & \text{if } n \geq 1
\end{cases} \)

■ 팩토리얼 \( n! \)을 정의하는데 다시 팩토리얼 \( \left( n-1 \right) ! \)이 사용되는 것을 볼 수 있다. 이러한 정의를 순환적인 정의라고 한다. 

def factorial(n):
    if n==1:
        return 1
    else:
        return n*factorial(n-1)

- factorial 함수는 n의 값이 1이 아니라면 else 문이 실행되어 n*factorial(n-1)이 수행된다. 이때, 다시 factorial 함수가 호출된다. 이 과정이 반복되어 펙토리얼 값을 계산할 수 있는 것이다.


1.7 변수의 범위

■ 함수 내에서 생성된 변수는 해당 함수 내에서 명령으로 접근할 수 있지만, 해당 함수의 실행이 끝날 때 존재하지 않게 된다. 함수 내의 변수는 해당 함수가 호출될 때마다 재생성되는 것으로 볼 수 있다.

예를 들어 다음과 같이 함수 내에 변수 x가 있고 함수 외부에 변수 x가 있다고 했을 때

def func():
    x = 10
    print(x)

x = 20
func()
```#결과#```
10
````````````

print(x)
```#결과#```
20
````````````

■ 함수 안에서 생성되는 변수를 지역 변수(local variable)라고 하며, 지역 변수는 함수가 종료되면 사라진다. 그러므로 지역 변수를 함수 외부에서 사용하려고 하면 오류가 발생한다.

■ 반면, 함수 외부에서 생성된 변수는 어디에서나 사용할 수 있다. 이러한 변수를 전역 변수(global variable)라고 한다. 즉, 모든 함수는 다음 예시처럼 전역 변수의 값을 사용할 수 있다.

def func(x):
    print(x)

a = 30 # 전역 변수
func(a) # 함수 호출
```#결과#```
30
````````````

위의 예시처럼 지역 변수와 전역 변수가 동일한 변수명을 갖는다고 해도, 두 변수는 서로 어떠한 관계도 갖고 있지 않다. 즉, 완전히 다른 변수로 처리된다. 

1.7.1 global 키워드

■ 함수 안에서 전역 변수의 값을 변경하고 싶은 경우 global이라는 키워드를 사용하면 된다. 

def func():
    global x
    print(x)

x = 20

func()
```#결과#```
20
````````````

print(x)
```#결과#```
20
````````````