함수

함수는 여러 명령어들을 계속해서 반복 사용할 필요가 있을 때 사용한다. 가령 학생의 국어, 영어, 수학 시험 점수의 합계와 평균을 알고 싶으면 다음과 같이 프로그램을 작성할 수 있다.

국어 = 90
영어 = 95
수학 = 92

합계 = 국어 + 영어 + 수학
평균 = 합계 / 3
print("합계:", 합계)
print("평균:", 평균)

하지만 다른 학생의 성적의 합계와 평균을 구하려고 하면 위의 코드를 반복해서 작성해 주어야 한다.

# 지원 성적
지원국어 = 90
지원영어 = 95
지원수학 = 92

지원합계 = 지원국어 + 지원영어 + 지원수학
지원평균 = 지원합계 / 3
print("합계:", 지원합계)
print("평균:", 지원평균)

# 지수 성적
지수국어 = 93
지수영어 = 90
지수수학 = 96

지수합계 = 지수국어 + 지수영어 + 지수수학
지수평균 = 지수합계 / 3
print("합계:", 지수합계)
print("평균:", 지수평균)

이와 같이 코딩하는 것은 지루하고 성가신 작업이다. 반복 사용되는 구역을 하나의 단위로 묶어 달라지는 것만 입력하게 프로그램을 만들 수 있는 것이 함수이다.

함수 정의

함수는 예약어 def 뒤에 함수이름, 소괄호 (), 콜론 :을 차례로 적고 다음 줄부터 들여쓰기를 해서 함수 구역을 작성한다. 정의된 함수를 부를 때는 함수이름과 괄호()를 사용한다. 괄호 안에는 함수에서 사용될 변수이름을 쉼표(,)를 이용하여 나열할 수 있다. 소괄호 안에 사용되는 변수이름을 함수의 매개변수(parameter)라고 부른다. 만일 함수에서 매개변수가 필요하지 않다면 적지 않아도 된다.

def <함수이름>(<매개변수1, 매개변수2, ..., 매개변수n>): # 매개변수는 필요없으면 안 적어도 된다.
    <함수몸통>

매개 변수 없는 함수

다음과 같이 매개변수가 없는 간단한 함수를 작성해보자.

In [125]:
def 안녕():
    print("안녕하세요")

함수를 정의하기 위해서는 항상 def라는 예약어로 시작해야 한다. 그리고 함수이름 안녕이 나오고 다음으로 반드시 괄호 ()를 열고 닫고 콜론 :으로 함수 머리부분을 마친다. 함수의 몸통은 다음 줄 들여쓰기와 함께 시작되고 들여쓰기 구역이 끝나면 함수의 선언이 끝이 난다. 다음과 같이 함수이름과 괄호를 입력해서 함수를 호출할 수 있다.

In [126]:
안녕()
안녕하세요

매개변수가 있는 함수

위에서 작성했던 합계와 평균을 구하는 문제를 함수를 사용해서 바꿔보자. 위에서 학생들이 바뀌면 점수가 달라지지만 합계와 평균을 구하는 방법과 출력하는 부분은 같은 것을 알 수 있다. 학생들에 따라 바뀌는 국어, 영어, 수학 점수를 함수의 매개변수로 설정하고 그 값에 따른 합계, 평균을 출력하는 방법은 동일하므로 이 부분을 함수의 몸통에서 계산하게 한다.

In [127]:
def 합계_평균(, , ):
     =  +  + 
     =  / 3
    print("합계: {}, 평균: {:.2f}".format(, ))

함수이름은 합계_평균이고 매개변수는 각각 , , 이다. 이와 같이 함수이름, 매개변수와 같은 식별자(identifier)는 한글을 사용해도 된다.

함수를 호출하기 위해서는 함수이름과 함께 매개변수에 대응되는 값들을 괄호 안에 넣어주면 된다.

함수를 부를 때 괄호 안에 들어가는 값들을 `인자 <https://docs.python.org/3/glossary.html#term-argument>`__(argument)라고 부른다.

In [128]:
합계_평균(90, 95, 92)
합계: 277, 평균: 92.33

여기서 인자들은 각각, 90, 95, 92가 된다.

함수는 필요할 때 언제든지 부를 수 있다. 두 번째 부를 때 합계_평균(93, 90, 96) 함수의 인자에 다른 학생의 점수를 넣으면 그 학생의 합계와 평균이 출력이 된다.

In [129]:
합계_평균(93, 90, 96)
합계: 279, 평균: 93.00

정리하면 매개변수는 함수를 정의할 때 괄호 안에 사용되는 식별자이고 인자는 함수를 부를 때 괄호 안에 넣는 값을 의미한다.

위 코드에서 학생의 이름도 같이 넘겨주어 출력하면 알아보기 편리할 것이다.

In [130]:
def 나은_합계_평균(이름, , , ):
     =  +  + 
     =  / 3
    print("{}의 성적:\n국어: {}, 영어: {}, 수학: {}".format(이름, , , ))
    print("합계: {}".format())
    print("평균: {:.1f}".format())

나은_합계_평균("지원", 90, 95, 92)
나은_합계_평균("지수", 93, 90, 96)
지원의 성적:
국어: 90, 영어: 95, 수학: 92
합계: 277
평균: 92.3
지수의 성적:
국어: 93, 영어: 90, 수학: 96
합계: 279
평균: 93.0

print("평균: {0:.1f}".format(평))에서.1f을 보여줄 때 부동 소수점 아래 한 자리만 표시하라는 뜻이다.

직접하기

  • 국어, 영어, 수학 점수를 하나의 리스트로 입력받아 합계와 평균을 구하는 함수를 작성하고 실행하시오. 함수의 이름과 매개변수를 리스트_합계_평균(국영수)와 같이 하시오.

직사각형 그리기 예제

