같은 일을 여러 곳에서
반복문은 한 곳의 코드를 여러 번 실행하고, 함수는 같은 코드를 여러 곳에서 불러 씁니다.
과목마다 평균을 구하려면 함수 없이는 이렇게 됩니다.
kor = [90, 85, 72]
eng = [65, 80, 95]
math_ = [88, 91, 70] # math는 파이썬 모듈 이름이라 끝에 밑줄을 붙여 피한다
print(round(sum(kor) / len(kor), 1)) # 82.3
print(round(sum(eng) / len(eng), 1)) # 80.0
print(round(sum(math_) / len(math_), 1)) # 83.0같은 계산이 세 곳에 흩어져 있습니다. 반올림 자릿수를 바꾸려면 세 군데를 모두 고쳐야 하고, 한 군데를 빠뜨리면 그 줄만 조용히 다른 값을 냅니다.
함수(function)
어떤 일을 하는 코드에 이름을 붙여 두고, 필요할 때 그 이름으로 불러 실행하게 한 것입니다. 코드는 한 곳에만 적히므로, 고칠 일이 생겨도 그 한 곳만 고칩니다.
def average(scores): # 「평균을 구하는 코드」에 이름을 붙인다
return round(sum(scores) / len(scores), 1)
print(average([90, 85, 72])) # 82.3
print(average([65, 80, 95])) # 80.0
print(average([88, 91, 70])) # 83.0반복문 — 한 곳을 여러 번
같은 코드를 연달아 여러 번 실행합니다. 언제 실행되는지는 반복문이 놓인 위치가 정합니다.
함수 — 여러 곳에서 같은 코드를
서로 떨어진 여러 곳에서 같은 코드를 불러 씁니다. 언제 실행할지는 부르는 쪽이 정합니다.
이름만 보고도 하는 일을 압니다
round(sum(scores) / len(scores), 1)은 읽고 나서야 평균인 줄 압니다.
average(scores)는 보자마자 압니다.
함수는 코드를 줄여 줄 뿐 아니라, 읽는 사람이 코드를 이름으로 이해하게 해 줍니다.
스스로 확인
평균을 소수 둘째 자리까지로 바꾸려 합니다. 두 코드에서 각각 몇 군데를 고쳐야 할까요?
정의와 호출
함수를 적어 두는 일과 실행하는 일은 따로입니다.
| 용어 | 형태 | 하는 일 |
|---|---|---|
| 정의(definition) | def 이름(): 아래 들여쓰기 |
함수가 할 일을 적어 둔다. 이때는 실행되지 않는다 |
| 호출(call) | 이름() |
적어 둔 코드를 실제로 실행한다 |
def greet():
print("안녕하세요")
# 여기까지는 아무것도 출력되지 않는다. 「함수를 정의했을 뿐」이다.
greet() # 안녕하세요 호출해야 비로소 실행된다
greet() # 안녕하세요 몇 번이든 호출할 수 있다요리법을 적어 두는 것과 요리를 하는 것
요리법을 적었다고 밥이 되지 않습니다. 정의는 요리법을 적어 두는 일이고, 호출은 실제로 요리를 하는 일입니다. 적어 둔 함수는 몇 번이든 다시 호출할 수 있습니다.
호출하기 전에 정의가 있어야 합니다
코드는 위에서 아래로 실행됩니다. 실행이 아직 def 줄에 이르지 않았다면 그 함수는 아직 정의되지 않은 것이라 NameError가 납니다.
greet() # NameError: name 'greet' is not defined
def greet():
print("안녕하세요")def줄 끝의 콜론과 들여쓰기는if·for에서와 같은 규칙이다.- 들여쓰기가 끝나는 곳이 함수의 끝이다.
- 함수 이름은 하는 일이 드러나게 짓는다 —
f보다average가 낫다.
스스로 확인
함수를 정의만 하고 한 번도 호출하지 않은 프로그램을 실행하면 무엇이 출력될까요?
매개변수와 인자
같은 함수를 다른 값에 쓰려면 값을 받을 변수가 있어야 합니다.
def greet(name): # name은 인자를 전달받는 매개변수
print(name + "님, 안녕하세요")
greet("학생 A") # 학생 A님, 안녕하세요
greet("학생 B") # 학생 B님, 안녕하세요| 용어 | 예 | 뜻 |
|---|---|---|
| 매개변수(parameter) | def greet(name)의 name |
정의할 때 괄호 안에 적어 두는, 전달받은 값을 저장할 변수 |
| 인자(argument) | greet("학생 A")의 "학생 A" |
호출할 때 괄호 안에 넣어 실제로 전달하는 값 |
매개변수는 값을 「받는 변수」이고 인자는 「전달하는 값」입니다. 함수를 호출하면 인자가 매개변수에 대입된 뒤 함수 안의 코드가 실행됩니다.
print에서 sep·end를 지정하고 len의 괄호 안에 값을 넣던 것도 모두 인자를 전달하는 일이었습니다.
매개변수가 여럿이면 인자는 적은 순서대로 들어갑니다.
sep="-"처럼 매개변수 이름을 붙여 넣은 인자만은 순서와 상관없이 그 이름의 매개변수에 들어갑니다.
def bmi(weight, height): # 인자를 순서대로 받는다
return round(weight / (height * height), 1)
print(bmi(62, 1.70)) # 21.5
print(bmi(1.70, 62)) # 0.0 순서를 바꾸면 다른 값이 나온다순서를 바꾸면 오류가 아니라 「틀린 값」입니다
둘 다 숫자이므로 파이썬은 그대로 계산합니다.
멈추지 않고 이상한 값이 나오는 쪽이 찾기 더 어렵습니다.
매개변수 이름을 weight·height처럼 분명히 지어 두면 이런 실수를 줄일 수 있습니다.
인자가 거의 늘 같은 값이라면 매개변수에 기본값을 정해 두고, 다를 때만 인자를 넣게 할 수 있습니다.
def greet(name, greeting="안녕하세요"): # 기본값은 뒤쪽 매개변수에만 둘 수 있다
print(name + "님, " + greeting)
greet("학생 A") # 학생 A님, 안녕하세요 인자를 생략하면 기본값을 쓴다
greet("학생 A", "반갑습니다") # 학생 A님, 반갑습니다 인자를 넣으면 그 값을 쓴다스스로 확인
bmi(62, 1.70)에서 62와 1.70 중 어느 것이 weight에 들어갈지는 무엇이 정할까요?
값을 반환하기
출력하는 것과 반환하는 것은 전혀 다른 일입니다.
def add(a, b):
return a + b # 값을 「반환한다」
result = add(3, 5) # 반환값을 변수에 저장한다
print(result) # 8
print(add(3, 5) * 10) # 80 반환값은 계산에 바로 쓸 수 있다return
함수를 그 줄에서 끝내고, 호출한 곳에 값을 반환합니다. 이렇게 반환하는 값을 반환값(return value)이라고 합니다. 호출한 곳에서는 그 값을 변수에 저장하거나 계산에 바로 쓸 수 있습니다.
여기서 가장 자주 헷갈리는 것이 있습니다. print는 화면에 출력하는 일이고,
return은 호출한 곳에 값을 넘기는 일입니다. 겉보기에는 둘 다 「결과가 나온 것」처럼 보입니다.
def add_print(a, b):
print(a + b) # 출력만 한다. return이 없다
def add_return(a, b):
return a + b # 반환한다
x = add_print(3, 5) # 8 출력은 된다
print(x) # None return이 없으면 None이 반환된다
y = add_return(3, 5) # 아무것도 출력되지 않는다
print(y) # 8 반환값을 받았다print만 하는 함수
화면에는 결과가 보입니다. 그러나 호출 결과를 변수에 저장하면 None이 들어가므로
다음 계산에 쓸 수 없습니다.
return하는 함수
화면에 저절로 출력되지는 않습니다. 대신 반환값이 남아 저장하고 더하고 비교할 수 있습니다.
None
리스트에서 본, 「값이 없음」을 나타내는 특별한 값입니다.
return이 없는 함수도 끝나면 None을 반환합니다.
계산 결과가 뜻밖에 None이면 return을 빠뜨리지 않았는지부터 확인합니다.
return을 만나면 함수는 그 줄에서 바로 끝납니다.
그래서 조건마다 return을 두면 else 없이도 경우가 나뉩니다.
def sign(n):
if n > 0:
return "양수" # 여기서 함수가 「끝난다」
if n < 0:
return "음수"
return "영"
print(sign(5)) # 양수
print(sign(-3)) # 음수
print(sign(0)) # 영스스로 확인
평균을 구하는 함수가 print로 출력만 하고 있다면, 그 평균으로 등급을 매기려 할 때 무엇이 막힐까요?
변수를 쓸 수 있는 범위
함수 안에서 만든 변수는 함수가 끝나면 사라집니다.
def average(scores):
total = sum(scores) # total은 지역 변수라 함수 「안에서만」 쓸 수 있다
return total / len(scores)
print(average([90, 85, 72])) # 82.33333333333333
print(total) # NameError: name 'total' is not defined지역 변수와 전역 변수
- 지역 변수(local variable) — 함수 안에서 만든 변수. 매개변수도 지역 변수다. 함수가 끝나면 사라진다.
- 전역 변수(global variable) — 함수 밖에서 만든 변수. 함수 안에서도 값을 읽을 수 있다.
불편한 규칙이 아니라 지켜 주는 규칙입니다
함수마다 total·i 같은 변수 이름을 마음 놓고 쓸 수 있는 것이
이 규칙 덕분입니다. 함수 안에서 어떤 값을 대입하든 바깥에 있는 같은 이름의 변수는 그대로입니다.
다만 인자로 받은 리스트를 append처럼 직접 고치면 바깥에서도 바뀝니다.
매개변수가 바깥의 그 리스트에 이름을 하나 더 붙인 것이기 때문입니다.
함수 안에서 전역 변수에 값을 다시 대입하려면 global로 그렇게 하겠다고 밝혀야 합니다.
count = 0
def visit():
global count # 「바깥의 그 count를 쓰겠다」고 밝힌다
count = count + 1
visit()
visit()
print(count) # 2되도록 값을 반환하는 쪽을 고릅니다
global을 쓰면 함수가 바깥의 변수를 몰래 바꾸게 되어,
값이 어디서 달라졌는지 찾기 어려워집니다.
인자로 받고 결과를 반환하면 오가는 값이 호출하는 줄에 그대로 드러납니다.
count = 0
def visit(count): # 인자로 받아서
return count + 1 # 1을 더한 값을 반환한다
count = visit(count)
count = visit(count)
print(count) # 2 global 없이도 된다다만 어느 쪽이 늘 옳은 것은 아닙니다. 여러 함수가 함께 읽어야 하는 값이라면 전역 변수로 두는 편이 나을 때도 있습니다. 값이 오가는 길이 눈에 보이는 쪽을 고르면 됩니다.
스스로 확인
두 함수가 저마다 total이라는 변수를 쓰고 있습니다. 서로 부딪히지 않는 이유는 무엇일까요?
이미 만들어져 있는 함수
직접 만들기 전에 있는지부터 찾습니다.
print·len·input도 함수입니다.
이렇게 파이썬에 처음부터 들어 있는 함수를 내장 함수(built-in function)라고 합니다.
scores = [90, 85, 72, 68]
print(len(scores)) # 4 개수
print(sum(scores)) # 315 합
print(max(scores)) # 90 가장 큰 값
print(min(scores)) # 68 가장 작은 값
print(round(78.456, 1)) # 78.5 반올림
print(abs(-3)) # 3 절댓값더 특별한 계산을 하는 함수는 따로 묶여 있어서, 필요할 때 가져와 씁니다.
모듈(module)
관련 있는 함수들을 모아 둔 묶음입니다.
처음부터 모두 가져오면 무거우므로, import로 필요한 모듈만 가져옵니다.
import math # 「math 모듈을 가져오겠다」
print(math.sqrt(16)) # 4.0 제곱근
print(math.gcd(12, 18)) # 6 최대공약수
print(math.floor(3.7)) # 3 내림
print(math.ceil(3.2)) # 4 올림가져오는 방법은 몇 가지가 있습니다. 어느 하나가 옳은 것은 아니고, 무엇을 드러내고 싶은지에 따라 고릅니다.
import math
print(math.sqrt(16)) # 4.0 모듈 이름을 앞에 붙여 호출한다
import math as m
print(m.sqrt(16)) # 4.0 모듈 이름에 짧은 별명을 붙인다
from math import sqrt
print(sqrt(16)) # 4.0 필요한 함수만 가져오면 모듈 이름 없이 호출한다| 쓰는 법 | 고르는 이유 |
|---|---|
import math |
호출할 때마다 어느 모듈의 함수인지 드러난다. 무난하게 쓸 수 있다 |
import math as m |
모듈 이름이 길고 여러 번 호출할 때 짧게 쓴다 |
from math import sqrt |
몇 개만 쓸 때 모듈 이름 없이 호출한다. 내가 만든 변수와 이름이 충돌할 수 있다 |
무작위로 값을 고르는 random 모듈도 자주 씁니다.
이 모듈의 함수는 실행할 때마다 달라질 수 있는 값을 반환합니다.
import random
random.seed(1) # 시드를 고정하면 「같은 순서」로 나온다
print(random.randint(1, 100)) # 18 1 이상 100 「이하」의 정수
print(random.randint(1, 100)) # 73
print(random.choice(["가위", "바위", "보"])) # 가위 하나를 무작위로 골라 반환한다시드를 고정하면 같은 결과가 나옵니다
random.seed로 시드(seed) 값을 정해 두면 매번 같은 순서로 값이 나옵니다.
무작위가 아니게 되는 것이 아니라, 같은 무작위 순서를 재현하는 것입니다.
결과를 다른 사람과 맞춰 보아야 할 때 씁니다. 이 예제의 값이 여러분 화면에도 똑같이 나오는 것도 이 때문입니다.
평균을 구하는 함수와 등급을 매기는 함수를 함께 쓰면 이렇게 됩니다. 두 함수로 나누어 두니 마지막 줄이 하는 일이 그대로 읽힙니다.
def average(scores):
return round(sum(scores) / len(scores), 1)
def grade(score):
if score >= 90:
return "A"
if score >= 80:
return "B"
return "C"
scores = [90, 85, 72, 68]
avg = average(scores)
print(f"평균 {avg}점, 등급 {grade(avg)}") # 평균 78.8점, 등급 C스스로 확인
직접 만든 average와 내장 함수 sum은 쓰는 쪽에서 보면 무엇이 다를까요?
핵심 정리
정의와 호출은 다르다
def는 함수를 적어 두는 일이고,
이름()으로 호출해야 실행된다. 호출하기 전에 정의가 있어야 한다.
인자는 순서대로 들어간다
이름을 붙이지 않은 인자는 적은 순서대로 매개변수에 대입된다. 순서를 바꾸면 오류 없이 틀린 값이 나올 수 있다.
print와 return
출력은 화면에 보여 주는 일, 반환은 호출한 곳에 값을 넘기는 일이다.
return이 없으면 None이 반환된다.
지역 변수
함수 안에서 만든 변수는 함수 밖에서 쓸 수 없다. 덕분에 함수마다 같은 이름을 마음 놓고 쓸 수 있다.
있는 것부터 찾는다
len·sum·max는 내장 함수이고,
math·random은 import로 가져오는 모듈이다.
가져오는 방법 셋은 무엇을 드러낼지에 따라 고른다.
확인 퀴즈
배운 내용을 확인해 봅시다.
1. def로 함수를 정의하기만 하고 호출하지 않으면?
2. 함수가 print만 하고 return이 없을 때, x = 함수()의 x는?
3. 다음 중 틀린 설명은?