지금까지 간단하게 문자열을 출력하는 함수와 두 수를 더하는 함수를 만들었다. 파이썬에서는 함수를 좀 더 편리하게 사용할 수 있도록 다양한 기능을 제공한다. 이번에는 함수에서 위치 인수와 키워드 인수를 사용하는 방법과 리스트와 딕셔너리의 언패킹을 활용하는 방법에 대해서 알아보자.
>>> print(10, 20, 30)
10 20 30
위와 같이 함수에 인수를 순서대로 넣는 방식을 위치 인수(positional argument) 라고 한다. 즉, 인수의 위치가 정해져있다. print
에 10, 20, 30 을 순서대로 넣어서 출력할 때도 10, 20, 30 순서대로 출력이 된다. 우리가 지금까지 흔히 사용한 방법이다.
그렇다면 간단하게 숫자 3개를 출력하는 함수를 만들어보자.
def print_number(a, b, c):
print(a)
print(b)
print(c)
print_number(10, 20, 30)
출력 결과
10
20
30
이와 같이 인수를 순서대로 넣을 때 리스트나 튜플을 사용할 수도 있다. 다음과 같이 리스트나 튜플 앞에 *
(에스터리스트)를 붙여서 함수에 넣어주면 된다.
>>> x = [10, 20, 30]
>>> print_numbers(*x)
10
20
30
리스트 x
에 *
만 앞에 붙여줬을 뿐인데 함수의 인수로 10, 20, 30이 순서대로 들어간다. 즉, 리스트나 튜플 앞에 *
를 붙여주면 언패킹(unpacking)이 되어 print_numbers(10, 20, 30)
과 똑같은 동작이 된다. 말 그대로 리스트의 포장을 푼다는 의미이다.
>>> print_numbers(*[10, 20, 30])
10
20
30
리스트 변수 대신 리스트 앞에 바로 *
를 붙여도 동작은 같다.
단, 이때 주의할 점은 인수의 개수와 리스트 요소의 개수가 일치해야 한다는 점이다. 만약 개수가 다르다면 함수를 호출할 수 없다. 여기에서는 print_numbers
함수가 인수 3개를 받으니까 리스트나 튜플의 요소의 개수가 3개인 것을 건내줘야 한다. 만약 그렇지 않다면 오류가 발생한다.
>>> print_numbers(*[10, 20])
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
print_numbers(*[10, 20])
TypeError: print_numbers() missing 1 required positional argument: 'c'
그러면 위치 인수와 리스트 언패킹은 어디에서 사용할까? 이 기능들은 인수의 개수가 정해져 있지 않은 가변 인수(variable argument)에 사용한다. 즉, 같은 함수에 인수 한 개를 넣을 수 있고, 열 개를 넣을 수 있다. 또는 인수를 안 넣을수도 있다.
다음과 같이 가변 인수 함수는 매개변수 앞에 *
를 붙여서 사용한다.
def 함수이름(*매개변수):
코드
그러면 앞서 만든 숫자를 출력하는 함수를 가변 인수 함수로 만들어보자.
def print_number(*args):
for arg in args:
print(arg)
위에 *args
와 같이 매개변수 앞에 *
를 붙여주면 된다. 그리고 for
로 args
를 반복해서 출력해주면 된다.
매개변수 이름은 마음대로 지어도 되지만 관례적으로 arguments 를 줄여서 args 로 사용한다. 특히 이 args 는 튜플이여서 for
로 반복할 수 있다.
>>> print_numbers(10)
10
>>> print_numbers(10, 20, 30, 40)
10
20
30
40
그러면 print_numbers
함수에 숫자를 한 개 넣으면 한 개가 출력되고 네 개 넣으면 네 개가 출력된다. 즉, 넣은 숫자 만큼 출력이 되는 것이다. 위와 같이 함수에 인수를 여러개 넣어도 되고
>>> x = [10]
>>> print_numbers(*x)
10
>>> y = [10, 20, 30, 40]
>>> print_numbers(*y)
10
20
30
40
위와 같이 숫자가 들어있는 리스트(튜플)을 넣고 언패킹을 해줘도 된다.
>>> def print_numbers(a, *args):
... print(a)
... print(args)
...
>>> print_numbers(1)
1
()
>>> print_numbers(1, 10, 20)
1
(10, 20)
>>> print_numbers(*[10, 20, 30])
10
(20, 30)
고정 인수와 가변 인수를 동시에 사용할 때는 위와 같이 고정 인수를 먼저 지정하고 그 다음에 가변 인수를 지정해주면 된다.
단, 이때 def print_numbers(*args, a):
처럼 고정 매개변수 보다 *args
가 먼저 오면 안된다. 매개변수 순서에서 *args
는 항상 맨 마지막이여야 한다.
지금까지 함수에 인수를 넣을 때 값이나 변수 그대로 넣었다. 그러다 보니 각각의 인수가 어떠한 용도인지 알기 어려웠다. 보통 함수에 사용 방법을 익힐 때 인수의 순서와 용도를 함께 익힌다.다음 코드를 보자. 개인 정보를 출력하는 함수이다.
>>> def personal_info(name, age, address):
... print('이름: ', name)
... print('나이: ', age)
... print('주소: ', address)
...
이 함수는 첫 번째 인수는 이름, 두 번째 인수는 나이, 세 번째 인수는 주소이다. 만약 인수의 순서가 바뀌면 나이에 이름이 들어가고 주소에 이름이 들어가는 등 잘못된 결과가 나올 것이다.
>>> personal_info('홍길동', 30, '서울시 용산구 이촌동')
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
이와 같이 함수를 사용할때 인수의 순서와 용도 모두 기억해야 한다는 불편한점이 있다. 그래서 파이썬에서는 순서와 용도를 매번 기억하지 않도록 키워드 인수(keyword argument)라는 기능을 제공한다. 키워드 인수는 말 그대로 인수에 이름(키워드)를 붙이는 것이다.
personal_info
함수를 키워드 인수 방식으로 호출해보자.
>>> personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
>>> personal_info(age=30, address='서울시 용산구 이촌동', name='홍길동')
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
키워드 인수를 사용하니까 인수의 용도가 정확히 보이고 특히 인수의 순서를 지키지 않아도 키워드에 해당값이 잘 들어간다. 이전에 personal_info
함수에는 이름, 나이, 주소 순서대로 인수를 넣어야 했지만 키워드 인수를 사용하면 순서를 지키지 않아도 되는 것이다.
print(10, 20, 30, sep=':', end='')
참고로 우리가 이전에 print
에서 사용했던 sep
이나 end
도 키워드 인수이다.
지금까지 함수를 호출할 때 키워드 인수로 직접 값을 넣었다. 이번에는 딕셔너리를 사용하여 키워드 인수로 값을 넣는 딕셔너리 언패킹에 대해서 알아보자
다음과 같이 딕셔너리 앞에 **
(에스터리스크 두 개)를 붙여서 함수에 넣어준다. 먼저 personal_info
함수를 만들어보자.
>>> def personal_info(name, age, address):
... print('이름: ', name)
... print('나이: ', age)
... print('주소: ', address)
...
이제 '키워드' : 값
형식의 인수를 저장하고 앞에 **
를 붙여서 함수에 넣어준다. 이때 딕셔너리 키워드(키)는 반드시 문자열 형태여야 한다.
>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**x)
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
딕셔너리에 저장된 값들이 잘 출력되었다. **x
처럼 딕셔너리를 언패킹을 하면 딕셔너리의 값들이 함수에 잘 들어가서 personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
또는 personal_info('홍길동', 30, '서울시 용산구 이촌동')
과 똑같은 동작이 된다.
>>> personal_info(**{'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'})
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
또한 딕셔너리 변수 대신 딕셔너리 앞에 바로 **
를 붙여도 된다.
딕셔너리 언패킹을 사용할 때는 함수의 매개변수 이름과 딕셔너리의 키 이름이 같아야 한다. 또한 딕셔너리 키의 개수와 매개변수 개수가 같아야 한다. 그렇지 않으면 오류가 발생한다.
>>> personal_info(**{'name': '홍길동', 'old': 30, 'address':'서울시 용산구 이촌동'})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: personal_info() got an unexpected keyword argument 'old'
>>> personal_info(**{'name': '홍길동', 'age': 30})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: personal_info() missing 1 required positional argument: 'address'
그렇다면 왜 리스트 언패킹 처럼 *
를 한 번 사용하지 않고 두 번 사용할까? 그 이유는 딕셔너리는 키-값 형태로 값이 저장되어 있기 때문이다. 먼저 *
를 한 번만 사용해서 함수를 호출해보자.
>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(*x)
이름: name
나이: age
주소: address
위와 같이 에스터리스크를 한 번만 사용하면 키가 출력된다. 즉, 딕셔너리를 한 번 언패킹 하면 키를 사용한다는 의미이다. 따라서 **
처럼 언패킹을 두 번 사용하여 키의 값을 사용하도록 만들어야 한다.
>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**x)
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
이번에는 키워드 인수를 사용하면 가변 인수 함수를 만들어보자
def 함수이름(**매개변수):
코드
위와 같이 키워드 인수를 사용하는 가변 인수 함수는 매개변수 앞에 **
를 붙여서 만든다
>>> def personal_info(**kwargs):
... for kw, arg in kwargs.items():
... print(kw, ': ', arg, sep='')
...
매개변수 이름은 마음대로 지어도 되지만 관례적으로 keyword arguments 를 줄여서 kwargs 를 사용한다. 또한 kwargs 는 딕셔너리여서 for 반복문을 활용할 수 있다.
>>> personal_info(name='홍길동')
name: 홍길동
>>> personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
name: 홍길동
age: 30
address: 서울시 용산구 이촌동
>>> x = {'name': '홍길동'}
>>> personal_info(**x)
name: 홍길동
>>> y = {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**y)
name: 홍길동
age: 30
address: 서울시 용산구 이촌동
딕셔너리 x
는 {'name': '홍길동'} 이므로 personal_info(**x)
로 호출하면 personal_info(name='홍길동')
과 같고 딕셔너리 y
는 {'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'} 이므로 personal_info(**x)
로 호출하면 personal_info(name='홍길동', age=30, address='서울시 용산구 이촌동')
과 같다.
이처럼 함수를 만들 때 def personal_info(**kwargs):
와 같이 매개변수에 **
를 붙여주면 키워드 인수를 사용하는 가변 인수 함수를 만들 수 있다. 그리고 이러한 함수를 호출할 때 키워드와 인수를 각각 넣거나 딕셔너리 언패킹을 사용하면 된다.
보통 **kwargs
를 사용하는 가변 인수 함수는 다음과 같이 특정 키가 있는지 확인한 뒤 해당 기능을 만든다.
def personal_info(**kwargs):
if 'name' in kwargs: # in으로 딕셔너리 안에 특정 키가 있는지 확인
print('이름: ', kwargs['name'])
if 'age' in kwargs:
print('나이: ', kwargs['age'])
if 'address' in kwargs:
print('주소: ', kwargs['address'])
고정 인수와 가변 인수를 같이 사용할 때는 다음과 같이 고정 인수를 먼저 지정하고 그 다음에 가변 인수를 마지막에 지정해준다.
>>> def personal_info(name, **kwargs):
... print(name)
... print(kwargs)
...
>>> personal_info('홍길동')
홍길동
{}
>>> personal_info('홍길동', age=30, address='서울시 용산구 이촌동')
홍길동
{'age': 30, 'address': '서울시 용산구 이촌동'}
>>> personal_info(**{'name': '홍길동', 'age': 30, 'address': '서울시 용산구 이촌동'})
홍길동
{'age': 30, 'address': '서울시 용산구 이촌동'}
이때 `def personal_info(kwargs, name):와 같이 고정 인수 앞에 가변 인수가 오면 안된다. 매개변수 순서에서
kwargs` 는 반드시 맨 마지막에 와야한다.
함수에서는 위치 인수를 받는 *args
와 키워드 인수를 받는 **kwargs
를 함께 사용할 수 있다. 대표적인 함수가 print
인데 print
는 출력할 값을 위치 인수로 넣고 sep
이나 end
등을 키워드 인수로 넣는다.
>>> def custom_print(*args, **kwargs):
... print(*args, **kwargs)
...
>>> custom_print(1, 2, 3, sep=':', end='')
1:2:3
이때 def custom_print(**kwargs, *args):
처럼 **kwargs
가 *args
보다 앞쪽에 오면 안된다. 매개변수 순서에서 **kwargs
는 반드시 가장 뒤쪽에 와야 한다.
특히 고정 매개변수와 *args, **kwargs
를 함께 사용한다면 def custom_print(a, b, *args, **kwargs):
처럼 매개변수는 고정 매개변수, *args
, **kwargs
순으로 지정해야 한다.
지금까지 함수를 호출할 때 항상 인수를 넣어서 전달하였다. 그렇다면 인수를 생략할 수는 없을까? 이때는 함수의 매개변수에 초기값을 지정해주면 된다.
>>> def personal_info(name, age, address='비공개'):
... print('이름: ', name)
... print('나이: ', age)
... print('주소: ', address)
...
위와 같이 address='비공개'
라고 하여 address
의 초기값을 '비공개'로 지정하면
>>> personal_info('홍길동', 30)
이름: 홍길동
나이: 30
주소: 비공개
위와 같이 address
인수를 생략하면 초기값인 '비공개'가 들어가게 된다.
>>> personal_info('홍길동', 30, '서울시 용산구 이촌동')
이름: 홍길동
나이: 30
주소: 서울시 용산구 이촌동
매개변수의 초기값이 지정되어 있다고 하더라도 값을 넣으면 해당 값이 전달된다.
매개변수의 초기값을 지정할 때 한 가지 주의할 점이 있다. 초기값이 지정된 매개변수 다음에는 초기값이 지정되어 있지 않은 매개변수가 오면 안된다.
>>> def personal_info(name, address='비공개', age):
... print('이름: ', name)
... print('나이: ', age)
... print('주소: ', address)
...
File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
왜냐하면 이렇게 만들어버리면 personal_info('홍길동', 30)
이라고 하면 30이 어디로 들어가는지 알 수 없기 때문이다. address
로 들어가자니 age
가 비어버리게 된다. 이는 잘못된 문법으로 이렇게 만들어버리면 안된다.
즉, 다음과 같이 초기값이 지정된 매개변수는 뒤쪽으로 몰아주면 된다.
def personal_info(name, age, address='비공개'):
def personal_info(name, age=0, address='비공개'):
def personal_info(name='비공개', age=0, address='비공개'):
참고로 def personal_info(name='비공개', age=0, address='비공개'):
와 같이 초기값이 전부 지정되어 있으면 인수를 안넘기고 personal_info()
이런식으로 사용해도 함수를 호출할 수 있다.
지금까지 위치 인수, 키워드 인수, 매개변수의 초기값 사용 방법에 대해서 알아보았다. 함수에서 *
를 붙이거나 **
를 붙이는 문법이 조금 생소할 수 있으나 리스트에 *
를 쓰고 딕셔너리에 **
를 쓴다는 것만 알아두자