거북이를 이용하여 다양한 크기의 직사각형을 그리기를 원한다고 해보자. 가로, 세로의 길이가 변할 때마다 새로운 코드를 작성해 주어야 할 것이다.

import turtle

연필 = turtle.Pen()

# 가로 300, 세로 200인 직사각형
연필.fd(300) # fd()는 forward()의 줄임말
연필.left(90)
연필.fd(200)
연필.left(90)
연필.fd(300)
연필.left(90)
연필.fd(200)
연필.left(90)

# 가로 200, 세로 100인 직사각형
연필.fd(200)
연필.left(90)
연필.fd(100)
연필.left(90)
연필.fd(200)
연필.left(90)
연필.fd(100)
연필.left(90)

가로, 세로의 길이를 입력받아 직사각형을 그리는 함수를 만들면 매번 코드를 만들 필요가 없어질 것이다.

import turtle

연필 = turtle.Pen()

def 직사각형(가로, 세로): # 함수 이름은 직사각형
    for _ in range(2):
        연필.fd(가로)
        연필.left(90)
        연필.fd(세로)
        연필.left(90)

직사각형(300, 200)
직사각형(200, 100)
  • 함수이름을 직사각형이라 짓고 매개변수로 가로, 세로를 설정한다.
  • for문을 이용해서 가로, 세로 그리기를 2번 반복하여 직사각형을 그린다.

직접하기

  • 한 변의 길이를 매개변수로 하는 정삼각형을 그리는 함수를 만들어, 그 함수를 이용해 길이가 50, 100, 150 인 정삼각형을 만들자.

  • 원 둘레를 네 가지 색깔(빨강, 파랑, 초록, 노랑)로 색칠하는 함수 프로그램을 만드시오. 함수는 반지름을 매개변수로 갖고 원의 중심은 (0,0)이 되게 작성하시오. 이 함수를 4번 반복실행하여 반지름이 50, 100, 150, 200인 다음과 같은 그림을 그리시오.

매개변수와 인자

매개변수는 핵심어(keyword) 매개변수와 위치(positional) 매개변수로 나눌 수 있다. 핵심어 매개변수는 매개변수=값과 같이 할당 기호 =를 사용하는 변수를 말하고, 그렇지 않은 매개변수를 위치 매개변수라고 한다. 인자란 함수를 부를 때 매개변수에 대응되어 넘겨주는 값을 말한다.

위치 매개변수

위치 매개변수는 할당 기호 =를 사용하지 않는 매개변수를 의미한다.

In [131]:
def 말하기(문장1, 문장2):
    print(문장1, 문장2)

말하기("안녕하세요.", "반갑습니다.")
안녕하세요. 반갑습니다.

말하기 함수에서 소괄호 안에 있는 문장1, 문장2가 위치 매개변수가 된다.

핵심어 매개변수

어떤 경우에는 함수를 부를 때 인자를 선택적으로 넘겨주어 사용자가 값을 넘겨주지 않으면 자동으로 기본값을 사용하도록 하는 것이 편할 때가 있다. 이런 경우, 핵심어(keyword) 매개변수에 기본값(default value)을 지정하면 된다. 함수를 선언할 때 원하는 핵심어 매개변수 뒤에 할당 연산자(=)와 기본값을 입력하여 지정한다.

def 말하기(문장, 횟수=1): # 횟수는 기본 매개변수, 1은 기본값
    print(문장 * 횟수) # 문장을 횟수만큼 출력한다.

말하기("안녕하세요. 예은님!") # 기본 매개변수를 지정하지 않았기 때문에 기본값 1회 출력
말하기("예은", 5) # 기본 매개변수 부분에 5를 지정함으로 5회 출력
  • def 말하기(문장, 횟수=1): 중에서 횟수이 기본 매개변수이고 기본값은 1로 설정한다.
  • 말하기("안녕하세요. 예은님!")에서 말하기() 함수를 부를 때 매개변수 횟수에 해당되는 부분이 입력되지 않았으므로 횟수는 기본값 1을 사용하게 되어 한 번만 출력한다.
  • 말하기("예은", 5)에서 "예은" 다음에 오는 5가 핵심어 매개변수 횟수에 해당하는 인자값으로 5회를 출력하게 된다.

직접하기

  • 직사각형 그리기에서 매개변수로 가로, 세로 길이와 기본 매개변수로 선두께=1, 선색깔="red"을 기본 인자값으로 갖는 함수 프로그램을 작성하고 실행하시오.

핵심어 인자(keyword argument)

함수를 부를 때 괄호 안에 사용되는 인자(argument)핵심어 인자(keyword argument)와 위치 인자(positional argument)로 나눌 수 있다. 핵심어 인자란 매개변수(또는 식별자) = 과 같이 사용되는 인자를 말하고 그렇지 않은 모든 인자를 위치 인자라고 한다. 말하기() 함수 예제를 살펴보자.

In [132]:
def 말하기(문장, 횟수=1):
    print(문장 * 횟수)

말하기(문장="안녕하세요. 예은님!") # 키워드 인자
말하기("예은", 5) # 위치 인자
말하기(횟수=5, 문장="예은") # 키워드 인자를 사용하여 인자 순서 변경
안녕하세요. 예은님!
예은예은예은예은예은
예은예은예은예은예은
  • 말하기(문장="안녕하세요. 예은님!") 에서 문장="안녕하세요. 예은님!"이 키워드 인자이다. 문장이 매개변수 이름(식별자)이고 "안녕하세요. 예은님!"이 인자값이다.
  • 말하기("예은", 5)에서 인자 "예은", 5는 위치인자이다.
  • 말하기(횟수=5, 문장="예은")에서와 같이 키워드 인자를 사용하면 순서를 바꿀 수 있다.

