[Python-basic] 함수(Function)
함수 (Function)
함수 정의와 호출
코드가 길어질수록 이를 체계적으로 정리할 필요가 있다. 그럴려면 큰 부분을 관리할 수 있을 정도로 작은 부분으로 나눌 필요가 있다. 또한 코드 내에 똑같은 기능을 수행하는 코드를 또 일일이 쓰는 것은 비효율적이다. 이러한 것들을 해결해줄 수 있는 것이 함수(function)이다. 함수는 다른 코드와 구별되는 함수 이름이 붙은 코드 구문이라고 볼 수 있다. 매개변수(parameter)를 받을 수 있고, 출력도 할 수 있는 입출력 가능한 구문이다.
함수는 두 가지 기능을 할 수 있다. 정의하고, 그 함수를 호출하는 것이다.
def 함수이름(매개변수):
실행할 코드
return 반환할 값
함수 이름은 변수 명명법과 동일하다.
() 괄호 안에는 매개변수가 하나 또는 여러 개 들어올 수 있고, 또는 생략하여 빈 괄호만 남길 수 있다. 매개 변수로는 어떤 자료형의 객체도 들어올 수 있다.
실행할 코드는 들여쓰기를 해야 한다. 또한 return 키워드를 통해 어떤 값을 반환시킬 수 있다. return 키워드는 생략 가능하며, 이 경우 해당 함수는 아무것도 반환하지 않는다.
함수이름(인자)
일단 함수를 한 번 정의하면, 이를 위 방법과 같이 호출할 수 있다. 이 때 함수를 정의한 대로 매개변수를 대입하거나, 매개변수를 넣지 않은 경우 빈 괄호로 남겨두면 된다.
def some_func():
pass
some_func()
print(some_func())
None
예제1의 2번째 줄의 pass는 아무런 기능을 수행하지 않도록 할 때 쓰인다.
함수 사용 예시
def print_something():
print("안녕")
print_something()
안녕
def return_true():
return True
if return_true():
print("참입니다")
else:
print("어... 이건 예상 못했는데...")
참입니다
def insert_stars(something_to_say):
return '*** ' + something_to_say + ' ***'
say_some = '이제 곧 크리스마스다!'
print(insert_stars(say_some))
*** 이제 곧 크리스마스다! ***
예제4에서, 함수에 매개변수(parameter) 하나를 넣어 정의하였다. 그리고 호출 시 say_some이라는 변수를 함수 괄호 안에 넣어 호출하여 그 리턴값을 받아 실행결과와 같이 출력하였다. 이 때, 함수 호출 시 함수 괄호 안에 넣는 값을 인수(argument, 인자라고도 한다)라고 한다. 매개변수와 인수의 차이는, 매개변수는 함수를 정의할 때 쓰이며, 함수 호출 시 넘겨받는 인수의 값을 저장하여 받는 변수라 보면 되고, 인수는 실제로 함수 호출 시 함수 괄호 안에 넘기는 변수라 보면 된다. 그래서 위 예제에서 something_to_say와 say_some은 서로 다른 객체이다. 즉 매개변수는 외부로부터 오는 값을 받는 ‘그릇’, 인수는 실제로 그 ‘그릇’에 담는 실제 ‘값’이라 보면 되겠다.
한 편 예제1에서, 함수가 그 어떤 값도 반환하지 않으면 함수 호출 시 None이라는 값을 받는다. 참고로 None은 False와 다르다. False는 0, ‘’, [] 등 빈 값을 표현하며, 어찌 보면 빈 값도 값이라 할 수 있다. 그러나 None는 값 자체가 없다는 것을 표현한다. 예제1에서 만약 함수 내에 pass대신 return 0와 같은 코드를 삽입했다면 반환값은 0이었을 것이다. 0도 값이다. 그러나 실제 예제1에선 아무런 값도 반환하지 않기에 ‘값 자체가 없음’을 표현하기 위해 None이 반환된 것이다.
print(None is False)
False
위치 인자 (positional argument)
위치 인자는 키워드가 아닌 인자로, 인자 목록의 처음에 나오거나 iterable의 앞에 *을 붙여 전달할 수 있다[2]. 위치 인자는 함수의 매개변수로 인자를 전달할 때 순서에 맞게 전달해야 원하는 값을 얻을 수 있다.
def info(fruit, unit, cost):
print("이 {}는 {}개당 {}원입니다.".format(fruit, unit, cost))
my_fruit = '사과'
my_unit = 2
my_cost = 4000
info(my_fruit, my_unit, my_cost)
이 사과는 2개당 4000원입니다.
위치 인자의 단점은 함수의 각 매개변수의 역할과 그 위치를 알아둬야 한다. 예제6-1에서 만약 각 매개변수의 역할 및 순서를 까먹었다면 이런 대참사(?)가 날 수도 있다.
def info(fruit, unit, cost):
print("이 {}는 {}개당 {}원입니다.".format(fruit, unit, cost))
my_fruit = '사과'
my_unit = 2
my_cost = 4000
info(my_fruit, my_cost, my_unit) # 과수원 사장님이 미쳤어요!!!
이 사과는 4000개당 2원입니다.
키워드 인자 (keyword argument)
키워드 인자는 함수 호출 때 식별자가 앞에 붙은 인자 (예를 들어, name=
) 또는 **
를 앞에 붙인 딕셔너리로 전달되는 인자를 말한다[2].
키워드 인자를 이용하면 앞선 위치 인자의 혼란스러움을 피할 수 있다. 함수에 인자를 넘길 때 해당 매개변수의 이름을 직접 명시함으로써 키워드 인자를 사용할 수 있다. 그러면 위치 인자처럼 굳이 순서대로 인자를 넘기지 않고 무작위의 순서로 인자를 넘겨도 잘 작동한다.
def info(fruit, unit, cost):
print("이 {}는 {}개당 {}원입니다.".format(fruit, unit, cost))
my_fruit = '사과'
my_unit = 2
my_cost = 4000
info(my_unit, my_fruit, my_cost) # 위치 인자
info(unit=my_unit, fruit=my_fruit, cost=my_cost) # 키워드 인자
이 2는 사과개당 4000원입니다.
이 사과는 2개당 4000원입니다.
한 편, 위치 인자와 키워드 인자를 섞어서 동시에 사용할 수 있다. 이 때 주의할 점은 위치 인자가 맨 처음에 쓰여야 한다는 것이다.
def info(fruit, unit, cost):
print("이 {}는 {}개당 {}원입니다.".format(fruit, unit, cost))
my_fruit = '사과'
my_unit = 2
my_cost = 4000
info(my_fruit, unit=my_unit, cost=my_cost)
이 사과는 2개당 4000원입니다.
def info(fruit, unit, cost):
print("이 {}는 {}개당 {}원입니다.".format(fruit, unit, cost))
my_fruit = '사과'
my_unit = 2
my_cost = 4000
info(fruit=my_fruit, my_unit, cost=my_cost)
File "c:\python2\python_basic\study.py", line 7
info(fruit=my_fruit, my_unit, cost=my_cost)
^
SyntaxError: positional argument follows keyword argument
별표(*) 문자를 이용하여 위치 인자 모으기
매개변수 앞에 * 문자를 사용하면, 여러 위치 인자들을 하나의 tuple로 모아 매개변수에 전달한다.
def some_func(*args):
print('위치 인자 tuple:', args)
some_func(1, 2, 3, '마이크 테스트')
some_func()
위치 인자 tuple: (1, 2, 3, '마이크 테스트')
위치 인자 tuple: ()
어떤 위치 인자도 대입하지 않았을 경우 위 예제에서처럼 *args에 아무것도 받지 않게 된다.
만약 함수에 위치 인자와 함께 써야 할 경우, *args는 마지막에 쓴다. 그러면 *args 앞에 오는 인자들을 제외한 모든 나머지 인자들은 *args에 대입된다.
def calculator(operator, something_to_say, *args):
total = 0
if operator == '+':
for value in args:
total += value
elif operator == '-':
for value in args:
total -= value
elif operator == '*':
for value in args:
total *= value
elif operator == '/':
for value in args:
total /= value
else:
print("잘못된 연산자입니다. 가능한 연산자는 덧셈(+), 뺄셈(-), 곱셈(*), 나눔셈(/)입니다.")
return
print(something_to_say)
print('=== 계산결과 ===')
print(total)
calculator('+', '그냥 덧셈', 1, 2, 3, 4, 5)
그냥 덧셈
=== 계산결과 ===
15
*를 쓸 때 변수명은 꼭 args일 필요는 없지만, 관례적으로 쓰인다.
** 문자로 키워드 인자 모으기
매개변수 앞에 ** (별표 두 개)를 붙이면 키워드 인자들을 dictionary로 모아준다. 이 때 매개변수 이름이 key, 그 매개변수에 할당된 인자들의 값이 value가 된다.
def print_kwargs(**kwargs):
print('keyword arguments:', kwargs)
print_kwargs(fruit='사과', unit=2, money=4000)
keyword arguments: {'fruit': '사과', 'unit': 2, 'money': 4000}
*args와 **kwargs를 같이 써야 하는 경우 *args, **kwargs 순서대로 써야 한다.
def mix_of_args_and_kwargs(*args, **kwargs):
print('positional args:', args)
print('keyword args: ', kwargs)
mix_of_args_and_kwargs(1, 2, 'a', time='night', hour=7)
positional args: (1, 2, 'a')
keyword args: {'time': 'night', 'hour': 7}
한 편 kwargs란 단어도 관행적으로 쓰인다.
정리) 함수 내에 인자 쓰는 순서
- 위치 인자, 키워드 인자
- 위치 인자, *args
- *args, **kwargs
Docstrings (문서화)
함수를 정의할 때 함수의 내용 부분의 맨 처음에 문자열 형태의 글을 써서 그 함수가 어떤 기능을 하는지 설명해줄 수 있다. 이를 함수의 docstring이라 한다. 이를 통해 함수가 어떤 기능을 하는지 처음 보는 사람한테 설명해줄 수 있고, 가독성을 높일 수 있는 장점이 있다.
def just_return(thing):
'대입받은 인수를 그대로 반환합니다.'
return thing
def long_docstring(something_to_say, repeat_num):
'''
something_to_say에 담긴 문자열을 repeat_num의 횟수만큼 반복하여 출력.
something_to_say는 문자열 자료형,
repeat_num은 숫자 자료형입니다.
'''
for i in range(repeat_num):
print(something_to_say)
help(just_return)
help(long_docstring)
Help on function just_return in module __main__:
just_return(thing)
대입받은 인수를 그대로 반환합니다.
Help on function long_docstring in module __main__:
long_docstring(something_to_say, repeat_num)
something_to_say에 담긴 문자열을 repeat_num의 횟수만큼 반복하여 출력.
something_to_say는 문자열 자료형,
repeat_num은 숫자 자료형입니다.
예제13-1에서 두 함수가 정의되었다. 함수 정의의 맨 처음 부분에 ‘’이나 ‘’’ ‘’’ 또는 “”” “””를 이용하여 문자열로 docstring을 만들어 줄 수 있다. docstring만 따로 출력하는 방법은 예제13-1의 맨 마지막 두 줄에서처럼 파이썬 내장 함수인 help()를 이용한다. 사용법은 help(함수이름)으로 함수 이름을 help()함수의 인자로 대입하면 된다. 이 때 함수이름만 넣고 그 뒤에 괄호 ()를 쓰진 않는다. help()를 이용하면 위 예제 실행결과처럼 잘 정돈된 포맷으로 docstring을 출력하여 보여준다.
어떤 정해진 포맷없이 그대로 docstring을 보고 싶다면 함수이름.__doc__을 이용한다. (이 때도 함수 이름 뒤에 () 괄호를 붙이지 않는다)
def just_return(thing):
'대입받은 인수를 그대로 반환합니다.'
return thing
def long_docstring(something_to_say, repeat_num):
'''
something_to_say에 담긴 문자열을 repeat_num의 횟수만큼 반복하여 출력.
something_to_say는 문자열 자료형,
repeat_num은 숫자 자료형입니다.
'''
for i in range(repeat_num):
print(something_to_say)
print(just_return.__doc__)
print(long_docstring.__doc__)
대입받은 인수를 그대로 반환합니다.
something_to_say에 담긴 문자열을 repeat_num의 횟수만큼 반복하여 출력.
something_to_say는 문자열 자료형,
repeat_num은 숫자 자료형입니다.
함수는 일급 객체이다.
일급객체 (First class object 또는 first class citizen)는 어떤 언어에서 일반적으로 모든 개체에서 다음의 동작들이 지원되는 개체를 말한다[3].
여기서 말하는 동작이란 다음의 세가지이다.
- 함수의 인자로 전달될 수 있다.
- 함수의 반환값이 될 수 있다.
- 수정되고 할당될 수 있다.
함수는 일급 객체에 해당한다. list, int과 같은 자료형들도 위 세 가지 동작이 가능하므로 해당 자료형들도 일급 객체에 해당한다.
함수도 객체이다. immutable하다. 함수도 다른 함수의 인자로 대입할 수 있고, list의 요소로 넣을 수도 있다. 변수에 대입할 수도 있다.
함수는 그 자체로 다른 함수의 인자로 들어갈 수 있다.
def print_something():
print('hello')
def run_function(func):
func()
run_function(print_something)
print(type(print_something))
print(type(run_function))
hello
<class 'function'>
<class 'function'>
예제14에서, print_something이란 함수를 run_function의 인자로 대입하여 run_function을 호출한 결과이다. 함수 속에 함수를 대입하여 실행해도 잘 실행됨을 알 수 있다. 참고로 함수를 다른 함수의 인자로 넘길 때에는 소괄호 ()를 쓰지 않고 함수 이름만 쓴다. 함수 이름 뒤에 쓰는 () 소괄호는 그 함수를 호출한다는 뜻이기 때문이다. 소괄호를 쓰지 않고 함수 이름만 쓰면 그것은 함수를 다른 객체처럼 다루겠다는 뜻이다.
한 편 예제14의 맨 마지막 두 줄은 두 함수의 자료형을 출력해보이고 있다.
함수 속에 함수를 넣어 쓸 때 속에 든 함수에 다시 인자를 대입할 수도 있다.
def add_numbers(num1, num2):
return num1 + num2
def run_func_with_numbers(func, num1, num2):
return func(num1, num2)
result = run_func_with_numbers(add_numbers, 2, 4)
print(result)
6
예제15에서, run_func_with_numbers라는 함수에 add_numbers라는 다른 함수를 인자로 대입하였다. run_func_with_numbers(이하 상위 함수라 하겠다) 함수 안에 들어간 add_numbers(이하 하위 함수라 하겠다)는 상위 함수로부터 받은 다른 인자 두 개 (num1, num2)를 인자로 받아 하위 함수의 코드를 실행한다. 따라서 위 결과를 얻을 수 있다.
물론 함수 속의 함수를 쓸 때 *args, **kwargs를 적절히 활용할 수 있다.
def sum_args(*args):
return sum(args)
def run_func(func, *args):
return func(*args)
print(run_func(sum_args, 1, 2, 3, 4))
10
inner function (내부 함수) (nested function, 중첩함수라고도 불림)
아예 함수 안에 다른 함수를 정의할 수 있다.
def find_common_divisors(a, b):
'''두 자연수 간의 공약수 찾기'''
def find_divisor_from_one_num(num):
'한 자연수의 약수들을 찾아 list로 반환'
divisor_list = []
for value in range(1, num+1):
quotient, remainder = divmod(num, value)
if remainder == 0:
divisor_list.append(quotient)
return divisor_list
a_divisor_set = set(find_divisor_from_one_num(a))
b_divisor_set = set(find_divisor_from_one_num(b))
return a_divisor_set.intersection(b_divisor_set)
common_divisors = find_common_divisors(4, 16)
print(common_divisors)
{1, 2, 4}
한 함수 안에 다른 함수를 정의하여 쓰는 것은 여러모로 유용할 수 있다. 예로, 예제17에서 두 자연수간의 공약수들을 찾기 위해 한 자연수를 약수들로 분해하여 list로 반환해줄 또 다른 함수를 정의하였다. 이 함수를 이용하여 두 자연수 각각의 약수들을 구한 후, 이 둘의 공약수를 찾아 이를 반환한다. 위 예제에서 함수 안에 한 자연수의 약수들을 모두 구해주는 또 다른 함수를 정의하지 않았더라면 어쩌면 그 역할을 하는 코드를 두 자연수에 적용하느라 두 번이나 똑같은 코드를 반복하여 작성했을지도 모른다. (그러면 비효율적이다)
Closure [3]
closure를 알려면 3가지 개념을 먼저 알아야 한다. 일급 객체, 함수 중첩, nonlocal이다. 일급 객체, 함수 중첩은 앞에서 다뤘으니 여기서는 nonlocal에 대해서 먼저 설명하겠다.
nonlocal
nonlocal은 일종의 코드 영역이라고도 볼 수 있다. 특히 nonlocal은 중첩 함수에서, 외부 함수와 내부 함수의 사이에 있는 코드 영역을 지칭한다.
a = 3
def some_func(x):
y = 3
def some_inner():
print(x * y)
some_inner()
some_func('hello')
예제18에서 nonlocal 영역에 있는 변수는 외부 함수인 some_func과 내부 함수인 some_inner 사이에 있는 x와 y이다. (외부 함수의 매개변수도 nonlocal이다)
한 편 inner 함수 안에 있는 영역은 local scope라 하고, 이 영역은 inner 함수가 통제할 수 있는 영역이다.
outer 함수 밖의 영역을 global scope라 한다. 위 예제에서 a 변수가 global 영역에 있는 것이다. 이 영역의 변수들은 다른 함수나 코드에서 참조 가능하다.
이렇게 3가지 영역으로 나뉠 수 있는데, 특징은 자신의 영역 밖에 있는 변수에 대해서 그 변수를 읽을 수만 있지 수정(쓰기) 및 새로 할당은 할 수 없다. 이 원리에 의하면, inner 함수는 nonlocal 변수를 읽을 수는 있으나 수정할 수는 없다.
def some_func():
x = 1
def some_inner():
x += 1
return x
return some_inner()
print(some_func())
예외가 발생했습니다. UnboundLocalError
local variable 'x' referenced before assignment
만약 nonlocal 변수를 수정하고 싶다면, 변수 이름 앞에 nonlocal이란 키워드를 쓰면 된다.
def some_func():
x = 1
def some_inner():
nonlocal x
x += 1
return x
return some_inner()
print(some_func())
2
마찬가지로, global 영역의 변수를 함수 내에서 수정하여 쓰고 싶다면 해당 변수 앞에 global이라고 쓰면 된다.
closure에 대한 본격적인 설명
closure는 어떤 함수의 내부함수가 자신을 둘러싼 함수 내의 상태값, 즉 nonlocal 영역의 변수값을 기억하는 함수이다. 어떤 함수가 closure이려면 다음의 3가지 조건을 만족해야 한다.
- 해당 함수가 다른 함수 내에 정의된 inner 함수여야 한다.
- 해당 함수는 자신을 둘러싼 함수 내의 상태값, 즉 외부 함수 내의 nonlocal 영역에 있는 변수를 반드시 참조해야 한다.
- 해당 함수를 둘러싸고 있는 외부 함수는 이 함수를 반환해야 한다. (이 때 함수 반환 시 소괄호는 쓰지 않고 함수 이름만 쓴다)
def add_calculator(num1, num2):
return num1 + num2
def number_memory(func):
result_memory = {}
def some_memory(num1, num2):
print(result_memory)
string_key = '{} + {}'.format(num1, num2)
if string_key in result_memory:
return result_memory[string_key]
else:
result_memory[string_key] = func(num1, num2)
return result_memory[string_key]
return some_memory
calc_with_memory = number_memory(add_calculator)
print(calc_with_memory(1, 2))
print(calc_with_memory(3, 4))
print(calc_with_memory(4, 5))
print(calc_with_memory.__closure__[1].cell_contents)
{}
3
{'1 + 2': 3}
7
{'1 + 2': 3, '3 + 4': 7}
9
{'1 + 2': 3, '3 + 4': 7, '4 + 5': 9}
예제20-1에서, some_memory함수는 closure를 만족한다. number_memory라는 함수의 내부에서 정의되었고, nonlocal 변수인 func, result_memory를 참조하였으며, 외부 함수인 number_memory가 내부 함수를 반환하고 있기 때문이다.
calc_with_menory에 number_memory 함수의 반환값이 할당되었는데, 이 변수는 이제 some_memory를 참조하는 함수가 되었다. 따라서 마치 add_calculator 함수처럼 행동할 수 있게된다.
some_money 함수는 add_calculator 함수를 통해 두 수를 더하는 연산을 할 때, 그 두 수와 그의 합의 결과값을 result_memory라는 dictionary에 저장해주는 역할을 한다. 그러면 마치 ‘계산 내역’을 저장하는 것 마냥 저장해준다. 이러한 closure의 특징을 이용하면 똑같은 계산을 하지 않고 이미 한 번 계산한 것을 closure가 참조하는 nonlocal 변수에서 참조만 하면 반복 계산에 의한 시간 낭비를 줄일 수 있는 장점이 있을 것이다.
예제20의 맨 마지막 줄에서 closure가 참조하는 변수에 어떤 값이 들어있는지를 반환해주고 있다. closure가 참조하는 변수는 func와 result_memory이다. 맨 마지막 줄에선 result_memory 안에 저장된 값을 보고 싶기 때문에 index를 1로 넣었다.
closure를 감싸는 외부 함수를 메모리상에서 삭제해도 closure가 참조하는 값은 사라지지 않는다.
del number_memory
print(calc_with_memory(6, 8))
{'1 + 2': 3, '3 + 4': 7, '4 + 5': 9}
14
예제 20-2의 코드를 예제 20-1의 마지막에 추가하여 실행한 모습이다. 분명 closure를 감싸는 함수인 number_memory를 삭제시켰음에도 참조값이 유지되는 것을 확인할 수 있다.
lambda() 함수
lambda 함수는 이름이 없고, 단 한 줄로 정의할 수 있는 함수이다. def 키워드를 이용해 함수를 정의할 정도로 복잡하지 않거나, def 키워드를 사용할 수 없는 곳에 쓰일 수 있다[4].
변수 = lambda 매개변수1, 매개변수2, ... : 매개변수를 이용한 표현식
lambda 함수는 return 키워드 없이도 표현식의 결과값을 반환한다.
위 정의법을 이용하여 lambda 함수를 정의하면 변수에 그 함수 자체가 저장된다. 이 함수를 호출하고자 한다면 앞서 다뤘던 일반적인 함수처럼 “변수(매개변수)”로 소괄호를 이용하면 된다.
def add_two(a, b):
return a + b
print(add_two(3, 4))
result = lambda a, b: a + b
print(result(3, 4))
7
7
예제21에서는 두 수를 받아 그 합을 반환하는 함수를 보여주고 있다. 맨 처음 add_two 함수에서는 def 키워드를 이용하여 해당 함수를 정의하고 이를 호출하고 있다. result 줄부터는 같은 기능을 수행하는 함수를 lambda 함수로 정의하고 호출하고 있는 모습이다.
result = lambda… 줄에서 lambda 자체는 매개변수로 표현한 표현식의 결과값을 바로 반환하는 것이 아니라 함수 그 자체를 반환하여 result라는 함수에 대입한다. 그래서 result 자체가 함수의 이름이 된 것처럼 행동한다. 예제21의 마지막 줄처럼 lambda 함수 호출 시 이 함수가 담긴 result에 소괄호를 붙여 인자를 전달하면 호출할 수 있음을 알 수 있다.
Generator
generator는 시퀀스를 생성하는 객체이다. 이를 이용하면 전체 시퀀스를 한꺼번에 생성하고 메모리에 저장하지 않고도 잠재적으로 큰 시퀀스를 반복시킬 수 있다.
generator의 한 예로 range()가 있다.
generator와 함수의 차이점은, generator를 이용하여 반복자를 반복할 시, 어떤 값이 언제 마지막으로 호출되었는지를 계속 추적하고, 다음 값을 반환한다. 일반적인 함수는 이전 호출에 대한 메모리를 전혀 가지지 않으며, 호출될 때 마다 항상 같은 상태로 첫 줄부터 시작한다.
잠재적으로 큰 시퀀스를 생성하고, generator comprehension을 쓰기에 코드가 너무 길다면 generator function을 쓰면 된다. 이는 일반적인 함수이나, return 키워드 대신 yield 키워드를 사용한다.
def my_range(first=0, last=10, step=1):
number = first
while number < last:
yield number
number += step
print(my_range) # generator function은 일반 함수
var = my_range(1, 5)
print(var) # generator 객체를 반환
for i in var:
print(i)
<function my_range at 0x000002238E303370>
<generator object my_range at 0x000002238E28FD80>
1
2
3
4
Decorator
decorator는 이전에 존재하는 함수에 대해, 그 함수 내부의 코드를 건드리지 않고 무언가 변경하고자 할 때 쓰인다.
다음 예제는 기존의 함수에 대한 세부정보를 알고자 하기 위해 decorator를 사용하는 예제이다.
def show_info_about_func(func):
def add_info(*args, **kwargs):
print('실행 중인 함수: ', func.__name__) # 함수 이름 반환
print('위치 인자: ', args)
print('키워드 인자: ', kwargs)
result = func(*args, **kwargs) # 해당 함수의 반환값 확인용
print('반환값: ', result)
return result
return add_info
def add_calc(a, b):
return a + b
예제23-1의 show_info_about_func 함수가 decorator이다. 매개변수로 함수를 받아드리고, 이 함수에 코드를 추가하기 위해 내부 함수인 add_info를 정의하여 그 안에서 func를 이용하고 있다.
decorator를 사용하기 위해 add_calc라는 또 다른 함수를 정의하였다. decorator를 사용하는 방법은 2가지이다.
1
about_add_calc = show_info_about_func(add_calc)
result = about_add_calc(1, 2)
print(result)
실행 중인 함수: add_calc
위치 인자: (1, 2)
키워드 인자: {}
반환값: 3
3
위 예제처럼, decorator에 적용하고자 하는 함수를 인자로 받고 그 반환값을 다른 변수에 할당하는 방법이다. 실행결과에서 맨 마지막 줄의 ‘3’을 제외한 다른 출력값들은 모두 decorator에서 나온 것이다. 만약 decorator를 적용시키지 않고 add_calc 함수만 단독으로 사용했다면 위 실행결과의 맨 마지막 줄(3)만 출력되었을 것이다.
2.
@show_info_about_func
def add_calc(a, b):
return a + b
result = add_calc(1, 2)
print(result)
실행 중인 함수: add_calc
위치 인자: (1, 2)
키워드 인자: {}
반환값: 3
3
또 다른 사용법은, 해당 함수를 정의하는 코드 바로 위에 @를 쓰고 그 뒤에 decorator 이름을 쓰는 것이다. 그러면 이후 add_calc 함수는 decorator가 기본으로 적용된 상태가 된다. 그래서 위 예제의 실행결과가 보는 바와 같이 되는 이유이다.
decorator는 closure를 “어떤 함수 내부의 코드를 건드리지 않고 다른 코드를 그 함수에 추가하고자 하는 특정 목적”을 가지고 사용하는 것이라고 볼 수도 있겠다.
한 편 decorator도 중첩하여 쓸 수 있다.
def show_info_about_func(func):
def add_info(*args, **kwargs):
print('실행 중인 함수: ', func.__name__) # 함수 이름 반환
print('위치 인자: ', args)
print('키워드 인자: ', kwargs)
result = func(*args, **kwargs) # 해당 함수의 반환값 확인용
print('반환값: ', result)
return result
return add_info
def square(func):
def inner(*args):
added = func(*args)
return added ** 2
return inner
@show_info_about_func
@square
def add_calc(a, b):
return a + b
result = add_calc(1, 3)
print(result)
실행 중인 함수: inner
위치 인자: (1, 3)
키워드 인자: {}
반환값: 16
16
위 예제는 두 개의 decorator를 add_calc 함수에 적용한 모습이다. 이 때 적용되는 순서는 적용하고자 하는 대상 함수의 정의 부분으로부터 가장 가까운 순으로 적용된다. 위 예제에서, add_calc 함수 정의부분 바로 위에 @square 가 먼저 있기 때문에 square가 먼저 적용되고 그 후에 show_info_about_func이 적용된다.
참고로 다음 예제는 위 예제에서 decorator의 순서만 바뀐 모습이다.
def show_info_about_func(func):
def add_info(*args, **kwargs):
print('실행 중인 함수: ', func.__name__) # 함수 이름 반환
print('위치 인자: ', args)
print('키워드 인자: ', kwargs)
result = func(*args, **kwargs) # 해당 함수의 반환값 확인용
print('반환값: ', result)
return result
return add_info
def square(func):
def inner(*args):
added = func(*args)
return added ** 2
return inner
@square
@show_info_about_func
def add_calc(a, b):
return a + b
result = add_calc(1, 3)
print(result)
실행 중인 함수: add_calc
위치 인자: (1, 3)
키워드 인자: {}
반환값: 4
16
Reference
[1] Bill Lubannovic, “Introducing Python” (O’REILLY, 2015)
[2] https://docs.python.org/ko/3.6/glossary.html
[3] Python의 Closure에 대해 알아보자
https://shoark7.github.io/programming/python/closure-in-python#2b
[4] 점프 투 파이썬, lambda
https://wikidocs.net/24#lambda
This content is licensed under
CC BY-NC 4.0
댓글남기기