profile image

L o a d i n g . . .

unit 29. 함수 사용하기

 

  • Hello, world! 출력 함수 만들기

함수(function)는 특정 용도의 코드를 한 곳에 모아 놓은 것을 뜻한다. 

함수의 장점은 다음과 같다.

- 코드의 용도를 구분할 수 있다.

- 코드를 재사용할 수 있다.

- 실수를 줄일 수 있다.

 

함수는 def에 함수 이름을 지정하고 ( )(괄호)와 :(콜론)을 붙인 뒤 다음 줄에 원하는 코드를 작성한다.(함수의 이름을 짓는 방법은 변수와 같다)

def 함수이름():
     코드

def는 정의하다(define)에서 따온 키워드이다.

 

 

* 함수 만들기

다음은 'Hello, world!'를 출력하는 함수이다.

>>> def hello():
...      print('Hello, world!')
... 

 

 

* 함수 호출하기

함수를 만든 부분 아래에서 hello()와 같이 함수 이름과 ()를 적어주면 함수를 사용할 수 있다.

>>> hello()
Hello, world!

 

 

* 함수의 실행 순서

파이썬 스크립트에서 함수(hello 함수)의 실행 순서는 다음과 같다. 

1. 파이썬 스크립트 최초 실행

2. hello 함수 호출

3. hello 함수 실행

4. print 함수 실행 및 'Hello, world!' 출력

5. hello 함수 종료

6. 파이썬 스크립트 종료

 

 

* 함수 작성과 함수 호출 순서

함수를 만들고 호출할 때 주의할 점이 있는데, 바로 함수를 만들기 전에 함수를 먼저 호출하면 안 된다는 점이다. 즉, 다음과 같이 함수를 먼저 호출한 뒤 함수를 만들 수는 없다.

hello()         # hello 함수를 만들기 전에 함수를 먼저 호출
 
def hello():    # hello 함수를 만듦
    print('Hello, world!')

함수를 먼저 호출하면 함수가 정의(define)되지 않았다는 에러가 발생한다.

파이썬 코드는 위에서 아래로 순차적으로 실행되기 때문이다. 따라서 반드시 함수를 먼저 만든 뒤에 함수를 호출해야 한다.

 

 

  • 덧셈 함수 만들기

더보기

이번에는 함수에 값을 넣어서 동작을 바꿔보겠다.

함수에서 값을 받으려면 ( )(괄호) 안에 변수 이름을 지정해주면 된다. 특히 이 변수를 매개변수(parameter)라고 부른다.

def 함수이름(매개변수1, 매개변수2):
    코드

 

다음은 두 수를 더하는 함수이다.

다음과 같이 함수의 괄호 안에 매개변수 a b를 지정하고, 그다음 줄에서 print a b의 합을 출력한다.

>>> def add(a, b):
...     print(a + b)

>>> add(10, 20)
30

add 함수가 만들어졌으니, add 함수에 10과 20을 넣어서 호출할 수 있다. 호출 결과 10과 20을 더한 값인 30이 출력된다.

여기서 함수를 호출할 때 넣는 값을 인수(argument)라고 부른다. 즉, add(10, 20)에서 10과 20이 인수이다.

 

* add 함수의 호출 과정은 다음과 같다.

 

 

  • 함수의 결과를 반환하기

더보기

함수 안에서 return을 사용하면 값을 함수 바깥으로 반환한다. (return에 값을 지정하지 않으면 None을 반환)

def 함수이름(매개변수):
    return 반환값

 

다음은 두 수를 더해서 반환하는 add 함수이다. 함수 add에 매개변수에 a b를 지정하고 그다음 줄에서 return으로 a b를 더한 값을 반환하도록 만든다.

>>> def add(a, b):
...     return a + b
...

add 함수에 10과 20을 넣고 결과를 x에 저장한 다음, x의 값을 출력해보면 10과 20을 더한 값인 30이 나온다.