위치 인자는 반드시 키워드 인자 앞에 위치해야 한다. 다음과 같이 키워드 인자 뒤에 위치 인자를 사용하면 에러가 발생한다.

In [133]:
말하기(횟수=5, "예은")
  File "<ipython-input-133-cabeff611f37>", line 1
    말하기(횟수=5, "예은")

^
SyntaxError: positional argument follows keyword argument

핵심어 변수와 가변 객체

핵심어 변수의 기본값으로 가변 객체를 사용할 때 주의해야 한다. 다음과 같이 애완동물에 대한 리스트를 만들려고 한다.

In [134]:
def 애완동물리스트(동물, 리스트=[]):
    리스트.append(동물)
    return 리스트
In [135]:
고양이 = 애완동물리스트('스코티시 폴드')
고양이
Out[135]:
['스코티시 폴드']
In [136]:
 = 애완동물리스트('치와와')

Out[136]:
['스코티시 폴드', '치와와']

함수의 몸통 부분은 여러번 실행할 수 있지만 함수의 머리 부분을 포함한 정의는 단 한번만 실행이 된다. 따라서 애완동물리스트의 매개변수 리스트는 처음 정의를 실행할 때 한 번 만들어지므로 함수를 호출하더라도 같은 객체를 공유하게 된다.

각 함수를 실행할 때마다 다르게 리스트를 만들고 싶으면 다음과 같이 한다.

In [137]:
def 애완동물리스트1(동물, 리스트=None):
    if 리스트 is None:
        리스트 = []
    리스트.append(동물)
    return 리스트
In [138]:
 = 애완동물리스트1('먼치킨')

Out[138]:
['먼치킨']
In [139]:
 = 애완동물리스트1('시추')

Out[139]:
['시추']

가변 매개변수

때때로 함수의 인자의 갯수가 상황에 따라서 달라져야 할 필요가 있다. 이 때 사용할 수 있는 매개변수가 가변 매개변수이다. 가변 매개변수는 가변 위치 매개변수와 가변 키워드 매개변수로 나눌 수 있다.

가변 위치 매개변수

가변 위치 매개변수는 별표(*)와 매개변수이름을 함께 적어 표시한다. 함수를 부를 때 위치 인자들이 차례로 가변 위치 매개변수 이름에 튜플로 할당된다. 가변 위치 매개변수를 갖는 함수를 예를 들어보자.

In [140]:
def 합계(*항목들):
     = 0
    for 항목 in 항목들:
         += 항목
    print('합계 =', )

합계(1, 2, 3, 4, 5)
합계 = 15
  • *항목들 에서 항목들이 가변 위치 매개변수이다.
  • 합계(1, 2, 3, 4, 5)의 위치 인자들 1, 2, 3, 4, 5가 차례로 항목들 변수에 튜플로 할당된다. 즉, 항목들 = (1, 2, 3, 4, 5)와 같이 할당된다.

가변 위치 매개변수 다음에 오는 매개변수는 키워드 인자로 입력해야 한다.

객체 싸기(packing) 및 풀기(unpacking)

반복가능 객체의 성분들을 변수에 대입하는 것을 싸기라고 한다.

In [17]:
a = [1, 2, 3]

거꾸로 자료들을 풀어 변수에 대입하는 것을 풀기라고 한다.

In [18]:
a, b, c = (1, 2), 3, '풀기'
In [14]:
a
Out[14]:
(1, 2)
In [15]:
b
Out[15]:
3
In [16]:
c
Out[16]:
'풀기'
In [10]:
*x, = range(5)
x
Out[10]:
[0, 1, 2, 3, 4]
In [11]:
x, *y, z = range(5)
x, y, z
Out[11]:
(0, [1, 2, 3], 4)
In [12]:
[x, *y, z] = range(5)
x, y, z
Out[12]:
(0, [1, 2, 3], 4)

리스트 또는 튜플의 성분들을 순서대로 위치 인자에 넘겨주기 위해서는 *를 이용하면된다. 리스트 또는 튜플 앞에 *를 붙여 인자에 대입하면 리스트 또는 튜플이 해체되어 성분들이 순서대로 인자로 대입된다. 일반적으로 *<반복가능 객체>이면 된다. 내장 함수 range() 는 시작, 끝, 간격을 인자로 입력 받을 수 있다.

In [160]:
인자들 = (1, 10, 2) # 튜플

list(range(*인자들)) # 인자들 튜플을 풀어서 1, 10, 2를 인자로 넘긴다.
Out[160]:
[1, 3, 5, 7, 9]

마찬가지 방법으로, **를 이용하여 사전 객체를 풀어서 키워드 인자 형식으로 넘겨줄 수 있다.

In [161]:
def 지저귀기(**새들):
    for  in 새들:
        print(, "는 ", 새들[], sep="")

새들 = {"참새": "짹짹", "갈매기":"끼룩끼룩", "매미":"맴맴"} # 사전 객체
지저귀기(**새들) # 사전 객체를 풀어 키워드 인자로 변경하여 넘겨준다.
참새는 짹짹
갈매기는 끼룩끼룩
매미는 맴맴

직접하기

  • 가변 위치 매개변수만을 갖는 더하기 함수 더하기(*숫자들)를 만들고 그 함수를 이용하여 1부터 1000까지 더하기를 하시오.

가변 키워드 매개변수

가변 키워드 매개변수는 별표 두 개(**)에 이어서 가변 키워드 매개변수 이름이 함께 온다. 예를 들면 **kwargs와 같이 사용한다. 함수를 부를 때 키워드 인자로 입력한 값들이 가변 키워드 매개변수이름에 사전(dict)형식으로 대입된다. 가변 키워드 매개변수는 매개변수들 중 맨 마지막에 위치해야 한다.

In [141]:
def 영화(**키워드들):
    for 키워드 in 키워드들:
        print(키워드, ":", 키워드들[키워드])

