- 반복적인 작업이 필요할 때 그 작업을 하나로 묶어두어 간편하게 재사용할 수 있도록 하는 것이 함수의 목적이다.
- 어떤 입력값(input)을 받으면 일련의 처리과정(process)을 거친 결과물(output)을 내어놓는 기능을 하는 것이 함수이다.
input
|
| V |__________
| |
| process | <-- 함수(function)
|__________ |
| | |
V
output
함수의 정의와 실행
- 함수의 정의는 아래와 같이 한다.
def 함수이름(입력):
명령
return 출력
- 함수는 여러 개의 입력을 받을 수 있지만 출력값은 항상 하나이며
return
명령을 통해 출력한다.
def 함수이름(입력):
명령1
명령2
return 명령1의 결과 # 실행됨
return 명령2의 결과 # 실행안됨
위와 같이 return
이 하나 이상 존재할 경우 가장 위의 return
만 실행된다. return
은 결과를 출력하고 함수를 빠져나오기 때문.
- 정의한 함수의 실행은 아래와 같이 한다.
함수이름(입력값)
매개변수와 인자
매개변수 (Parameter)
- 매개변수는 외부의 값을 받아서 함수 내부로 불러올 수 있는 비어있는 변수이다.
def func(매개변수1, 매개변수2):
pass
인자 (Argument)
- 인자는 함수를 실행할 때 입력하여 매개변수에 전달되는 실제 값이다.
func(인자1, 인자2)
- 두 개의 값을 받아서 그 합을 구해주는 함수가 있다고 생각해보자.
def sum(a, b): # a와 b는 함수 내부에서 사용되는 매개변수이다.
result = a + b # 함수 내부에서는 매개변수 a와 b의 합을 result라는 변수에 할당한다.
return result # 변수 result를 함수 밖으로 출력한다.
- 실제로 이 함수를 이용해 두 수의 합을 구하기 위해 아래와 같이 함수를 실행한다.
sum(3, 5) # 3과 5라는 인자들이 함수에 입력되었다.
- 이 때, 함수의 내부에서는 아래와 같은 일이 일어난다.
def sum(3, 5): # 3과 5가 각각 매개변수 a와 b에 할당된다.
result = 3 + 5 # 변수 result에는 매개변수 a와 b의 합이 할당되므로 이 경우에는 3과 5의 합인 8이 할당된다.
return result # 8을 함수 바깥으로 반환한다.
함수의 유형
- 함수의 형태에 따라 네 가지의 유형으로 나누어진다.
1. 일반적인 함수
- 형태
def 함수이름(매개변수):
명령
return 출력값
- 실행
함수이름(인자)
2. 입력을 받지 않는 함수
- 형태
def 함수이름():
명령
return 출력값
- 실행
변수 = 함수이름()
3. 출력값이 없는 함수
- 형태
def 함수이름(매개변수):
명령
- 실행
함수이름(인자)
4. 입력, 출력값이 모두 없는 함수
- 형태
def 함수이름():
명령
- 실행
함수이름()
위치 인자와 키워드 인자
위치 인자 (positional argument)
- 매개변수의 순서대로 인자를 입력받아 실행하는 경우의 인자들을 위치 인자라고 한다.
>>> def profile(name, age, country): # 이름, 나이, 국가의 순서로 매개변수를 설정
... return(f'이름: {name} 나이: {age} 국가: {country}')
...
>>> profile('John Petrucci', 50, 'USA') # 인자를 입력할 때에도 이름, 나이, 국가의 순서로 입력해야 의도한 결과를 얻을 수 있다.
이름: John Petrucci 나이: 50 국가: USA
>>> profile(50, 'USA', 'John Petrucci') # 위치 인자의 순서가 바뀔 경우 의도치 않은 결과를 얻게된다.
'이름: 50 나이: USA 국가: John Petrucci'
키워드 인자 (keyword argument)
- 인자들을 매개변수의 이름에 할당하여 받아오는 경우의 인자들을 키워드 인자라고 한다.
>>> profile(age = 50, name = 'John Petrucci', country = 'USA') # 각각의 매개변수에 인자들을 직접적으로 할당하여 전달
'이름: John Petrucci 나이: 50 국가: USA' # 인자들은 나이, 이름, 국가 순으로 전달되었지만 출력결과에서는 제자리를 찾아간 것을 확인할 수 있다.
※ 위치 인자와 키워드 인자를 혼합하여 사용할 수 있다. 이 경우 위치 인자가 먼저 와야한다.
>>> profile('John Petrucci' , age = 50, country = 'USA')
'이름: John Petrucci 나이: 50 국가: USA'
>>> profile('John Petrucci' , age = 50, 'USA')
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
인자 묶음
*
: 위치 인자의 묶음
- 몇 개의 인자를 입력받을지 알 수 없을 경우 아래와 같이
*
를 사용하여 다수의 인자들을 묶어서 받을 수 있다.
def 함수이름(*매개변수):
명령
return 출력값
- 위치 인자 묶음 매개변수는 여러 개의 인자를 받아 튜플의 형태로 반환한다.
>>> def return_args(*args): # args는 arguments를 뜻하며 관례적으로 매개변수의 이름으로 자주 사용한다.
... return args # 인자들을 받아서 그대로 리턴해준다.
...
>>> return_args(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) # 인자가 10개이든
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
>>> return_args(1, 2, 3, 4, 5) # 5개이든
(1, 2, 3, 4, 5)
>>> return_args(1) # 심지어 1개이든 상관없이 모두 잘 작동한다.
(1,) # 튜플의 형태로 리턴하는 것을 확인할 수 있다.
- 위치 인자와 위치 인자 묶음을 동시에 사용할 수 있다.
>>> def sum_or_mult(selector, *args): # 첫 번째 인자 하나를 받는 selector 매개변수와 나머지 인자를 묶어서 받는 *args 매개변수를 설정
... if selector == 'sum': # 첫 번째 인자가 'sum'이면
... result = 0
... for i in args:
... result = result + i # 나머지 인자들의 합을 result에 저장
... elif selector == 'multiply': # 첫 번째 인자가 'multiply'이면
... result = 1
... for i in args:
... result = result * i # 나머지 인자들의 곱을 result에 저장
... return result # result 출력
...
>>> sum_or_mult('sum', 1, 2, 3, 4, 5)
15
>>> sum_or_mult('multiply', 1, 2, 3, 4, 5)
120
위치 인자 묶음 사용 시 주의사항
- 위치 인자 묶음을 혼용할 경우 위치 인자 묶음을 받는 매개변수는 매개변수의 가장 마지막에 와야한다.
그렇지 않으면 위치 인자를 받는 매개변수는 키워드 인자를 받는 매개변수로 변하게 된다.
>>> def sum_or_mult(*args, selector): # selector와 *args의 위치를 바꾸었다.
... if selector == 'sum':
... result = 0
... for i in args:
... result = result + i
... elif selector == 'multiply':
... result = 1
... for i in args:
... result = result * i
... return result
...
>>> sum_or_mult(1, 2, 3, 4, 5, 'sum') # 이렇게 하면 되지 않을까?! 하고 생각할 수 있지만...
Traceback (most recent call last):
File "/home/che1/Projects/python/test.py", line 67, in <module>
sum_or_mult(1, 2, 3, 4, 5, 'sum')
TypeError: sum_or_mult() missing 1 required keyword-only argument: 'selector' # selector라는 키워드 인자가 없다는 에러 메세지가 뜬다.
**
: 키워드 인자의 묶음
- 여러개의 키워드 인자를 받으려면
**
를 사용한다.
def 함수이름(**매개변수):
명령
return 출력값
- 키워드 인자 묶음으로 묶인 인자들은 딕셔너리형태로 반환된다.
>>> def return_kwargs(**kwargs): # *args와 마찬가지로 **kwargs는 키워드 인자 묶음을 뜻하는 관용적인 매개변수 이름이다.
... return kwargs # 키워드 인자들을 받아 그대로 리턴.
...
>>> return_kwargs(a = 1, b = 2, c = 3)
{'a': 1, 'b': 2, 'c': 3} # 딕셔너리 형태로 반환되는 것을 확인할 수 있다.
- 키워드 인자 묶음 또한 다른 인자들과 혼용이 가능하다.
>>> def return_kwargs(message, **kwargs): # 위치 인자와 키워드 인자 묶음을 받음.
... return message, kwargs
...
>>> return_kwargs('hi', a = 1, b = 2, c = 3)
('hi', {'a': 1, 'b': 2, 'c': 3})
키워드 인자 묶음 사용 시 주의사항
- 키워드 인자 묶음을 혼용하는 경우, 위치 인자 묶음과 달리 키워드 인자 묶음의 위치가 앞으로 오면 함수 정의 자체가 안된다.
>>> def return_kwargs(**kwargs, message): # 키워드 인자 묶음과 위치 인자의 순서를 바꾸었다.
... return message, kwargs
...
File "/home/che1/Projects/python/test.py", line 55
def return_kwargs(**kwargs, message):
^
SyntaxError: invalid syntax # 잘못된 문법이라는 에러가 뜬다.
매개변수의 기본값 설정
- 아래와 같이 매개변수의 기본값을 설정해줄 수 있다.
def 함수이름(매개변수=기본값):
명령
return 출력값
- 매개변수의 기본값은 아무 인자도 받지 않을 경우 할당된다.
>>> def default(arg=1):
... return arg
...
>>> default() # 아무 인자도 받지 않은 경우 기본값 1이 할당되어 출력된다.
1
>>>default(3) # 인자가 입력된 경우에는 입력된 인자가 할당되어 출력된다.
3
- 모든 매개변수가 기본값을 가질 필요는 없다.
>>> def send_message(receiver, message, sender='me'): # sender 매개변수만 'me'라는 기본값을 가진다.
... print(f'message sent from {sender}, to {receiver}: {message}')
...
>>> send_message('you', 'Hi!') # 하나의 인자만으로도 함수가 작동한다.
message sent from me, to you: Hi! # sender 매개변수의 기본값인 'me'가 출력된 것을 확인할 수 있다.
매개변수의 기본값 설정시 주의사항
- 기본값을 가지는 매개변수들은 다른 매개변수들의 뒤에 위치하도록 해야한다.
>>> def send_message(receiver, sender='me', message): # 기본값을 가지는 sender 매개변수를 가운데에 위치시켰다.
File "/home/che1/Projects/python/test.py", line 55
def send_message(receiver, sender='me', message):
^
SyntaxError: non-default argument follows default argument # 기본값이 없는 인자가 기본값을 가진 인자 뒤에 온다는 에러가 뜬다.
※ 매개변수 기본값의 정의 시점
- 매개변수의 기본값은 함수가 실행될 때마다 할당되지 않고, 함수가 정의되는 시점에 할당되어 계속해서 사용된다.
>>> def return_list(value, result=[]): # 함수 호출 시에 result를 []로 초기화 해줄 것을 기대하며 이렇게 작성한다.
... result.append(value)
... return result
...
>>> return_list('a')
['a']
>>> return_list('b')
['a', 'b'] # ['b']를 기대했지만 result가 초기화되지 않고 그대로 append 된 것을 볼 수 있다.
- 왜 이런것일까?
id
함수를 이용해 메모리 주소를 확인해보자.
>>> def return_list(value, result=[]):
... print(id(result)) # 리스트에 value 추가하기 전의 메모리 주소 확인
... print(result)
... result += value
... print(id(result)) # 리스트에 value 추가한 후의 메모리 주소 확인
... return result
...
>>> return_list([1])
139644361361800 # 리스트에 value 추가하기 전의 메모리 주소와
[]
139644361361800 # 리스트에 value 추가한 후의 메모리 주소가 같은 것을 확인할 수 있다.
[1]
- 리스트가 아닌 다른 자료형은 어떨까?
>>> def return_list(value, result=0): # 정수를 초기화하도록 설정해보았다.
... print(id(result)) # 정수값에 value 추가하기 전의 메모리 주소 확인
... print(result)
... result += value
... print(id(result)) # 정수값에 value 추가한 후의 메모리 주소 확인
... return result
...
>>> return_list(1)
9285664 # 정수값에 value 추가하기 전의 메모리 주소와
0
9285696 # 정수값에 value 추가한 후의 메모리 주소가 다른 것을 확인할 수 있다.
1
>>> def return_list(value, result=''):
... print(id(result))
... print(result)
... result += value
... print(id(result))
... return result
...
>>> return_list('a') # 문자열도
140248230677168 # value를 더하기 전과
140248229981184 # value를 더한 후의 메모리 주소값이 다른 것을 확인할 수 있다.
a
이는 리스트 자료형이 변경가능한(mutable) 객체이기 때문인 것 같다.
- 리스트 자료형인 경우의 코드 진행 과정을 살펴보자. (코멘트의 숫자는 코드 실행 순서임.)
>>> def return_list(value, result=[]): #1 result에 [] 저장
... result += value #3 [] += ['a'] #6 ['a'] += ['b']
... return result #4 #7
...
>>> return_list(['a']) #2 value = ['a']
['a']
>>> return_list(['b']) #5 value = ['b']
['a', 'b']
result 매개변수의 기본값은 함수가 정의되는 시점 즉, ‘#1’이 실행될 때 정의 된다. 이 시점에 메모리 주소 139644361361800에 빈 리스트 []가 저장되고 result는 항상 이 주소를 가리키게 된다.
‘#2’에서 함수가 호출되어 ‘#3’이 실행되면 139644361361800에 있는 []에 value값이 더해지고 그 결과가 139644361361800에 새로 저장된다.
리스트 자료형은 변경가능하기 때문에 같은 메모리 주소에 저장되어있는 값이 바뀌는 것이 가능하다.
이 때문에 ‘#3’의 결과가 메모리 주소 139644361361800에 그대로 남아 ‘#5’가 실행되었을 때 다시 호출되어 [‘b’]가 추가된 [‘a’, ‘b’]가 출력되는 것이다.
함수가 정의된 시점인 ‘#1’에서 result는 메모리 주소 139644361361800를 가리키도록 정의되었으므로, 함수가 호출된 후에 result는 항상 메모리 주소 139644361361800를 가리킨다.
- 정수 타입의 기본값일 경우를 살펴보자
>>> def return_list(value, result=0): #1 result에 0 저장
... result += value #3 0 + 1 #6 0 + 2
... return result #4 #7
...
>>> return_list(1) #2 value = 1
1
>>> return_list(2) #5 value = 2
2
정수의 경우 함수가 정의된 시점 ‘#1’에서 메모리 주소 9285664에 0이 저장되고 result는 이 곳을 참조한다. 이후 ‘#2’에서 함수가 호출되고 ‘#3’에서 result에 + 1을 하여 result가 1이 된다.
이 때, 메모리 주소 9285664에 있던 0에 1이 더해진 것이 아니라, 새로운 메모리 주소 9285696에 1이 저장되고 result가 그 주소를 참조하도록 바뀐다.
이렇게 되는 이유는 정수는 변경이 불가능한 (immutable) 객체이며 각각의 정수값들은 고유의 메모리 주소를 할당받기 때문이다.
그렇기 때문에 ‘#5’에서 함수가 호출되어 ‘#6’이 실행될 때 메모리 주소 9285664에 저장되어 있는 값 0이 호출되어 result가 초기화 된 것 처럼 보이게 된다.
이것의 결과로 1 + 2가 아닌 0 + 2의 값을 돌려받게 된다.
함수가 정의된 시점인 ‘#1’에서 result는 메모리 주소 9285664 가리키도록 정의되었으므로 함수가 호출된 후에는 항상 메모리 주소 9285664을 가리킨다. 문자열 또한 변경 불가능한 객체이기 때문에 정수일 때와 같은 결과를 낸다.
이해가 잘 안되어서 길게 한 번 풀어써보았는데 아직도 헷갈린다. 나중에 더 나은 이해를 했을 때 다시 작성해봐야지ㅋ
Reference
- 이한영 강사님 github: https://github.com/Fastcampus-WPS-6th/Python/blob/master/09.%20%ED%95%A8%EC%88%98.md
- 점프 투 파이썬: https://wikidocs.net/24