파이썬에서 재사용할 수 있는 로직을 함수(function)로 정의해서 재사용 할 수 있다. 함수란 입력 값을 가지고 어떤 로직을 수행한 다음 결과물을 리턴해주는 코드 블럭이다.
예를 들어 어떤 숫자를 입력 받아서 짝수인지 판단해주는 로직을 생각해보자.
num = 1
if num % 2 == 0:
is_even = True
else:
is_even = False
어떤 숫자 num이 짝수인지 판단하기 위해 2로 나눈 나머지를 확인한다. 프로그램을 작성하면서 홀짝을 판단해야하는 코드 여기저기에 이 로직이 들어갈 수 있다.
효율적인 코드는 아니지만 예를 들어보면 짝수와 홀수를 판단하는 코드가 다음처럼 반복될 수 있다.
num = 1
if num % 2 == 0:
is_even = True
else:
is_even = False
print(is_even)
num = 4
if num % 2 == 0:
is_even = True
else:
is_even = False
print(is_even)
num = 7
if num % 2 == 0:
is_even = True
else:
is_even = False
print(is_even)
만약 반복되는 저 로직을 떼어서 이름을 붙일 수 있다면? 예를 들면 is_even()이라는 형태로 따로 떼어서 재사용할 수 있다면 다음과 같이 좀 더 깔끔하고 가독성 좋은 코드로 만들 수 있다.
print(is_even(1))
print(is_even(4))
print(is_even(7))
반복되는 저런 로직을 따로 떼어서 재사용할 수 있게 만들어 주는 것이 '함수(function)'다.
함수의 정의
파이썬에서는 함수를 def 키워드로 정의할 수 있다.
def 함수이름(매개변수):
함수의 내용
def 키워드는 함수를 정의할 때 사용하는 키워드다. (파이썬은 함수가 값을 리턴하는지, 어떤 타입의 값을 리턴하는지를 함수 정의부에 적지 않는다.)
def 키워드 뒤에는 함수의 이름이 온다. 뭘하는 함수인지 나타낼 수 있는 이름이 좋다. 예를 들어 입력받은 숫자가 짝수인지 판단하는 함수는 is_even() 같은 이름으로 짓는 것이 좋다. 함수의 정의를 확인하지 않아도 어떤 동작을 하는 함수인지 이름에서 알 수 있기 때문이다. 잘 지은 이름의 함수는 버그를 줄여준다.
함수 이름 뒤에는 함수의 내용에서 사용할 매개변수들이 쓰인다. 정의한 함수를 호출한 쪽에서 넘겨준 값을 받아 사용할 수 있는 변수다.
나머지 함수의 내용은 일반 파이썬 코드에서 사용하는 조건문, 반복문 같은 것들로 로직을 구성하면 된다.
is_even() 함수를 정의해보고 다른 곳에서 호출해보자.
def is_even(num):
if num % 2 == 0:
return True
else:
return False
print (is_even(1))
print (is_even(4))
print (is_even(7))
# False
# True
# False
is_even(1)이 호출되면 매개변수인 num이 1로 할당되어 함수의 내용이 실행된다. 마찬가지로 is_even(4)가 호출되면 num 값이 4로 할당되어 호출된다.
함수의 내용에서 return 키워드를 만나면 함수를 호출한 쪽에 return 키워드 다음에 오는 값을 전달해준다. is_even(4)는 4라는 값을 2로 나눈 나머지를 확인해서 True로 리턴한다. 결국 is_even(4)는 True를 리턴하게 되고, print() 함수가 리턴받은 값을 화면에 출력한다.
함수의 매개변수(Parameter)와 리턴
함수는 여러개의 매개변수를 받을 수 있다. 두 개의 숫자를 입력받아서 두 숫자의 합을 리턴해주는 add() 함수를 정의해보자.
def add (a, b):
return a + b
print(add(1, 2))
print(add(3, 4))
# 3
# 7
이 함수는 매개변수로 2개의 숫자를 입력 받는다. 입력받은 숫자를 더한 다음 리턴해준다.
물론 매개변수가 3개인 경우도 가능하다.
def sum (a, b, c):
return a + b + c
print(add(1, 2, 3))
print(add(4, 5, 6))
# 6
# 15
함수를 정의할 때 매개변수가 꼭 있어야하는 것은 아니다. 매개변수가 없는 함수도 얼마든지 만들 수 있다.
def print_hello():
print("hello!")
print_hello()
print_hello()
# hello!
# hello!
위 코드에서 알아차렸을 수도 있지만 함수는 리턴 값이 없을 수도 있다. 위에서 정의한 print_hello() 함수에는 리턴값이 없다. 그냥 함수의 내용이 실행되고 종료되었을 뿐이다.
참고로 리턴값이 없는 함수의 리턴값을 받아오려고하면 None 값이 받아진다.
매개변수 지정
많은 프로그래밍 언어에서 매개변수의 순서는 매우 중요하다. 함수나 메소드를 호출 할 때, 매개변수는 순서대로 할당된다. 예를들어 add(1, 2)를 호출하면 함수의 내용을 실행할 때, a = 1, b = 2 로 할당되어 실행된다. add(2, 1)을 호출하면 a = 2, b = 1로 할당되어 실행된다. add() 함수의 경우 매개변수의 순서가 바뀌어도 리턴값이 동일하다. 하지만 그렇지 않는 경우가 더 많다.
a의 b 승을 구하는 함수를 정의해보자.
def power(a, b):
num = 1
for i in range(0, b):
num = num * a
return num
print(power(2, 3))
print(power(3, 2))
print(power(b = 2, a = 3))
# 8
# 9
# 9
power(2, 3)은 2의 3승을 리턴한다. 즉 8이 리턴된다. 이 경우 a = 2, b = 3 이 할당되어 함수의 내용이 실행된다.
power(3, 2)는 3의 2승을 리턴한다. 즉 9가 리턴된다. 이 경우 a = 3, b = 2 가 할당되어 함수의 내용이 실행된다. 매개변수의 순서가 바뀌어 리턴되는 결과 값이 바뀌었다.
power(b = 2, a = 3)의 경우 2가 먼저 왔고 3이 뒤에 왔다. 하지만 매개변수의 이름을 명시해서 넘겨줬기 때문에 순서 대신 매개변수의 이름에 맞게 할당되어 두 번째 호출과 같은 결과가 리턴되었다.
이렇듯 파이썬은 함수를 호출할 때 할당할 매개변수를 지정해서 호출할 수 있다.
매개변수의 기본값
함수에서 사용할 수 있는 매개변수는 기본값을 가질 수 있다. 다음 함수를 생각해보자.
def sum(num, start = 1) :
sum_num = 0
for i in range(start, num + 1):
sum_num += i
return sum_num
print(sum(10))
print(sum(10, 5))
print(sum(start = 3, num = 10))
# 55
# 45
# 52
sum() 함수는 두 개의 매개변수를 받는다. start 매개변수부터 num 매개변수까지 숫자를 모두 더해서 리턴해준다. 여기서 start 매개변수는 sum() 함수 호출시 입력해줘도 되고 안해줘도 된다.
sum() 함수 호출시 start 매개변수를 입력하지 않으면 1을 입력한 것으로 간주하고 함수의 내용이 실행된다. 예를들어 sum(10)을 호출하면 sum(10, 1), sum(num = 10, start = 1)이 호출된 것처럼 동작한다.
매개변수의 값이 대부분의 경우 고정이고, 특별한 경우에만 바뀌는 상황에서 기본값을 이용하면 편하다. 주의할 점은 매개변수의 순서다.
sum(start = 5, 10)
# File "./test.py", line 11
# print(sum(start = 3, 10))
# ^
# SyntaxError: positional argument follows keyword argument
이런식으로 호출하면 SyntacError가 발생한다.
우리가 보면 start 매개변수가 5니까 나머지 10은 num 변수에 할당하겠군... 이라고 생각하겠지만 파이썬 인터프리터는 두 번째 오는 10이라는 값을 num에 할당할지 start에 할당할지 알수 없기 때문에 에러를 발생시킨다.
정확히는 매개변수 이름을 적지 않은 인자들(positional argument)이 함수 호출시 인자의 앞쪽에 와야하고, 매개변수를 명시한 인자들은 그 뒤쪽에 와야한다. 다시말해서
sum(10, start = 3)
이렇게 호출하면 순서가 매개변수를 적지 않은 인자 10이 앞에오고, 매개변수를 적은 인자가 뒤쪽에 오기 때문에 정상동작한다.
가변 매개변수(가변인자) 사용
가끔은 몇 개의 인자가 사용될지 모르는 경우도 있다. 예를 들어 입력받은 인자를 모두 더해주는 sum() 함수가 있다고 해보자.
print(sum(1, 2))
print(sum(1, 3, 4))
print(sum(1, 3, 4, 7))
# 3
# 8
# 15
호출하는 쪽에서 몇 개의 인자를 넘겨줄지 모른다. 호출을 해봐야아는 경우가 있다. 다른 언어에서는 모든 경우에 대해서 함수를 정의해야하는 경우가 많다. 하지만 파이썬에서는 가변 인자를 받아 처리할 수 있다.
sum() 함수는 다음과 같이 정의할 수 있다.
def sum(*args):
result = 0
for i in args:
result = result + i
return result
함수를 호출 할 때 몇 개의 인자가 넘겨질지 모르는 경우 *매개변수 형태로 쓰면된다. 이러면 사용자가 입력한 인자값은 튜플의 형태로 args 변수에 저장해준다. 실제로 함수의 내용에서 print(args)라고 입력해보면 함수를 호출한 쪽에서 넘겨준 인자들이 튜플 형태로 출력되는 것을 확인할 수 있다.
가변 매개변수는 일반 매개변수와 섞어서 사용할 수도 있다.
def sum(start, *args):
result = start
for i in args:
result = result + i
return result
print(sum(1, 2))
print(sum(1, 2, 3))
# 3
# 6
첫 번째로 입력한 인자는 start 매개변수에 할당되고, 그 뒤에 오는 인자들은 args에 튜플로 저장되어 처리된다.
만약 함수를 호출하는 쪽에서 매개변수를 지정해서 호출한 경우, 즉 키워드 인자(keyword argument)를 사용한 경우에는 매개변수 앞에 별 두개(**)를 붙여서 받아낼 수 있다.
def sum(**kwargs):
print(kwargs)
sum(start = 1, end = 10)
sum(start = 1, end = 10, print_console = True)
# {'start': 1, 'end': 10}
# {'start': 1, 'end': 10, 'print_console': True}
여러개의 값 리턴하기
많은 프로그래밍 언어에서 함수나 메소드는 하나의 값만 리턴할 수 있다. 여러개의 값을 리턴하기 위해서는 여러개의 값을 컬렉션 같은 곳에 담아서 리턴해야한다. 파이썬은 한 번에 여러개의 값을 리턴할 수 있다.
숫자를 입력받아서 그 숫자부터 연속되는 세 개의 숫자를 리턴해주는 함수를 정의해보자.
def next_values(num):
return num, num + 1, num + 2
a, b, c = next_values(1)
print(a)
print(b)
print(c)
# 1
# 2
# 3
next_values() 함수는 숫자를 인자로 받아서 그 숫자와 +1, +2 한 숫자를 한꺼번에 리턴한다. 그래서 3개의 숫자가 한꺼번에 리턴되어 a, b, c 변수에 담긴다.
사실 이건 여러개의 값이 리턴되는게 아니다. (num, num + 1, num + 2)라는 튜플이 생성되 리턴된 것이고, 튜플을 여러 변수에 한꺼번에 할당하는 코드다. 이 과정이 생략된 것처럼 보여서 여러개의 값이 리턴되는 것처럼 보일뿐이다.
지역변수와 전역변수
함수를 설명하려면 함수의 범위(Scope)를 설명해야한다. 다음 코드를 생각해보자.
num = 0
def add_num(a):
a = a + 10
num = a
add_num(10)
print(num)
이 코드가 실행되면 어떤 값이 리턴될까? 아마 20이라고 생각하는 사람이 많을 것이다. 하지만 결과는 1이다.
이 코드에서 num이라는 변수의 범위에 대해서 생각해봐야한다. num = 1과 print(num)에서 사용한 num 변수와 add_num() 함수의 내용에서 사용한 num 변수는 다른 변수다. 우연히 이름만 같을 뿐이다. 따라서 add_num(10)이 호출되면, 10에 10을 더한 값이 a 변수에 할당되고, a 변수의 값이 num에 할당되었지만 이 변수는 함수 내부에서만 유효한 변수다. 다시말해서 add_num() 안쪽에 있는 num 변수는 num1 혹은 아무의미없는 sdfsadfas 라고 이름을 바꿔도 무방하다.
동일하게 add_num() 안쪽에서 사용한 a라는 변수는 함수 외부에서 사용할 수 없다. 함수가 호출되면서 생겼다가 함수가 종료되면서 사라지기 때문이다.
물론 함수 내부에서 함수 밖의 변수에 접근하는 방법이 있다. 파이썬은 global 키워드를 통해서 함수 외부의 변수에 접근할 수 있다. 위 코드를 다시 바꿔보겠다.
num = 1
def add_num(a):
global num
a = a + 10
num = a
add_num(10)
print(num)
함수의 내용을 살짝 바꿨다. 이 코드를 실행하면 이제 20이라는 값이 출력된다. global num 이라는 코드는 "num 변수를 사용할껀데, 이 값은 함수 밖에 있는 값이야"라는 뜻을 파이썬 인터프리터에게 전달한다. 파이썬 인터프리터는 num = a 를 실행할 때 함수 밖에 있는 num에 a를 할당하게 된다.
참고로 global 키워드는 부분별하게 사용하지 않는 것을 권장한다. 함수는 "어떤 값을 입력할 때, 어떤 값이 리턴되는" 코드 블럭이다. global 키워드로 전역변수에 접근하기 시작하면 함수가 호출되었을 때, 입력과 리턴값(작용) 이외에 다른 어떤 값이 바뀌게되는 부작용(Side effect)이 발생한다. 일반적으로 이런 부작용(Side Effect)은 프로그램을 예측하지 못한 동작으로 이끌게 된다.
파이썬 람다
나중에 별도의 포스트를 할애해서 다루겠지만 파이썬도 람다를 지원한다. (람다가 뭔지 잘 모르겠으면 그냥 넘어가자) 함수의 일종이라고도 할 수 있는 파이썬의 람다는 다음과 같이 정의할 수 있다.
lambda 매개변수, ... : 표현식
예를 들어 두 개의 인자를 곱해주는 람다를 정의해보면
func = lambda a, b : a * b
print(func(2, 10))
# 20
이 코드는 다음 코드와 동일하다.
def func(a, b):
return a * b
print(func(2, 10))
댓글