영화(제목="클래식", 감독="곽재용", 각본="곽재용", 출연="손예진, 조인성, 조승우, 이기우", 개봉일="2003년 1월 30일")
제목 : 클래식
감독 : 곽재용
각본 : 곽재용
출연 : 손예진, 조인성, 조승우, 이기우
개봉일 : 2003년 1월 30일
  • 함수 영화(제목="클래식", 감독="곽재용", 각본="곽재용", 출연="손예진, 조인성, 조승우, 이기우", 개봉일="2003년 1월 30일")를 키워드 인자로 입력하면 키워드들 가변 키워드 매개변수에 사전(dict)형식으로 입력이 된다.

튜플과 사전형은 자료 형 부분에서 좀더 자세히 설명한다.

매개변수 순서

위치 매개변수는 핵심어 매개변수 앞에 위치하여야 한다. 단, 가변 매개변수 사이에 오는 매개변수는 예외이다. 매개변수의 순서는 다음과 같다.

(<위치 매개변수>, <핵심어 매개변수>, <가변 위치 매개변수>, <위치 매개변수 또는 핵심어 매개변수>, <가변 핵심어 매개변수>)

가변 위치 매개변수 뒤에 오는 매개변수를 함수에서 인자로 부를 때는 반드시 핵심어 인자 형식으로 불러야 한다.

In [142]:
 def f(a, b=2, *c, d, e=5, **k):
    print("a={}, b={}, c={}, d={}, e={}, k={}".format(a, b, c, d, e, k))

가변 위치 매개변수 *c 뒤에 나오는 위치 매개 변수 d는 반드시 핵심어 인자 d=4와 같은 형식으로 불러야 한다. 그렇지 않으면 다음과 같이 오류가 발생한다.

