━━━━ ◇ ━━━━
따라하며 배우는 파이썬과 데이터 과학/PART 1. 파이썬 기초체력 다지기

Chapter 06. 함수로 일처리를 짜임새있게 하자

이번 시간의 목차

1. 프로그램에서의 함수란?

2. 함수를 만들어보자!

3. 함수에게 값을 주고 돌려받자!

4. 함수 속 변수는 밖에서도 계속 쓰일 수 있을까?

5. 여러 종류의 인자

6. 나를 불러 줘! 재귀함수

7. 함수를 저장하고 싶다면 모듈을 만들자!

8. 마무리

 

 

자, 가자! 파이썬의 세계로!


 

프로그램에서의 함수란?

 

 함수(Function)은 우리가 반복적으로 사용하는 코드를 묶은 것으로, 코드 덩어리라고도 볼 수 있다. 우리가 앞 장에서 만든 코드보다 더 큰 프로그램을 만들 때, 이 함수를 쓰지 않고 일일이 동일한 코드를 여러 줄 반복하게 된다면 어떨까? 늘 그렇지만 프로그램은 간결할 수록 좋다. 당연히 길어지면 쓰는 사람도 읽는 사람도 힘들어질 수밖에 없다. 

 

 함수 말고도 프로그램을 작게 쪼개는 방법이 2가지 더 있다. 

  • 객체(Object): 코드 중에서 독립적인 단위로 분리할 수 있는 조각
  • 모듈(Module): 프로그램의 일부를 가지고 있는 독립적인 파일

 이번 장에서는 우선 함수에 대해서 먼저 알아보려고 한다. 

함수라는 이름 자체는 이미 많이 들어봤을 것이다. 현재 중학교 2학년 1학기 즘이면 수학에서 '일차 함수' 단원을 배운다. 

 

 

 이렇게 함수는 입력을 받아서 출력을 내보내는 것으로 생각할 수 있다. 함수 속 식(코드)는 바뀌지 않고, 입력 받는 값과 출력 값만 바꾸는 것이다. 

 

 


 

함수를 만들어보자!

 

 함수를 만들려면 파이썬의 키워드 중 def를 사용하면 된다. 아이엠그라운드 게임의 대사로 예시를 살펴보자. 

 첫째 줄 def Iamground() : 에서 def 키워드를 이용하여 함수를 정의한다. def의 뒤로는 함수의 이름을 적고, 소괄호()와 콜론 :을 붙인다. 이때 소괄호 속에 들어가는 것은 매개변수(Parameter)로, 이해하기 쉽게 설명하자면 수학 함수 f(x)에서 x와 같은 역할이다. 위의 Iamground() 함수에서는 매개변수를 사용하지 않지만, 매개변수를 x로 두고 x에 관한 코드를 짜 놓으면, 나중에 사용할 때 매개변수 자리에 넣은 값, 즉 인자(Argument)가 x에 관한 코드 속 x에 들어가서 해당 값에 대한 출력값을 출력하게 된다. 콜론은 앞 장에서 설명해서 알겠지만, 한 블록이 시작된다는 뜻을 가진다. 블록이 시작되면 항상 같은 깊이만큼 들여쓰기를 해야 한다. 키보드의 Tab 버튼을 이용하면 동일한 깊이만큼 들여쓰기가 되니 되도록이면 Tab을 이용한 들여쓰기를 하자. 

 

 그럼 이렇게 함수를 정의하기만 하면 실행이 되는 것일까? 

아니, 함수 속 코드를 실행하려면 함수를 호출해야 한다. 함수 호출(Function Call)은 간단하다. 위에서 설정한 함수이름(인자)를 적어주면 된다.

 

 그런데 Iamground() 속 여섯 문장은 그냥 print() 함수로도 충분히 출력할 수 있지 않나?