이처럼 return을 사용하면 값을 함수 바깥으로 반환할 수 있고, 함수에서 나온 값을 변수에 저장할 수 있다.

그리고 return으로 반환하는 값은 반환값이라고 하며 함수를 호출해준 바깥에 결과를 알려주기 위해 사용한다.

 

add 함수의 호출 과정은 다음과 같다.

+ 반환값은 변수에 저장하지 않고 바로 다른 함수에 넣을 수도 있다.  

 

* return으로 함수 중간에서 빠져나오기

return은 값을 반환하는 기능뿐만 아니라 함수 중간에서 바로 빠져나오는 기능도 있다.

다음은 매개변수 a가 10이면 함수를 그냥 빠져나온다.

>>> def not_ten(a):
...     if a == 10:
...         return
...     print(a, '입니다.', sep='')
...
>>> not_ten(5)
5입니다.
>>> not_ten(10)
>>> 

이처럼 return은 함수 중간에서 빠져나올 때 자주 사용하며, 보통은 if와 조합해서 특정 조건일 때 함수 중간에서 빠져나온다.

 

  • 함수에서 값을 여러 개 반환하기

더보기

함수에서 값을 여러 개 반환할 때는 다음과 같이 return에 값이나 변수를 ,(콤마)로 구분해서 지정하면 된다.

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

 

다음은 두 수를 더한 값과 뺀 값을 반환하는 함수이다.

다음과 같이 return으로 a b를 더한 값과 a에서 b를 뺀 값을 반환하도록 만든다.

>>> def add_sub(a, b):
...     return a + b, a - b
...

add_sub 함수에 10과 20을 넣고 결과를 x y에 저장한 후 x y의 값을 출력해보면 다음과 같다.

>>> x, y = add_sub(10, 20)
>>> x
30
>>> y
-10

x의 값을 출력해보면 10과 20을 더한 값인 30이 나오고, y의 값을 출력해보면 10에서 20을 뺀 값인 -10이 나온다.

이처럼 return은 값을 여러 개 반환할 수 있으며, 그것을 각 변수에 저장할 수 있다.

 

add_sub 함수의 호출 과정은 다음과 같다.

 

그런데 return으로 값을 여러 개 반환하면 실제론 튜플이 반환된다.

다음과 같이 add_sub의 결과를 변수 한 개에 저장해서 출력해보면 튜플이 반환되는 것을 볼 수 있다.

>>> x = add_sub(10, 20)
>>> x
(30, -10)

즉, 앞에서 튜플이 변수 여러 개에 할당되는 특성을 이용한 것이다.(언패킹)

 

  • 함수의 호출 과정 알아보기

더보기

함수 여러 개를 만든 뒤에 각 함수의 호출 과정을 스택 다이어그램(stack diagram)이라고 한다.

스택은 접시 쌓기와 같은데, 파이썬에서는 함수가 아래쪽 방향으로 추가되고 함수가 끝나면 위쪽 방향으로 사라짐.

 

다음은 덧셈 함수 add와 곱셈 함수 mul이 있고, add 함수 안에서 mul 함수를 호출하는 방식으로 만들어져 있다.

def mul(a, b):
    c = a * b
    return c
 
def add(a, b):
    c = a + b
    print(c)
    d = mul(a, b)
    print(d)
 
x = 10
y = 20
add(x, y)

실행 결과는 다음과 같다.

30
200

 

소스코드를 보면 먼저 줄 1부터 실행한다. 여기서 줄 12 y = 20까지 실행하면 전역 프레임(global frame)에는 함수 mul, add 변수 x, y가 들어간다(함수 mul, add는 생성만 되었을 뿐 호출이 되지 않은 상태임).

프레임이란 메모리에서 함수와 함수에 속한 변수가 저장되는 독립적인 공간입니다. 특히 전역 프레임은 파이썬 스크립트 전체에서 접근할 수 있어서 전역 프레임이라 부른다.

 

