정보

함수

큰 문제는 작은 문제로 쪼개어 풉니다.
그 작은 문제 하나하나가 코드에서는 무엇이 될까요?
이름을 붙인 코드 묶음, 곧 함수가 됩니다.

학습 목표

  1. 같은 일이 여러 곳에 흩어져 있을 때 함수로 묶으면 무엇이 좋아지는지 설명할 수 있다.
  2. 매개변수로 값을 전달받고 return으로 결과를 반환한다는 것을 알고, 간단한 함수를 만들어 쓸 수 있다.
  3. 함수 안에서 만든 변수를 밖에서 쓸 수 없는 까닭을 설명하고, 문제를 쪼개어 함수로 나눌 수 있다.

같은 일이 세 군데에 흩어져 있으면

고칠 때도 세 군데를 찾아다녀야 합니다.

문구점에서 물건 세 개를 10% 할인해 파는 프로그램을 만든다고 해 봅시다. 할인가는 가격에 0.9를 곱하고 반올림하면 됩니다. 함수 없이 쓰면 이렇게 됩니다.

print(round(12000 * 0.9))
print(round(8500 * 0.9))
print(round(23000 * 0.9))

10800, 7650, 20700이 나옵니다. 잘 실행됩니다. 그런데 같은 모양의 계산이 세 번 나와 있는 것이 눈에 걸립니다. 실제로 문제가 되는 것은 할인율이 바뀔 때입니다. 20% 할인으로 바뀌면 0.9를 세 군데에서 찾아 0.8로 고쳐야 합니다. 하나를 빠뜨리면 물건 하나만 값이 다르게 나오는데, 오류는 나지 않습니다.

같은 일에 이름을 붙여 한 군데에만 적어 두면 이 문제가 사라집니다.

def discount(price):
    return round(price * 0.9)


print(discount(12000))
print(discount(8500))
print(discount(23000))

함수(function)

특정한 일을 하는 코드를 묶어 이름을 붙인 것입니다. def로 만들고, 그 이름으로 함수를 실행하는 것을 호출(call)이라고 합니다.

이제 20% 할인으로 바뀌어도 고칠 곳은 한 군데뿐입니다. discount 안의 0.9를 0.8로 바꾸면 세 곳의 결과가 한꺼번에 바뀝니다.

줄 수는 오히려 늘었습니다. 그런데도 더 나은 코드입니다. discount(12000)이라고 적힌 줄을 읽으면 계산 방법을 몰라도 「할인가를 구하는구나」를 알 수 있기 때문입니다. 이름만 보고 하는 일을 알 수 있다는 점은 변수 이름을 지을 때와 같습니다.

스스로 확인

물건이 세 개가 아니라 서른 개라면, 두 코드에서 할인율을 바꾸는 데 각각 몇 군데를 고쳐야 할까요?

값을 받아서 값을 돌려준다

함수는 자판기와 비슷합니다.

자판기에 돈을 넣으면 음료가 나옵니다. 안에서 어떻게 작동하는지는 몰라도 됩니다. 무엇을 넣으면 무엇이 나오는지만 알면 쓸 수 있습니다. 함수도 그렇습니다.

함수는 값을 전달받아 값을 반환한다 12000 넣는 값 discount 가격의 90% 하는 일 10800 나오는 값 값을 전달받는 변수를 매개변수, 반환하는 값을 반환값이라 한다

매개변수(parameter)

함수가 바깥에서 전달받은 값을 저장하는 변수입니다. def discount(price):의 price가 그것입니다.

discount(12000)이라고 호출하면 함수 안에서 price가 12000이 됩니다.

반환값(return value)

함수가 일을 마치고 호출한 곳으로 돌려주는 값입니다. return 뒤에 적습니다.

return을 만나면 즉시 함수가 끝나고 값이 호출한 곳으로 돌아갑니다.

매개변수는 하나가 아니어도 됩니다. 쉼표로 나누어 여러 개를 적습니다. 할인율까지 매개변수로 전달받으면 한 함수로 여러 할인율을 다룰 수 있습니다.