물론 그렇게 할 수도 있지만, 함수를 한 번 정의해 놓으면 언제든지 필요할 때마다 함수를 불러 일을 시킬 수 있기 때문에 일일이 print()로 출력하는 것보다 더욱 편리해진다. 

 

 조금 전에는 매개변수와 인자를 사용하지 않은 함수를 예시로 들어보았다. 그러면 이제는 매개변수와 인자를 한 번 써 보자. 

위의 Iamground() 함수에서는 아이엠그라운드 게임 참가자들의 과일 이름이 정해져있었다. 매개변수와 인자를 사용하면 내 차례에 올 과일만 바꿔서 출력할 수 있을 것이다.

 

>>> def Iamground(fruit) :    #매개변수는 fruit이다.
    print('아이엠그라운드 자기소개 하기')
    print('나는 사과')
    print('나는 딸기')
    print('나는 포도')
    print('나는', fruit)
    print('아이엠 그라운드 지금부터 시작')
 
    
>>> Iamground('귤')           #인자로 '귤'을 넣었다.
아이엠그라운드 자기소개 하기
나는 사과
나는 딸기
나는 포도
나는 귤                       #fruit 자리에 '귤'이 들어간다.
아이엠 그라운드 지금부터 시작
cs

 

 매개변수를 fruit로 두고 인자를 '귤'로 넣었더니 내 차례에 귤이 들어온 것을 볼 수 있다. 


 

함수에게 값을 주고 돌려받자!

 

 함수에 값을 주면 우리에게 결과를 보내주기도 한다. 이때 함수가 돌려주는 값을 반환 값(Return Value)이라고 한다. 함수 속에서 만들어진 값을 돌려 받으려면 return 키워드를 사용하면 된다. 원의 반지름을 넣으면 원의 넓이를 구해주는 함수를 만들어 보자.

 

>>> def circle_area(radius) :
    area = 3.14 * radius * radius
    return area
 
>>> c1_area = circle_area(6)
>>> c1_area
113.03999999999999
 