이제 함수 add를 호출한 뒤 안으로 들어가서 줄 7 print(c)까지 실행하면 다음과 같은 모양이 된다.

즉, 함수 add의 스택 프레임이 만들어지고 거기에 매개변수 a b 그리고 변수 c가 들어간다.

여기서 함수 mul을 호출한 뒤 안으로 들어가서 줄 3 return c까지 실행하면 함수 mul의 스택 프레임이 만들어지고 매개변수 a b 그리고 변수 c가 들어가게된다.

 

이제 줄 9 print(d)까지 실행하면 함수 mul을 끝내고 함수 add로 되돌아간다.

이렇게 하면 mul에서 반환한 반환값 200이 변수 d에 저장되며 add의 스택 프레임에 들어간다. 물론 mul은 끝났으므로 스택 프레임도 사라진다.

 다음에 줄 13 add(x, y)까지 실행해서 함수 add를 끝낸다. 이렇게 하면 add의 스택 프레임도 사라진다.

 

※ 정리 : 지금까지 설명한 과정처럼 함수는 스택(stack) 방식으로 호출된다. 즉, 함수를 호출하면 스택의 아래쪽 방향으로 함수가 추가되고 함수가 끝나면 위쪽 방향으로 사라진다.

그리고 프레임은 스택 안에 있어서 각 프레임을 스택 프레임이라고 부른다.

참고로 전역 프레임은 스크립트 파일의 실행이 끝나면 사라진다.

 

 

 

unit 30. 함수에서 위치 인수와 키워드 인수 사용하기

 

  • 위치 인수와 리스트 언패킹 사용하기

더보기

함수에 인수를 순서대로 넣는 방식위치 인수(positional argument)라고 한다. 즉, 인수의 위치가 정해져 있다는 것이다.

>>> print(10, 20, 30)
10 20 30

print에 10, 20, 30 순으로 넣었으므로 출력될 때도 10 20 30으로 출력되는 것이 그 예이다.

 

* 위치 인수를 사용하는 함수를 만들고 호출하기

다음은 숫자 세 개를 각 줄에 출력하는 함수이다.

>>> def print_numbers(a, b, c):
...     print(a)
...     print(b)
...     print(c)
...

print_numbers에 숫자 세 개를 넣으면 각 줄에 숫자가 력된다.

 

* 언패킹 사용하기

인수를 순서대로 넣을 때는 리스트나 튜플을 사용할 수도 있다.

다음과 같이 리스트 또는 튜플 앞에 *(애스터리스크)를 붙여서 함수에 넣어주면 된다.

- 함수(*리스트)

- 함수(*튜플)

>>> x = [10, 20, 30]
>>> print_numbers(*x)
10
20
30

즉, 리스트(튜플) 앞에 *를 붙이면 언패킹(unpacking)이 되어서 print_numbers(10, 20, 30)과 똑같은 동작이 된다. 말 그대로 리스트의 포장을 푼다는 뜻이다.

단, 이때 함수의 매개변수 개수와 리스트의 요소 개수는 같아야 하는데, 만약 개수가 다르면 함수를 호출할 수 없다. 

 

 

* 가변 인수 함수 만들기

위치 인수와 리스트 언패킹 기능은 인수의 개수가 정해지지 않은 가변 인수(variable argument)에 사용된다.

 

일단 다음과 같이 가변 인수 함수는 매개변수 앞에 *를 붙여서 만든다.

def 함수이름(*매개변수):
    코드

 

다음은 숫자 여러 개를 받고, 숫자를 각 줄에 출력하는 함수이다.

>>> def print_numbers(*args):
...     for arg in args:
...         print(arg)
...

print_numbers 함수에 숫자를 넣어서 호출해보면, 숫자를 한 개 넣으면 한 개 출력되고, 네 개 넣으면 네 개가 출력된다.

즉, 넣은 숫자 개수만큼 출력된다.

>>> print_numbers(10)
10
>>> print_numbers(10, 20, 30, 40)
10
20
30
40

 