In [143]:
f(1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-143-281ab0a37d7d> in <module>()
----> 1 f(1)

TypeError: f() missing 1 required keyword-only argument: 'd'
In [144]:
f(1, d=4)
a=1, b=2, c=(), d=4, e=5, k={}

return 문

함수가 끝날 때 함수의 결과를 return 문을 이용해서 반환할 수 있다. 예를 들면 합계를 구하는 함수를 부르면 그 결과값으로 합계를 반환하면 그 반환값을 가지고 다른 곳에서 사용할 수 있을 것이다. return 문은 return 뒤에 객체를 적어 주면 그 객체가 함수의 종료와 함께 반환된다.

In [145]:
def 합계(*항목들):
     = 0
    for 항목 in 항목들:
         += 항목
    return 

국어합 = 합계(86, 90, 76)
영어합 = 합계(90, 64, 93)
print('총합 =', 국어합 + 영어합)
총합 = 499
  • return 을 실행하면 함수가 종료가 되면서 을 반환한다.

함수 실행 중에 return문을 만나면 그 즉시 함수는 종료된다. 즉, 함수 구역에서 return 아래 어떤 문장도 실행이 되질 않고 함수를 끝내고 return 문장과 함께 있는 객체가 반환이 된다.

In [146]:
def 몫과나머지(, 나눌수):
    if not 나눌수:
        return None, None
     =  // 나눌수
    나머지 =  % 나눌수
    return , 나머지
In [147]:
, 나머지 = 몫과나머지(7, 3)
print("몫: {}, 나머지: {}".format(, 나머지))
몫: 2, 나머지: 1

return 문 뒤에 아무것도 지정하지 않으면 None을 반환한다.

In [148]:
def 반환없음():
    print("return이 없으면 None객체를 반환합니다.")

 = 반환없음()
print("반환값:", )
return이 없으면 None객체를 반환합니다.
반환값: None

None은 파이썬의 예약어로 아무것도 없다는 표시이다. 함수가 종료될 때 return 문이 없으면 암시적으로 None을 반환한다.

직접하기

  • 원의 반지름을 입력받아 원의 면적을 반환하는 함수를 작성하자.

변수 유효범위

지역 변수

함수 안에서 만들어진 변수는 함수를 벗어나서는 사용할 수 없다. 이러한 변수를 지역 변수(local variable)라고 한다. 다음 프로그램을 살펴보자.

In [149]:
def 함수1():
    y = 5 # 함수 안에서 y값 만듦
    print("함수 안에서 만든 y 값:", y)

함수1() # 함수를 부른다.
print(y) # 함수 안에서 만든 지역 변수 y를 함수 밖에서 부른면 에러
함수 안에서 만든 y 값: 5
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-149-ae384abcefd6> in <module>()
      4
      5 함수1() # 함수를 부른다.
----> 6 print(y) # 함수 안에서 만든 지역 변수 y를 함수 밖에서 부른면 에러

NameError: name 'y' is not defined

위 코드를 실행하게 되면 함수()는 정상적으로 실행되지만, print(y)를 실행하는 순간 NameError: name 'y' is not defined에러가 발생하게 된다. 함수 안에서 선언된 변수 y는 함수가 실행될 때 만들어 졌다가 함수 실행을 끝내는 순간 사라지기 때문에 y변수 이름이 존재하지 않는다는 에러를 발생하게 된다.

다음 함수2() 안에 있는 x는 첫 줄에서 정의된 전역변수 x = 1이 대입되어 x += 1을 실행하면 x = 2가 될 것이라고 예상할 수 있지만 다음과 같은 오류가 발생한다.

In [2]:
x = 1

def 함수2():
    x += 1 # x = x + 1
    print(x)

함수2()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-2-9ce1f0ab791a> in <module>()
      5     print(x)
      6
----> 7 함수2()

<ipython-input-2-9ce1f0ab791a> in 함수2()
      2
      3 def 함수2():
----> 4     x += 1
      5     print(x)
      6

UnboundLocalError: local variable 'x' referenced before assignment

x = x + 1에서, 좌변의 변수 x는 함수 안에서 처음으로 등호에 의해서 값을 할당하려고 할 때 자동으로 지역변수로 선언이 된다. 그런데 우변의 x는 값이 할당이 안된 상태에서 더하기를 하려고 해서 오류를 발생하게 된다.

다음의 경우도 함수3() 안에서 x = 1로 처음으로 값이 할당되기 때문에 x는 지역변수로 정의된다. 그런데 print(x)에서 변수에 값이 할당되기전에 x를 사용하려고 해서 오류가 발생된다. 자세한 것은 모듈의 이름공간 부분에서 다룬다.

In [5]:
x = 1

def 함수3():
    print(x)
    x = 1

함수3()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-5-f80809c15b0a> in <module>()
      5     x = 1
      6
----> 7 함수3()

<ipython-input-5-f80809c15b0a> in 함수3()
      2
      3 def 함수3():
----> 4     print(x)
      5     x = 1
      6

UnboundLocalError: local variable 'x' referenced before assignment

매개변수와 지역변수

함수의 매개변수로 선언된 변수는 함수 안에서만 사용되는 지역변수이다.

In [150]:
x = 10 # 전역변수

def 함수2(x):
    print("함수 안에 있는 x:", x) # 함수를 부를 때 넣은 x값 출력
    x = 5 # 함수 안에서 x값 변경과 동시에 지역변수로 변한다.
    print("함수 안에서 변경된 x 값:", x) #변경된 x값 출력

함수2(x) # 전역변수 x=10을 건네준다.
print("함수 밖에 있는 x:", x)
함수 안에 있는 x: 10
함수 안에서 변경된 x 값: 5
함수 밖에 있는 x: 10

x = 10이라고 선언되었기 때문에 함수2(x)를 부를 때 x대신 10이 인자로 넘어간다. print("함수 안에 있는 x:", x) 에서는 10을 출력하게 되고 다음 문장 x = 5에서 위에 있던 x를 무시하고 새로운 지역 변수 x를 만들게 된다. 함수2()가 끝나는 시점까지 x는 지역변수가 된다. 함수를 끝내고 다음 문장 print("함수 밖에 있는 x:", x)을 실행하면 함수 안에서 만들어졌던 지역변수 x는 사라지고 전역변수 x=10이 그대로 출력이 된다.

global 문

함수 안에서 변경된 값이 계속해서 함수가 끝난 후에도 적용되게 하고 싶을 때는 global 예약어를 사용하면 된다.

In [151]:
x = 10 # 전역변수

def 함수3():
    global x
    print("함수 안에서 변경되기 전 x:", x)
    x = 5 # 함수 안에서 x값 변경
    print("함수 안에서 변경된 후 x 값:", x) # 변경된 x값 출력

함수3()
print("함수 실행 끝낸 후 x:", x)
함수 안에서 변경되기 전 x: 10
함수 안에서 변경된 후 x 값: 5
함수 실행 끝낸 후 x: 5

함수를 실행하기 전에 변수 x=10이 값을 가지고 있고 함수 실행을 하면 global x라는 문장을 통해서 이미 있는 변수 x는 전역변수로 간주를 한다. 따라서 함수 안에서 x의 값을 변경하면 함수가 끝난 후에도 변경된 값이 적용이 된다. 전역변수를 여러 개 선언하고 싶으면 global x, y, z와 같이 한다.

global 변수는 사용되기 전에 선언이 되어 있어야 한다. 다음은 xglobal 변수로 선언되기전에 x = 1이라고 할당을 해서 함수가 정의되는 과정에서 오류가 발생한다.

In [6]:
def 함수4():
    x = 1
    global x
    print(x)
  File "<ipython-input-6-197b991c8018>", line 3
    global x
    ^
SyntaxError: name 'x' is assigned to before global declaration

함수 밖에서 전역변수로 설정이 되지 않은 상태에서 함수 안에서 전역변수로 설정을 해서 사용할 수도 있다.

In [9]:
def 함수5():
    global x
    x = 10
    print('함수 안에서: ', x)

함수5()
print('함수 밖에서: ', x)
함수 안에서:  10
함수 밖에서:  10

global 변수는 꼭 필요한 경우가 아니면 사용하지 말아야 한다. 왜냐면 global 변수가 선언된 함수를 프로그램 중에서 호출할 때마다 변수의 값이 변하기 때문에 값의 상태를 예측할 수 없기 때문이다.

가변 객체 변수

파이썬에서는 함수 안의 변수에 새로운 객체가 할당되기 전까지는 그 변수는 전역변수로 간주한다. 물론 그 변수는 이미 선언되어 있어야 한다. 그렇지 않고 새로운 객체를 변수에 할당하는 순간에 그 변수는 지역변수가 된다. 따라서 다음과 같이 가변 객체(mutable object)인 리스트 성분을 함수 안에서 변경한다고 해서 지역변수가 되는 것은 아니다.

In [152]:
x = [1, 2, 3] # 가변 객체 리스트

def 함수4():
    print("함수 안에 있는 x:", x) # 전역변수 x값 출력
    x[0] = 5 # 함수 안에서 x의 첫번째값 변경
    print("함수 안에서 변경된 x 값:", x) # 함수 안에서 변경된 x값 출력

함수4()
print("함수 실행 후 x:", x) # 함수 안에서 변경된 값이 출력된다.
함수 안에 있는 x: [1, 2, 3]
함수 안에서 변경된 x 값: [5, 2, 3]
함수 실행 후 x: [5, 2, 3]

리스트는 가변 객체이기 때문에 함수4() 안에서 x[0] = 5 문을 실행하면 함수 안에서 x의 성분을 변경할 수 있다. 함수 안에서 리스트 객체가 새로 만들어진 것이 아니기 때문에 함수 실행이 끝나도 x는 계속 전역변수를 유지하게 된다. 따라서 바뀐 값이 적용되어 출력된다. 가변 객체란 객체를 구성하는 성분을 변경할 수 있는 객체를 의미하고 성분을 바꿀 수 없는 객체를 불변 객체(immutable object)라고 한다. 자세한 것은 클래스 부분에서 다루게 된다. 대표적인 불변 객체로는 숫자형(int, float, complex), 문자열(str) 및 튜플(tuple)형이 있다.

함수 객체

함수도 객체이기 때문에 변수에 할당할 수 있다.

In [153]:
def 함수():
    print("함수")

f = 함수
f()
함수

함수 이름으로 변수에 할당하는 것과 함수()를 할당하는 것은 다르다. 함수()은 함수를 호출하여 얻어진 반환값이기 때문에 반환값이 할당된다.

In [154]:
def 함수():
    print("함수")

f = 함수()
print(f)
함수
None

lamda 식

이름이 없는 간단한 함수를 만들 때 lambda 식을 이용한다. 사용방법은 다음과 같다.

lambda <매개변수> : <반환될 >

다음은 제곱을 하는 함수를 람다식을 이용해서 만든 것이다.

In [155]:
f = lambda x : x ** 2
f(2)
Out[155]:
4

다음은 람다식을 이용해서 함수의 반환값이 다시 함수가 되도록 만든 것이다.

In [156]:
def 증분(n):
    return lambda x: x + n

증분2 = 증분(2)
증분5 = 증분(5)

증분(2)의 반환값은 람다함수로서 람다함수의 인자값에 2를 더한 값을 반환한다.

In [157]:
증분2(10)
Out[157]:
12

증분(5)도 람다함수로 5를 더한 값을 반환한다.

In [158]:
증분5(10)
Out[158]:
15

다음은 리스트 항목을 sort() 메소드를 이용해 정렬하는 예이다.

In [159]:
pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])
pairs
Out[159]:
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
  • 리스트의 sort 메소드는 key 인자값으로 하나의 매개변수만을 갖는 함수를 취한다. 이 함수의 반환값들을 이용해 비교 연산자 < 연산을 수행하여 리스트 항목을 정렬한다.
  • key=lambda pair: pair[1]에서 lambda pair: pair[1] 부분이 함수이고 함수의 입력 매개변수는 pair이고 이 매개변수에 리스트의 항목이 하나씩 입력된다. 콜론 뒤에 수식 pair[1]이 함수의 반환값으로 가령 pair = (1, 'one')이면 pair[1]'one'이 되는 것이다.
  • 따라서 각각 반환되는 함수값은 두번째 성분인 'one', 'two', 'three', 'four'가 되고 이것들을 < 연산을 가지고 정렬을 하면 결과와 같이 나오는 것이다.