>>> area_sum = circle_area(6+ circle_area(4)
>>> area_sum
163.28
cs

 

 위의 circle_area(radius) 함수는 radius를 매개변수로 두고 인자로 받은 6을 radius 자리에 넣어서 원의 넓이를 구해서 반환해준다. 이 반환 값은 변수에 저장할 수 있다. 변수에 저장할 수 있으므로 이 반환 값 또한 데이터다. 이 함수가 수행한 결과를 더할 수도 있다. 

 

 동시 할당문을 기억하고 있는가? 한 번에 여러 변수에 여러 값을 넘겨주는 할당문인데, 이 함수에서도 인자로 한 번에 여러 값을 넘겨줄 수 있다. 이때 인자의 개수와 매개변수의 개수가 같아야 한다. 다르면 오류가 발생하게 된다. 

 

 또 함수로는 여러 개의 값을 돌려받을 수 있다. 

 

>>> def sort_num(x, y) :
    if x > y :
        return x, y
    else :
        return y, x
 
    
>>> print(sort_num(518), sort_num(2516))
(185) (2516)
cs

 

 이렇게 조건에 따라 각각 값을 받을 수도 있고, 

 

>>> def calculate(x, y) :
    return x + y, x - y, x * y, x / y #사칙연산 결과를 반환
 
>>> x, y = 350210
>>> a1, a2, a3, a4 = calculate(x, y)
>>> print(x, '+', y, '=', a1)
350 + 210 = 560
>>> print(x, '-', y, '=', a2)
350 - 210 = 140
>>> print(x, '*', y, '=', a3)
350 * 210 = 73500
>>> print(x, '/', y, '=', a4)
350 / 210 = 1.6666666666666667
cs

 

 동시 할당문을 이용해 한 번에 여러 값을 넘겨줄 수도 있다. 


 

함수 속 변수는 밖에서도 계속 쓰일 수 있을까?

 

우리는 앞에서 변수에 대해서 다룬 적이 있다. 변수는 어디에서나 쓰이는 만큼 함수 속에서도 쓰이는데, 그렇다면 한 가지 의문이 들 것이다. '함수 속에서 선언한 변수는 함수 밖에서도 적용이 되는가?'  궁금하면 직접 해 보는 것이 답이다. 한 번 해보자.

 

>>> def my_age() :
    age = 19
    print('age =', age)
 
    
>>> age = 20
>>> my_age()
age = 19
>>> print(age)
20
cs

 

 my_age() 함수에서 age라는 변수에 19라는 값을 저장해둔 뒤에 바깥에 있는 age 변수에 20이라는 값을 담았다. 

my_age() 함수를 출력할 때는 age가 19로 나오지만, 정작 age만 출력해 보면 따로 설정한 20이라는 값이 나온다. 이렇게 함수 속에서 선언한 변수가 밖에서도 적용되지 않는 것은 함수 속 변수가 지역변수(Local Variable)로, 밖에서 전체적으로 사용하는 변수 전역변수와는 별개의 메모리에 존재하기 때문이다. 지역변수는 함수가 종료되면 그대로 사라지는 변수다. 

 

 그렇다면 함수 속에서 선언한 변수를 밖에서도 계속 쓸 수는 없을까? 

왜 안 될까? 당연히 할 수 있다! global이라는 키워드를 사용하면 된다. 

def my_age() :
    global age
    age = 19
    print('age =', age)
 
age = 20
my_age()
print(age)
 
= RESTART: ................. 
age = 19
19
cs

 

 'global age' 는 함수 바깥의 전역변수 age를 사용하겠다는 선언이다. 그래서 위처럼 age = 20이라고 선언한 뒤 함수를 실행하면 age = 20이 age = 19로 덮어씌워지기 때문에 my_age()의 age도 19이고, age를 출력해도 19로 출력된다. 

 

 전역변수를 사용하면 함수가 끝나고 사라진 변수를 다시 선언할 필요가 없어지긴 하지만, 전역변수를 많이 사용하면 프로그램의 어느 부분에서 오류가 발생하였는가를 추적하기가 매우 어려워지기 때문에 전역 변수의 사용은 최소로 하는 것이 좋다. 

 


 

여러 종류의 인자

 

매개변수 자리에 인자를 하나하나 넣기 힘들 때는 함수의 매개변수에 기본값을 주면 된다. 이 기본값을 디폴트 인자(Default Argument)라고 한다. 예시로 햄버거 가게에서 주문을 받는다고 해 보자.

 

def burger(num, coke, fries) :
    print('햄버거 {}개 - 콜라 {}, 감자튀김 {}'.format(num, coke, fries))
 
burger(1TrueFalse)
 
= RESTART: ................. 
햄버거 1개 - 콜라 True, 감자튀김 False
cs

 

 이때 인자로 num, coke, fries에 해당하는 값을 넘겨주지 않으면 오류가 발생한다.  

 

>>> burger(1)
Traceback (most recent call last):
  File "<pyshell#63>", line 1in <module>
    burger(1)
TypeError: burger() missing 2 required positional arguments: 'coke' and 'fries'
cs

 

 하지만 매번 콜라와 감자튀김을 선택할지를 선택한다면 번거로울 것이다. 그러면 선택하지 않을 때만 따로 설정하도록 하면 어떨까? 키오스크를 조작해봤다면 쉽게 이해할 수 있을 것이다. 햄버거 세트 메뉴를 주문시키면 기본으로 햄버거 하나와 스몰 사이즈 콜라, 스몰 사이즈 감자튀김이 선택되고, 빼고 싶을 때만 따로 설정을 해주면 된다. 

 

 그렇다면 디폴트 인자를 활용해서 기본값으로 True를 넣어줄 수 있을 것이다. 

 

def burger(num, coke = True, fries = True) :
    print('햄버거 {}개 - 콜라 {}, 감자튀김 {}'.format(num, coke, fries))
 
burger(1)
burger(1, coke = False, fries = True)
 
= RESTART: ................. 
햄버거 1개 - 콜라 True, 감자튀김 True
햄버거 1개 - 콜라 False, 감자튀김 True
cs

 

 burger(1)에서 coke와 fries의 인자를 넣어주지 않았는데도 True로 나오는 것을 볼 수 있다. 

 

 이때 coke = False, fries = True라고 각각 매개변수와 인자를 매치시켜 두었는데, 이렇게 인자 앞에 키워드를 붙인 인자를 키워드 인자(Keyword Argument)라고 한다. 함수를 호출할 때 인자의 이름을 명시적으로 지정해서 전달하는 것이다. 이렇게 키워드 인자를 설정하면 인자가 많을 때 인자의 의미를 헷갈릴 일을 줄인다. 또, 키워드 인자를 사용하면 인자의 순서를 바꿔 넣어도 알아서 매개변수와 인자를 맞추어 출력한다. 

 

def burger(num, coke = True, fries = True) :
    print('햄버거 {}개 - 콜라 {}, 감자튀김 {}'.format(num, coke, fries))
 
burger(coke = True, num = 3)
burger(True1False)
 
= RESTART: ................. 
햄버거 3개 - 콜라 True, 감자튀김 True
햄버거 True개 - 콜라 1, 감자튀김 False
cs

 

 burger(coke = True, num = 3)에서는 num과 coke의 자리를 바꾸고 fries에 대한 인자를 생략했다. 그런데도 햄버거는 3개, 콜라는 포함하고, 감자튀김도 포함해서 출력된다. 

 

 그런데 키워드를 설정하지 않고 그냥 입력하면 def burger(num, coke, fries): 에서 설정한 num, coke, fries 순서대로 인자가 부여되기 때문에 맞지 않는 출력이 나온다. 이런 인자 전달 방식을 위치 인자(Positional Argument)라고 한다. 주의해야 할 점이 하나 있다. 키워드 인자 뒤로 위치 인자가 나올 수는 없다. 이는 파이썬의 문법 중 하나이므로 키워드 인자 뒤로 위치 인자를 넣어 버리면 오류가 난다. 

 

>>> burger(coke = False3)
SyntaxError: positional argument follows keyword argument
cs

 


 

나를 불러 줘! 재귀함수

 

 재귀함수(Recursion)란 함수 내부에서 자기 자신을 호출하는 함수를 말한다. 자기 자신을 불러 오면 뭐가 좋은가 싶을 수도 있지만, 재귀는 잘만 활용하면 매우 유용한 문제 해결 기법으로 처리 단계를 전부 기술하기 어려운 문제를 직관적이고 간단하게 해결할 수 있다. 

 

1! = 1
2! = 2 * 1
3! = 3 * 2 * 1
...
n! = n * (n-1* (n-2* ... * 2 * 1
----------------------------------------
def factorial(n) :                   #재귀함수를 이용한 팩토리얼 계산
    if n <= 1 :                      #종료 조건(1이 되면 곱을 멈춘다.)
        return 1
    else :                           #n이 1이 아닐 경우 
        return n * factorial(n-1)    #n에 (n-1)!을 곱한다. 
 
print('5! =', factorial(5))
----------------------------------------
5! = 120
cs

 

 재귀함수에서는 이 프로그램이 언제 종료되는가가 중요하다. 

위의 팩토리얼 예시에서는 n이 1 이하일 경우에는 팩토리얼을 정의할 필요가 없으므로 if 조건문에서 n이 1이하일 때 1을 반환하고 종료하게 된다. 이 조건문이 없으면... 프로그램은 무한 루프로 빠지게 될 것이다. 아래는 위의 factorial(5)의 재귀 절차를 그림으로 나타낸 것이다.

 

 


 

함수를 저장하고 싶다면 모듈을 만들자!

 

 프로그램이 간단한 경우에는 직접 함수를 호출하고 조합하는 것만으로 코딩할 수 있겠지만 복잡하고 길어지는 순간 그렇게 일일이 불러오고 조합하기 힘들어질 것이다. 좀 더 편안하고 간단한 코딩을 위해서 우리는 모듈(Module)을 활용할 필요가 있다. 

 

 우리는 이미 1장에서 모듈에 대해 간략하게 배운 적 있다. 복습 차 다시 설명하자면, 모듈은 파이썬 함수나 변수, 또는 클래스들을 모아 놓은 스크립트 파일을 말한다. 파이썬을 설치하면 기본적으로 제공되는 모듈을 파이썬 표준 라이브러리(Python Standard Library)라 한다. 이 외에도 여러 기관과 개발자들에 의해서 이미 많은 모듈이 만들어져 있고, 이 모듈을 활용하면 효과적으로 소프트웨어를 개발할 수 있다. 

 

 예시로 datetime 모듈을 한 번 불러와 보자. 

 

>>> import datetime
>>> datetime.datetime.now()
datetime.datetime(202161313459970593)
cs

 모듈을 불러오려면 import (모듈 이름)을 입력하면 된다. 여러 모듈을 불러오고 싶으면 쉼표 , 를 붙이고 모듈 이름을 더 써 주면 된다. 이때 datetime.datetime.now() 에서 맨 앞 datetime은 모듈 이름, 두 번째 datetime은 클래스, now()는 메소드이다. datetime이라는 모듈에 datetime이라는 클래스가 있어서 now()라는 기능(메소드)를 제공하는 것이다. 클래스와 메소드에 접근하려면 마침표 .를 붙여주면 된다.

 

 우리도 모듈을 한 번 만들어 보자.

 

#파일 이름은 Allsum.py 이다.
 
def Sum(num) :              #1부터 num까지의 수를 더하는 함수
    if num <= 1 :
        return 1
    else :
        return num + Sum(num - 1)
---------------------------------
 
>>> import Allsum as As     #as를 통해 모듈을 별명으로 부를 수 있다.
>>> As.Sum(100)
5050
 
---------------------------------
 
>>> from Allsum import Sum  #함수만 바로 불러올 수도 있다. 
>>> Sum(100)
5050
 
---------------------------------
 
>>> from Allsum import *    # *을 import하면 모든 함수를 불러올 수 있다.
>>> Sum(100)
5050 
cs

 

 예시로 1부터 num까지의 수의 합을 반환하는 함수를 만들어보았다. 

이 코드가 모듈로 활용되려면 저장을 해야 한다. 저장할 때 파일 이름이 곧 모듈 이름이 된다. 그러니 파일 이름도 무슨 기능들을 담은 것인지 한 눈에 알 수 있는 이름으로 설정하는 것이 좋다. 

 

 Allsum이라는 모듈 속 Sum(num) 함수를 사용하려면 Allsum.Sum(num) 형태로 호출하면 된다. 

모듈 이름이 너무 길거나 복잡할 때는 별명(Alias)를 지정하여 사용할 수도 있다. 별명을 지으려면 import할 때 모듈 이름 뒤로 as (별명)을 붙여주면 된다. 예시로는 import Allsum 뒤로 as As를 붙여 별명을 'As'로 지었다. 

 

 모듈 전체가 아닌 함수 각각을 가지고 오고 싶다면 from (모듈 이름) import (함수 이름)을 사용하면 된다. 이때 함수 이름 자리에 * 를 입력하면 모듈 내 모든 함수를 사용할 수 있게 된다. 

 


 

마무리

 이번 시간에는 함수를 만들고 이를 모듈로서 활용하는 법에 대해 알아보았다. 

마지막으로 심화문제 6.4와 6.6, 6.8을 풀어보고 마치도록 하자. 접은글 속의 해답 코드는 참고만 하자.

 

6.4 : n1, n2, n3이라는 이름의 매개변수를 3개 입력받아서 이 값들중에서 가장 큰 값과 작은 값, 두 개를 반환하는 max_and_min(n1, n2, n3) 함수를 구현하시오. 사용자로부터 임의의 정수 3개를 입력으로 받아서 이 함수를 호출하여 다음과 같은 출력을 내 보이도록 하라. 

 

3 수를 입력하시오 : 34 66 20
가장 큰 수 : 66
가장 작은 수 : 20
cs
더보기
n1, n2, n3 = map(int, input("3 수를 입력하시오 : ").split())  #세 수를 받고 한 번에 공백 단위로 분리한다.
 
def max_and_min(n1, n2, n3) :
    if n1 > n2 and n1 > n3 :   #n1이 가장 클 때
        if n2 > n3 :           #n2와 n3을 비교하여 최솟값을 찾는다. 
            return n1, n3
        else :
            return n1, n2
 
    elif n2 > n1 and n2 > n3 : #n2이 가장 클 때도 마찬가지
        if n1 > n3 :
            return n2, n3
        else :
            return n2, n1
 
    else :                     #n3이 가장 클 때도 마찬가지
        if n1 > n2 :
            return n3, n2
        else :
            return n3, n1
 
Max, Min = max_and_min(n1, n2, n3)  #최대/최솟값을 각각 변수에 담는다.
print("가장 큰 수 : ", Max)
print("가장 작은 수 : ", Min)
 
cs

 

 

6.6 : 임의의 수를 입력으로 받은 다음 이 수가 소수인지 아닌지를 판단하는 함수 is_prime(n)을 작성하여라. 만일 n이 소수이면 True, 그렇지 않으면 False를 반환하도록 하여라. (힌트: 소수는 1과 자기 자신 이외에는 약수를 가지지 않는다. 따라서 어떤 수 n을 2에서 n-1까지의 수로 나누어 보다 중간에 나누어 떨어지는 수가 있으면 이 수는 소수가 아닐 것이다.)

 

소수 검사를 할 정수를 입력하시오: 9677
소수인가요? : True
소수 검사를 할 정수를 입력하시오: 13
소수인가요? : True
소수 검사를 할 정수를 입력하시오: 4
소수인가요? : False
소수 검사를 할 정수를 입력하시오: 12347987345345
소수인가요? : False
 
 
cs
더보기
def is_prime(x) :
    for i in range(2, x) :      #2부터 x-1까지 아래를 반복한다. 
        if num % i == 0 :       #2부터 x-1까지 나누었을 때 나머지가 0이면 False를 반환
            return False
 
        else :                  #아니면 반복을 계속한다.
            continue
 
    return True                 #나머지가 0이 되는 수가 없으면 True를 반환한다.
 
num = int(input('소수 검사를 할 정수를 입력하시오 : '))
print('소수인가요? :', is_prime(num))
 
cs

 

 

6.8: 사용자로부터 x1, y1, x2, y2의 값을 받아들인 다음 (x1, y1)좌표에서 (x2, y2) 좌표를 잇는 직선의 거리를 출력하고자 한다. distance(x1, y1, x2, y2)를 구현하고 이 함수를 호출하여 다음과 같이 화면에 직선과 거리를 출력하도록 하여라. 

x1 : 4
y1 : 2
x2 : 2
y2 : 1
(4.02.0)과 (2.01.0) 사이의 거리는 2.23606797749979
cs
더보기
def distance(x1, y1, x2, y2) :
    distance = ((x1 - x2) ** 2 + (y1 - y2) ** 2** (1/2)
    return distance
 
x1 = float(input('x1 : '))
y1 = float(input('y1 : '))
x2 = float(input('x2 : '))
y2 = float(input('y2 : '))
 
= (x1, y1)
= (x2, y2)
print(A, '과', B, '사이의 거리는', distance(x1, y1, x2, y2))
 
cs
COMMENT