그리고 이때 리스트(튜플) 언패킹을 사용해도 된다. 다음과 같이 숫자가 들어있는 리스트를 만들고 앞에 *를 붙여서 넣어본다.

>>> x = [10]
>>> print_numbers(*x)
10
>>> y = [10, 20, 30, 40]
>>> print_numbers(*y)
10
20
30
40

※ 정리 : 이처럼 함수를 만들 때 def print_numbers(*args):와 같이 매개변수에 *를 붙여주면 가변 인수 함수를 만들 수 있다.

그리고 이런 함수를 호출할 때는 인수를 각각 넣거나, 리스트(튜플) 언패킹을 사용하면 된다.

 

  • 키워드 인수 사용하기

더보기

보통은 함수를 사용할 때 인수의 순서와 용도를 함께 알아야 한다.

예를 들어 개인 정보를 출력하는 함수가 있다.

>>> def personal_info(name, age, address):
...     print('이름: ', name)
...     print('나이: ', age)
...     print('주소: ', address)
...

이 함수를 사용할 때는 첫 번째 인수에 이름(name), 두 번째 인수에 나이( age), 세 번째 인수에 주소(address)를 넣어야 하며, 만약 인수의 순서가 달라지면 잘못된 결과가 출력된다.

 

이처럼 인수의 순서와 용도를 모두 기억해야 해서 불편하다. 그래서 파이썬에서는 인수의 순서와 용도를 매번 기억하지 않도록 키워드 인수(keyword argument)라는 기능을 제공한다.

 

키워드 인수는 말 그대로 인수에 이름(키워드)을 붙이는 기능인데 키워드=값 형식으로 사용한다.

- 함수(키워드=값)

 

다음은 personal_info 함수를 키워드 인수 방식으로 호출한 것이다.

>>> personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
이름:  홍길동
나이:  30
주소:  서울시 용산구 이촌동

키워드 인수를 사용하니 함수를 호출할 때 인수의 용도가 명확하게 보인다. 특히 키워드 인수를 사용하면 인수의 순서를 맞추지 않아도 키워드에 해당하는 값이 들어간다는 장점이 있다.

 

  • 키워드 인수와 딕셔너리 언패킹 사용하기

더보기

앞에서 함수를 호출할 때 키워드 인수로 직접 값을 넣었는데, 딕셔너리를 사용해면 키워드 인수로 값을 넣는 딕셔너리 언패킹이 가능하다.

다음과 같이 딕셔너리 앞에 **(애스터리스크 두 개)를 붙여서 함수에 넣어준다.

- 함수(**딕셔너리)

 

 다음은 personal_info 함수이다.

>>> def personal_info(name, age, address):
...     print('이름: ', name)
...     print('나이: ', age)
...     print('주소: ', address)
...

이제 딕셔너리에 '키워드': 값 형식으로 인수를 저장하고, 앞에 **를 붙여서 함수에 넣어준다.

이때 딕셔너리의 키워드(키)는 반드시 문자열 형태라야 한다.

>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**x)
이름:  홍길동
나이:  30
주소:  서울시 용산구 이촌동

딕셔너리에 저장된 값들이 잘 출력됨을 알 수 있다.

**x처럼 딕셔너리를 언패킹하면 딕셔너리의 값들이 함수의 인수로 들어간다. 즉, personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동') 또는 personal_info('홍길동', 30, '서울시 용산구 이촌동')과 똑같이 동작한다.

 

※ 주의 : 딕셔너리 언패킹을 사용할 때는 함수의 매개변수 이름과 딕셔너리의 키 이름이 같아야 한다. 또한, 매개변수 개수와 딕셔너리 키의 개수도 같아야 한다. 만약 이름과 개수가 다르면 함수를 호출할 수 없다.

 

 

**를 두 번 사용하는 이유

딕셔너리 언패킹에 **를 두 번 사용하는 이유는 딕셔너리는 키-값 쌍 형태로 값이 저장되어 있기 때문이다.