생성자(generator)

생성자는 반복자(iterator)의 특수한 한 형태이다. 생성자 함수(generator function)는 함수 안에 yield를 사용하여 값을 반환하는 함수이다. 생성자 함수가 처음 호출되면, 그 함수 실행 중 처음으로 만나는 yield 에서 값을 반환하고 함수 실행을 멈춘다. 생성자 함수가 다시 호출되면, 직전에 실행되었던 yield 문 다음부터 다음 yield 문을 만날 때까지 문장들을 실행하고 yield에서 실행을 멈추고 값을 반환한다. 더 이상 실행할 yield가 없을 때가지 이러한 과정을 반복한다. 이러한 생성자 함수를 호출하여 생성자(generator) 객체를 만든다.

다음은 간단한 생성자 함수와 그 호출 예를 보인 것이다. 여기서 생성() 함수는 생성자 함수로서 3개의 yield 문을 가지고 있다. 따라서 한번 호출시마다 각 yield 문에서 실행을 중지하고 값을 리턴하게 된다.

In [162]:
def 생성함수():
    yield 1
    yield 2
    yield 3

생성자 함수를 호출하여 생성자를 만들어 아래와 같이 변수 g에 할당한다. 그러면 g는 생성자 객체가 된다.

In [163]:
g = 생성함수()
type(g)
Out[163]:
generator

생성자 호출은 내장함수 next(생성자)를 이용할 수 있다. next를 처음 호출하면 처음으로 만나는 yield 1에서 실행을 멈추고 yield 뒤에 있는 값 1을 반환한다.

In [164]:
print(next(g))
1

다시 next를 실행하면 yield 1 다음 문장 즉, yield 2에서 실행을 멈추고 2를 반환한다.

In [165]:
print(next(g))
2

next를 실행하면 yield 2 다음 문장, yield 3에서 실행을 멈추고 3을 반환한다.

In [166]:
print(next(g))
3

생성자가 더이상 반환할 값이 없으면 StopIteration 예외를 발생시킨다.

In [167]:
print(next(g))
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-167-1dfb29d6357e> in <module>()
----> 1 print(next(g))

StopIteration:

for 문을 이용해서 자동으로 반복자를 가져오고 next 함수를 실행시켜 값을 가져온다.

In [168]:
for x in 생성함수():
    print(x)
1
2
3

리스트나 집합(set)과 같은 자료형에 대한 반복자는 해당 객체가 이미 모든 값을 가지고 있는 경우이나, 생성자는 모든 값을 갖지 않은 상태에서 yield에 의해 하나씩만 값을 만들어 가져온다는 차이점이 있다. 이러한 생성자는 자료가 매우 커서 모든 자료를 한꺼번에 반환할 수 없는 경우나, 모든 자료를 미리 계산하면 속도가 느려서 필요한 자료 요청시 처리하는 것이 좋은 경우 등에 사용된다.

직접하기

  • 구구단 출력하는 프로그램을 생성자 함수 구구단()으로 만들고 for문을 이용해 호출해보자. 즉 다음을 실행하면 구구단이 출력되어야 한다.

    for 구구 in 구구단():
        print(구구)
    