def discount(price, rate):
    return round(price * (1 - rate))


print(discount(12000, 0.1))
print(discount(12000, 0.25))

10800과 9000이 나옵니다. 순서가 중요합니다. 앞의 값이 price, 뒤의 값이 rate가 됩니다. discount(0.1, 12000)처럼 순서를 바꿔 호출하면 엉뚱한 값이 나옵니다.

화면에 출력하는 것과 반환하는 것은 다릅니다

함수 안에서 print만 하고 return을 적지 않으면, 화면에는 보이지만 반환값은 없습니다. 그런 함수의 결과를 변수에 대입해 보면 「아무것도 없음」을 뜻하는 None이 들어 있습니다. 보여 주는 것과 넘겨주는 것은 다른 일입니다. 계산 결과를 뒤에서 또 쓸 것이라면 반드시 return으로 반환해야 합니다.

스스로 확인

가로와 세로를 전달받아 직사각형의 넓이를 반환하는 함수를 만든다면, 매개변수는 몇 개이고 반환값은 무엇이 될까요?

함수 안에서 만든 변수는 그 안에서만 쓸 수 있다

불편해 보이지만, 이것이 함수를 안전하게 만듭니다.

함수 안에서 변수를 하나 만들고, 함수 밖에서 그 변수를 사용하면 어떻게 될까요?

def make_total(price):
    total = price * 3
    return total


print(make_total(1000))
print(total)

3000이 출력되고 나서 오류가 나며 멈춥니다. total이라는 이름이 정의되어 있지 않다는 것입니다. 분명히 함수 안에서 만들었는데 말입니다.

함수 안에서 만든 이름은 함수가 끝나면 사라집니다

함수가 일을 하는 동안에만 잠깐 쓰이고, 일이 끝나면 그 변수도 함께 없어집니다. 그래서 밖에서는 그 이름을 쓸 수 없습니다. 그 값을 밖에서 쓰려면 return으로 반환해야 합니다.

왜 이렇게 만들어 두었을까

처음에는 불편하게 느껴집니다. 그런데 반대였다면 어떤 일이 벌어질지 생각해 보세요.

함수 안에서 쓴 이름이 밖에도 남는다면, 여러 함수가 우연히 같은 이름을 쓰는 순간 서로의 값을 덮어씁니다. total이나 i처럼 흔한 이름이라면 거의 반드시 부딪힙니다. 그러면 함수를 하나 가져다 쓸 때마다 그 안에서 무슨 이름을 쓰는지 전부 확인해야 합니다. 자판기 안을 열어 봐야 하는 셈입니다.

안과 밖을 나누어 두었기 때문에 함수를 마음 놓고 가져다 쓸 수 있습니다. 무엇을 넣으면 무엇이 나오는지만 알면 되고, 안에서 무슨 이름을 쓰든 밖에 영향을 주지 않습니다. 불편해 보이던 규칙이 사실은 함수를 믿고 쓸 수 있게 해 주는 장치였습니다.

스스로 확인

total을 밖에서 사용한 코드가 오류가 나지 않게 고치려면 어떻게 하면 될까요? total의 값을 밖에서 쓰고 싶다면 무엇을 해야 할지 말해 보세요.

문제를 쪼갠 조각이 그대로 함수가 된다

함수를 배우는 진짜 이유는 여기 있습니다.

큰 문제를 만나면 손댈 수 있는 크기로 쪼개는 것이 첫걸음입니다. 「점수를 보고 등급을 알려 주는 프로그램」도 통째로 놓으면 막막하지만, 쪼개 보면 두 개의 작은 문제입니다.

1

점수들의 평균을 구한다

점수 목록을 전달받아 평균 하나를 반환하면 되는 일입니다.

2

평균을 보고 등급을 정한다

수 하나를 전달받아 등급 문자 하나를 반환하면 되는 일입니다.

두 조각 모두 「무엇을 전달받아 무엇을 반환하는가」가 뚜렷합니다. 그러면 그대로 함수 하나씩이 됩니다.

def average(values):
    return sum(values) / len(values)