먼저 *를 한 번만 사용해서 함수를 호출해보면 다음과 같다.

>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(*x)
이름:  name
나이:  age
주소:  address

personal_info *x를 넣으면 x의 키가 출력된다.

즉, 딕셔너리를 한 번 언패킹하면 키를 사용한다는 뜻이다. 따라서 **처럼 딕셔너리를 두 번 언패킹하여 값을 사용하도록 만들어야 한다.

 

 

키워드 인수를 사용하는 가변 인수 함수 만들기

다음과 같이 키워드 인수를 사용하는 가변 인수 함수는 매개변수 앞에 **를 붙여서 만든다.

def 함수이름(**매개변수):
    코드

 

다음은 값 여러 개를 받아서 매개변수 이름과 값을 각 줄에 출력하는 함수이다.

함수를 만들 때 괄호 안에 **kwargs와 같이 매개변수 앞에 **를 붙인다.

>>> def personal_info(**kwargs):
...     for kw, arg in kwargs.items():
...         print(kw, ': ', arg, sep='')
...

특히 이 kwargs는 딕셔너리라서 for로 반복할 수 있다.

personal_info 함수에 키워드와 값을 넣어서 실행하면 다음과 같다. 

>>> personal_info(name='홍길동')
name: 홍길동
>>> personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
name: 홍길동
age: 30
address: 서울시 용산구 이촌동

이렇게 인수를 직접 넣어도 되고, 딕셔너리 언패킹을 사용해도 된다.

다음과 같이 딕셔너리를 만들고 앞에 **를 붙여서 넣으면 된다.

>>> x = {'name': '홍길동'}
>>> personal_info(**x)
name: 홍길동
>>> y = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**y)
name: 홍길동
age: 30
address: 서울시 용산구 이촌동

※ 정리 : 함수를 만들 때 def personal_info(**kwargs):와 같이 매개변수에 **를 붙여주면 키워드 인수를 사용하는 가변 인수 함수를 만들 수 있다. 그리고 이런 함수를 호출할 때는 키워드와 인수를 각각 넣거나 딕셔너리 언패킹을 사용하면 된다.

 

  • 매개변수에 초깃값 지정하기
더보기

이함수의 매개변수에 초깃값을 지정하면 인수를 생략할 수 있다.

초깃값은 다음과 같이 함수를 만들 때 매개변수=값 형식으로 지정한다.

def 함수이름(매개변수=값):
    코드

매개변수의 초깃값은 주로 사용하는 값이 있으면서 가끔 다른 값을 사용해야 할 때 활용한다.

 

personal_info 함수에서 매개변수 address의 초깃값을 '비공개'로 지정해보면 다음과 같다.

>>> def personal_info(name, age, address='비공개'):
...     print('이름: ', name)
...     print('나이: ', age)
...     print('주소: ', address)
...

address는 초깃값이 있으므로 personal_info는 다음과 같이 address 부분을 비워 두고 호출할 수 있다.

>>> personal_info('홍길동', 30)
이름:  홍길동
나이:  30
주소:  비공개

또한 매개변수에 초깃값이 지정되어 있더라도 값을 넣으면 해당 값이 전달된다.

 

* 초깃값이 지정된 매개변수의 위치

※ 주의 : 초깃값이 지정된 매개변수 다음에는 초깃값이 없는 매개변수가 올 수 없다.

 

예를 들어 personal_info 함수에서 address를 두 번째 매개변수로 만들고, 그 다음에 초깃값을 지정하지 않은 age가 오도록 만들어보면 다음과 같다.

>>> def personal_info(name, address='비공개', age):
...     print('이름: ', name)
...     print('나이: ', age)
...     print('주소: ', address)
...
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument

그런데 함수를 만들어보면 문법 에러가 발생한다.