생성자 축약

생성자 식은 생성자 축약(generator comprehension)으로도 불리우는데, 리스트 축약(list comprehension)과 외관상 유사하다. 리스트 축약은 앞뒤를 대괄호 [...]로 표현한다면, 생성자 축약은 소괄호 (...)를 사용한다. 하지만 생성자 축약은 리스트 축약과 달리 실제 값 전체를 반환하지 않고, 그 표현식만을 갖는 생성자 객체만 반환한다. 즉 실제 실행은 하지 않고, 표현식만 가지며 위의 yield 방식으로 늦은 연산(Lazy Operation)을 수행하는 것이다.

아래 예제는 1부터 10까지의 숫자에 대한 제곱값을 생성자 축약으로 표현한 것으로 여기서 생성자 축약을 할당받은 변수 g는 생성자 타입 객체이다.

In [169]:
g = (x*x for x in range(1, 11))
In [170]:
print(type(g))
<class 'generator'>

for 문을 사용하여 10개의 next() 문을 실행하여 처음 10개에 대한 제곱값을 출력한 것이다.

In [171]:
for i in range(10):
    print(next(g))
1
4
9
16
25
36
49
64
81
100

또는 다음과 같이 for문에 직접 생성자를 넣어서 실행할 수 있다.

In [172]:
for x in g:
    print(x)

그런데 위와 같이 실행하면 아무런 결과가 나오질 않는 것을 알 수 있다. 이것은 생성자 g는 위에서 next 문을 통해 이미 끝까지 값을 반환했기 때문에 더이상 반환할 것이 없어 for 문이 실행이 되질 않는 것이다. 이때는 다시 생성자를 만들어 실행해야 한다.

In [173]:
g = (x*x for x in range(1, 11))
for x in g:
    print(x)
1
4
9
16
25
36
49
64
81
100

내장함수 sum과 같이 반복가능 객체를 인자로 받는 함수에서 생성자 축약을 인자로 넘겨줄 수 있다.

In [174]:
h = (x * x for x in range(11))
sum(h)
Out[174]:
385

또는 직접 인자로 생성자 축약을 대입해도 된다. 이때 소괄호는 생략할 수 있다.

In [175]:
sum( x * x for x in range(11))
Out[175]:
385

거북이 이벤트 함수

거북이는 마우스 클릭 또는 키보드의 키눌림 이벤트를 알아채서 뭔가를 할 수 있는 함수를 제공한다. 이벤트(event)란 프로그램에 의해서 감지되고 처리될 수 있는 동작이나 사건을 말한다. 키보드 또는 마우스 또는 휴대폰의 터치스크린 등은 사용자가 상응하는 동작을 하는 순간 이벤트를 감지하여 처리한다. 키보드를 누르는 순간, 마우스를 움직이거나 클릭하는 순간, 터치스크린을 터치하는 순간에 이벤트가 발생하는 것이다. 거북이는 사용자가 마우스를 클릭 했을 때 onclick(func)이라는 함수가 실행되어 onclick 인자인 func를 작동시킨다. func는 클릭 한 곳의 좌표 (x, y)를 매개변수로 하는 함수이어야 한다. onclick(func)함수가 실행될 때, func 함수에 클릭한 곳의 좌표 (x, y)를 인자로 넘겨준다. 그러면 func(x, y) 함수가 실행되는 것이다.

import turtle

 = turtle.Screen()
 = turtle.Pen()

def 글씨쓰기(x, y):
    .pu()
    .goto(x, y)
    문자열 = "(" + str(x) + ', ' + str(y) + ")"
    .write(문자열)

.onclick(글씨쓰기)
.mainloop()
  • 창.onclick(글씨쓰기)를 실행하면 거북이는 마우스가 클릭 되기를 무한정 기다리다가 마우스가 창을 클릭하는 순간 onclick 인자인 글씨쓰기 함수를 부른다.
  • 글씨쓰기 함수를 부를 때 클릭한 곳의 x, y 좌표를 글씨쓰기(x, y)에 자동으로 넘겨주어 글씨쓰기 함수가 실행되는 것이다.
  • onclick 함수는 마우스가 거북이 창을 클릭할 때마다 호출되어 실행된다.
  • 창.mainloop() 함수도 창의 종료 이벤트(창의 오른쪽 위 X 누르기 또는 Alt + F4 누르기)를 무한정 기다리는 이벤트 함수이다.

직접하기

  • 거북이를 이용하여 마우스를 클릭하면 “코딩은 즐거워!”라는 문자열을 클릭한 곳에 출력하는 프로그램을 작성하시오.
  • 거북이를 이용하여 마우스를 클릭하면 클릭한 곳에 한변의 길이가 20인 정사각형을 그리는 프로그램을 작성하시오.

문서화 문자열

파이썬은 문서화 문자열(documentation string)이라고 불리우는, 줄여서 docstring라 불리우는 편리한 기능을 가지고 있다. 문서화 문자열은 프로그램을 알아보기 쉽게 해 주고, 또 나중에 프로그램에 대한 설명서를 작성할 때 유용하게 사용될 수 있는 중요한 도구이다. 아래 예제와 같이, 문서화 문자열은 프로그램이 실행 중일 때도 읽어올 수 있다.

In [176]:
def 함수():
    """이것은 함수를 간단히 설명하는 줄이다.

    한 줄을 띄우고 문서에 대한 설명을 적는다.
    문서화 문자열은 함수이름() 콜론 바로 다음 줄에 와야 한다.
    """

    pass

help(함수)
Help on function 함수 in module __main__:

함수()
    이것은 함수를 간단히 설명하는 줄이다.

    한 줄을 띄우고 문서에 대한 설명을 적는다.
    문서화 문자열은 함수이름() 콜론 바로 다음 줄에 와야 한다.

  • 줄2, 문서화 문자열은 함수이름 선언 바로 다음 줄에서 따옴표 3개를 사용한 문자열을 이용한다.
  • 줄2, 첫 줄은 보통 한 줄로 적으며 마침표로 끝낸다.
  • 줄3, 둘째 줄은 빈 줄로 놓는다.
  • 줄4, 셋째 줄부터 필요한 설명들을 적는다.
  • 줄8, 문서화 문자열이 끝난 다음 줄부터 함수의 몸통이 시작된다.

소행성 게임

거북이를 이용하여 움직이는 소행성과 우주선이 부딪혀 소행성을 파괴하는 게임이다. 아래 코드는 게이머와 소행성의 움직임만 작성한 것이다.

import turtle
import random
import math

 = turtle.Screen() # 거북이 창

우주선 = turtle.Pen() # 게이머
우주선.color('blue')
우주선.shape('turtle')
우주선.penup()
우주선.speed(0)

소행성1 = turtle.Pen() # 소행성1
소행성1.color('red')
소행성1.shape('circle')
소행성1.penup()
소행성1.speed(0)
소행성1.goto(random.randint(-300, 0), random.randint(-300, 300))

소행성2 = turtle.Pen() # 소행성2
소행성2.color('red')
소행성2.shape('circle')
소행성2.penup()
소행성2.speed(0)
소행성2.goto(random.randint(-300, 0), random.randint(-300, 300))

def 왼쪽():
    우주선.goto(우주선.pos() + (-10, 0))

def 오른쪽():
    우주선.goto(우주선.pos() + (10, 0))

def ():
    우주선.goto(우주선.pos() + (0, 10))

def 아래():
    우주선.goto(우주선.pos() + (0, -10))

.onkeypress(왼쪽, "Left")
.onkeypress(오른쪽, "Right")
.onkeypress(, "Up")
.onkeypress(아래, "Down")
.listen()

def 놀자():
    소행성1.forward(2)
    소행성2.forward(2)
    .ontimer(놀자, 10)

놀자()

.mainloop()
  • 우주선은 게이머이고 speed(0)함수를 이용하여 움직이는 속도를 가장 빠르게 했다. 1이 가장 느리고 숫자가 10까지 올라갈수록 더 빨라진다. 보통 속도는 6이고 가장 빠른 속도는 0이다.
  • 소행성1.goto(random.randint(-300, 0), random.randint(-300, 300))에서 randint(x, y)을 이용하여 소행성이 시작하는 위치를 x의 위치는 -300에서 0까지, y의 위치는 -300에서 300까지 무작위 수로 설정했다.
  • 왼쪽, 오른쪽, , 아래로 이동하는 함수를 onkeypress() 함수에 건네주어 키보드의 왼쪽, 오른쪽, 위, 아래 키가 눌릴 때마다 작동하게 했다.
  • 왼쪽, 오른쪽, , 아래로 이동하는 함수안에 우주선.goto(우주선.pos() + (-10, 0))을 이용하여 키가 눌릴 때의 우주선 위치 우주선.pos()+10 또는 -10 만큼을 더해서 우주선를 움직이게 했다.
  • 창.ontimer(놀자, 10)에서 ontimer(함수, 시간) 함수는 주어진 시간(ms 단위) 간격으로 함수를 실행한다. 시간의 단위는 ms(mille second)로 1/1000초를 의미한다. 따라서 놀자() 함수를 0.01초 간격으로 실행하면서 forward(2)가 실행되어 소행성들이 앞 쪽으로 2만큼 움직인다.

연습

  1. 섭씨 온도를 화씨 온도로 바꾸는 함수 섭씨2화씨(섭씨)를 작성하고 실행하시오.

  2. 위의 섭씨2화씨(섭씨) 함수의 문서화 문자열을 넣고 help()함수를 이용해서 함수의 설명을 확인해 보시오.

  3. 정사각형() 함수를 만들어 거북이를 이용하여 다음과 같은 그림을 그리시오.

  4. 다음과 같은 그림을 그리시오.

  5. 별의 시작 위치 x, y와 한 변의 길이를 매개변수로 하는 별 그리는 함수를 작성하고 실행하시오. 함수의 이름과 매개변수를 별그리기(x, y, 한변)로 하시오.

  6. 위의 별그리기(x, y, 한변)함수를 이용하여 별의 시작 위치및 한 변의 길이를 무작위 수를 생성하여 10개를 그리시오. 반복문을 이용하시오.

  7. 거북이를 이용하여 키보드 오른쪽 화살표를 누르면 오른쪽으로 회전하여 50만큼 이동하고, 왼쪽 화살표를 누르면 왼쪽으로 회전하여 50만큼 이동하도록 프로그램을 만드시오. 거북이 창 메소드 onkeypress(func, key)listen() 함수를 이용하시오. 여기서 key는 왼쪽은 'Left', 오른쪽은 'Right'이다.

  8. 거북이를 이용하여 창을 클릭한 곳에 별이 그려지는 프로그램을 작성하시오. onclick() 함수를 이용하시오. 위에서 작성한 별그리기 함수를 수정해서 사용하시오.

  9. 위 소행성 게임 예제에서 소행성과 우주선과의 거리가 20보다 작으면 “꽝!”이라는 문자를 우주선의 위치에 출력하는 함수 사정거리()를 작성하시오. 놀자() 함수에 사정거리() 함수를 넣어 우주선이 소행성 근처에 접근하면 “꽝!”이라는 문자가 출력되게 프로그램을 수정하시오.

  10. 두 점 (x1, y1), (x2, y2)를 사용자로부터 입력받아 두 점을 지나는 직선을 그리는 함수 직선그리기(x1, y1, x2, y2)를 만들고 실행하시오. 그래프 위에 두 점과 두 점의 좌표를 함께 표시하시오.