def grade(value):
    if value >= 80:
        return 'A'
    elif value >= 60:
        return 'B'
    else:
        return 'C'


scores = [88, 74, 95, 61, 79]
result = average(scores)

print(round(result, 1), grade(result))

79.4 B가 나옵니다. 함수 정의 아래의 세 줄은 점수를 놓고, 평균을 구하고, 등급을 붙입니다. 코드가 문제를 쪼갠 순서 그대로 읽힙니다.

고칠 곳도 뚜렷해집니다. 등급 기준이 바뀌면 grade만 보면 되고, 평균 대신 중간값을 쓰기로 하면 average만 고치면 됩니다. 한쪽을 고쳐도 다른 쪽이 흔들리지 않습니다. 쪼개 두었기 때문입니다.


이미 만들어져 있는 함수들

자주 쓰이는 일은 파이썬이 미리 함수로 만들어 두었습니다. 이런 것을 내장 함수라고 합니다. 리스트의 개수를 세는 len, 다 더하는 sum도 그런 것입니다.

scores = [88, 74, 95, 61, 79]

print(len(scores))
print(max(scores))
print(min(scores))
print(round(sum(scores) / len(scores), 1))
print(sorted(scores))
자주 쓰는 내장 함수와 하는 일
함수 반환값 점수 목록에 쓴 결과
len 개수 5
max · min 가장 큰 값 · 가장 작은 값 95, 61
sum 다 더한 값 평균을 구하는 데 썼습니다
round 반올림한 값. 딱 절반이면 짝수 쪽으로 가서 round(2.5)는 2가 된다 79.4 — 뒤의 1이 남길 소수점 아래 자릿수입니다
sorted 정렬한 새 목록 [61, 74, 79, 88, 95]

sorted가 있는데 왜 정렬 방법을 알아야 할까

실제로 프로그램을 만들 때는 이미 만들어진 것을 쓰는 편이 낫습니다. 직접 짜면 틀릴 여지가 있고, 남이 읽기도 어렵습니다. 그런데도 정렬이 어떻게 작동하는지 알아 둘 이유가 있습니다. 안이 보이지 않는 것을 쓸 때 무엇을 조심해야 하는지는 안을 살펴본 사람이 더 잘 압니다. 데이터가 아주 많을 때 왜 오래 걸리는지, 어떤 상황에서 유리한지 같은 것들입니다.

스스로 확인

「일주일치 걸음 수를 전달받아 가장 많이 걸은 날과 평균을 알려 주는 프로그램」을 만든다면, 함수를 몇 개로 쪼개겠습니까? 각각 무엇을 전달받아 무엇을 반환할까요?

핵심 정리

함수

function

  • 특정한 일을 하는 코드를 묶어 이름을 붙인 것
  • 고칠 곳이 한 군데로 모인다
  • 이름만 보고도 하는 일을 알 수 있어 읽기 쉬워진다

매개변수와 반환값

넣는 것과 나오는 것

  • 매개변수 — 바깥에서 전달받은 값을 저장하는 변수
  • 반환값 — return으로 호출한 곳에 돌려주는 값
  • 화면에 출력하는 것과 반환하는 것은 다르다

안과 밖

변수를 쓸 수 있는 범위

  • 함수 안에서 만든 변수는 함수가 끝나면 사라진다
  • 값을 밖에서 쓰려면 return으로 반환한다
  • 안과 밖을 나누어 두었기에 마음 놓고 가져다 쓸 수 있다

쪼개면 함수가 된다

배우는 이유

  • 작은 문제 하나가 함수 하나가 된다
  • 코드가 쪼갠 순서 그대로 읽힌다
  • 자주 쓰는 일은 이미 내장 함수로 있다

확인 퀴즈

배운 내용을 확인해 봅시다.

Q1

같은 계산을 세 곳에 적는 대신 함수로 묶으면 좋은 점은?

Q2

함수 안에서 만든 변수를 함수 밖에서 사용하면 어떻게 될까?

Q3

함수 안에서 print만 하고 return을 적지 않았다면?