왜냐하면 함수를 이렇게 만들어버리면 personal_info('홍길동', 30)으로 함수를 호출했을 때 30이 어디로 들어가야 할지 알 수가 없기 때문이다. address에 들어가려니 age 부분이 비어 버린다.

 

즉, 다음과 같이 초깃값이 지정된 매개변수는 뒤쪽에 몰아줘야 한다.

def personal_info(name, age, address='비공개'):
def personal_info(name, age=0, address='비공개'):
def personal_info(name='비공개', age=0, address='비공개'):

+ def personal_info(name='비공개', age=0, address='비공개'):와 같이 모든 매개변수에 초깃값을 지정하면 personal_info()처럼 인수를 넣지 않고 호출할 수 있다.

 

 

 

unit 31. 함수에서 재귀호출 사용하기

 

  • 재귀호출 사용하기

더보기

함수 안에서 함수 자기자신을 호출하는 방식재귀호출(recursive call)이라고 한다.

 

다음은 간단한 재귀호출 함수이다.

def hello():
    print('Hello, world!')
    hello()
 
hello()

hello 함수 안에서 다시 hello 함수를 호출하고 있는데, 소스 코드를 실행해보면 'Hello, world!' 문자열이 계속 출력되다가 에러(RecursionError)가 발생한다.

hello 함수가 자기자신을 계속 호출하다가 최대 재귀 깊이를 초과하였기 때문이다.

 

* 재귀호출에 종료 조건 만들기

따라서 재귀호출을 사용하려면 반드시 다음과 같이 종료 조건을 만들어주어야 한다.

def hello(count):
    if count == 0:    # 종료 조건을 만듦. count가 0이면 다시 hello 함수를 호출하지 않고 끝냄
        return
    
    print('Hello, world!', count)
    
    count -= 1      # count를 1 감소시킨 뒤
    hello(count)    # 다시 hello에 넣음
 
hello(5)    # hello 함수 호출

실행 결과는 다음과 같다.

Hello, world! 5
Hello, world! 4
Hello, world! 3
Hello, world! 2
Hello, world! 1

먼저 hello 함수의 반복 횟수를 계산하기 위해 매개변수 count를 지정한다. 그리고 count가 0이면 hello 함수를 호출하지 않고 끝낸다. 만약 0이 아니면 'Hello, world!'를 출력하고, count의 값을 1씩 감소시킨 뒤 hello 함수를 호출할 때 넣어준다.

 

  • 재귀호출로 팩토리얼 구하기

더보기

팩토리얼은 1부터 n까지 양의 정수를 차례대로 곱한 값이며 !(느낌표) 기호로 표기한다.

 

다음은 팩토리얼을 재귀 함수로 구현한 것이다. 

def factorial(n):
    if n == 1:      # n이 1일 때
        return 1    # 1을 반환하고 재귀호출을 끝냄
    return n * factorial(n - 1)    # n과 factorial 함수에 n - 1을 넣어서 반환된 값을 곱함
 
print(factorial(5))

실행 결과는 120이다.

 

먼저 factorial 함수를 만들 때 매개변수 n을 지정해준다.

팩토리얼은 1부터 n까지의 곱을 구하는 문제인데 여기서는 n부터 역순으로 1씩 감소하면서 재귀호출을 하고 n이 1이 되었을 때 재귀호출을 중단한다.

 

factorial 함수의 핵심은 바로 반환값 부분이다. 계산 결과가 즉시 구해지는 것이 아니라 재귀호출로 n - 1을 계속 전달하다가 n이 1일 때 비로소 1을 반환하면서 n과 곱하고 다시 결괏값을 반환한다. 그 뒤 n과 반환된 결괏값을 곱하여 다시 반환하는 과정을 반복한다.

 

factorial 함수의 호출 순서와 계산 과정는 다음과 같다. n이 1이 될 때까지 재귀호출 한 후, n이 1일 때 비로소 1을 반환하면서 거꾸로 올라오며 반환값을 구하는 것이다.

복사했습니다!