- 사전 필요 개념
- First-Class Functions
- HOF (Higher-Order Functions)
- Closure
함수를
객체
처럼 다루는 것
= 임의의 변수에 함수명을 할당(저장, assign)한 후,객체
처럼 사용.
새로운 컨셉을 배우고 있는 것이며 특히 어렵게 느껴질 수 있다.
왜냐하면 기존방식에 매우 익숙해져 있기 때문이다.
기존 방식
: 함수의 리턴값을 변수에 할당하는 것
def add(a, b):
return a+b
# 함수의 리턴값 '7'을 변수 result에 할당(assign)
result = add(2, 5)
print(result)
(자바스크립트에도 존재하는 syntax이며, '컨셉'을 이해하는게 중요하다.)
아래 예시들을 통해 새로운 개념이 어떤 이점을 가져다 주는지 확인해보자.
def func(a, b):
return a+b
# 함수명을 바꿔서 사용이 가능하다.
add = func
print(add(40, 60))
이 예제만 보면 함수명을 바꿀 수 있다는 것은 큰 장점이 없어 보인다.
애초에 함수명을 잘 지으면 되기 때문이다.
그래도 우선,
기존의 함수명 func를 add에 assign했고, (메모리 주소 복사)
func를 객체처럼 사용할 수 있구나
정도만 이해하고 넘어가자.
computer science에는
HOF
라는 개념이 있다.
H
igher-O
rderF
unctions는 아래의 조건 중 하나 이상을 만족해야 한다.
- 함수가 다른 함수를 인자(argument)로 받거나,
- 함수명을 리턴
(ex) return add위의 두 조건이 왜 HOF(고차함수)라는 단어로 명명하게 된건지는
조건을 자세히 보면 알 수 있다.
함수를 인자로 받거나 리턴한다는 것은 결국 함수 안에 또 다른 함수가 존재한다는 것이다.
즉, 자연스레 함수의 '계층구조'가 생긴다.
def add(a, b): # <---- 첫 번째 예제를 통해 배운 개념
return a+b
func = add # '함수명'을 변수에 할당
def map_func(func): # <---- HOF 개념 : (func)
result = []
for i in range(5):
result.append(func(i, i+1))
return result
# 함수 호출
total = map_func(add)
print(total)
추가로, 짚고 넘어가야할 내용이 있다.
(추가1) 함수 옆에 괄호가 있으면 그 함수를 실행시킨다는 의미다.
(추가2) 중첩함수 : 함수 안에 또 다른 함수가 정의되어 있는 함수가 있다. 그 안에 정의된 함수를 '중첩함수'(=내장함수)라고 한다.
아래 예제를 직접 따라해보면 이해에 큰 도움이 된다.
def html_tag(tag): # <---- 부모함수
def content_area(content): # <---- 중첩함수(자식함수) 시작 부분
print(f'<{tag}>{content}</{tag}>')
# content_area 함수를 리턴하고 있다.(HOF 개념 사용!)
return content_area
# 인자가 'h1'인 함수를 print_h1에 저장(할당)
print_h1 = html_tag('h1')
# 아래 코드의 이해가 중요
print_h1('This is a h1 tag.')
print_p = html_tag('p')
print_p('This is a p tag')
위의 예제는 HOF의 개념과 First-Order Function 개념이 섞여 있다.
무엇보다도 아래의 코드가 모든 것을 말해주고 있다.
print_h1 = html_tag('h1')
print_h1('This is a h1 tag.')
우선, print_h1 = html_tag('h1')을 분석해보자.
문자열 'h1'을 html_tag라는 함수에 인자를 념겨 준 것이다.
그런데 html_tag 함수의 정의 부분을 보면
우리는 아직 변수 content의 값을 받지 못하여 알 수 없는 상태다.
바로 그 다음줄 코드를 바라보자.
정말 핵심 중 핵심인 부분이다.
print_h1('This is a h1 tag')
print_h1 옆에 괄호를 열고 문자 입력이 가능한 것은 바로!!!
내장함수 content_area가 함수명
을 return 했기 때문이다.
(우리는 함수명
자체로는 실행이 안된다고 앞에서 확인했다.)
따라서, 아래 코드까지의 상태는
print_h1 = html_tag('h1')
tag = 'h1'
이 content_area라는 함수에 전달됐다.수학적으로 표현재보자면
print_h1 == html_tag('h1') == content_area
따라서, 그 이후에
print_h1('This is a h1 tag.')
는 결국,
print_h1('This is a h1 tag) == content_area('This is a h1 tag)
아무 생각없이 코드를 바라보면 어렵게 느껴지고, 이질적인 방법이다.
그러나 이 모든 복잡한 것을 가능케하고 이해시키는 열쇠는 바로
return
함수명
잠시 잊고 있었던
변수 tag
에 대해 생각해보자.
사실 지금 시점에서 '변수 tag가 있었나?'와 같은 반응은 당연하다고 생각한다.
print_h1('안녕하세요'), print_h1('tHis Is A tAg') 등과 같이
변수 'content'를 이리저리 바꾸는 얘기만 했다.
즉, 자식함수(중첩함수)의 변수를 이것저것 바꿔본 연습을 했던 것이다.
그런데, 이번엔 변수 tag
의 값을 수정하려고 하니 뭔가 막힌 느낌이 든다.
(접근이 어려워짐)
왜냐하면
tag='h1'이라는 값이 이미 content_area 함수에 전달됐기 때문이다.
언제 그랬냐면
# 인자가 'h1'인 함수를 print_h1에 저장(할당)
print_h1 = html_tag('h1')
따라서,
이처럼 '다른 사용자가 특정 변수에 접근하는 걸 어렵게 하고 싶을때' 사용할 수 있다.
이를 Closure
라고 한다.( = 폐쇄)
사실 위에서 다룬 개념들을 모두 이해했다면 Closure 개념이 더 이상 낯설지 않을 것이다.
오히려 이해하기가 수월해졌을 것이다.
위의 예제에서 다룬 코드를 다시 보자.
print_h1 = html_tag('h1')
print_h1('This is a h1 tag.')
설명1) print_h1 함수는 'h1' 이라는 인자를 갖고 있다.
설명 2) 그리고 다음줄에서 다시 'This is a h1 tag'라는 인자를 갖고 있다.
즉, 'h1'이라는 문자열 값이 그 아래 문자열로 대체되어 삭제된 것이 아니라
기억
하고 있다.
- 중첩함수가 있다.
- 그 중첩함수는 부모함수에서 가지고 있는 변수를 참조한다. ex) 'h1'
- 부모함수는 중첩함수명을 return 한다.
데이터들을 기반으로 연산을 수행하고 싶으나
기반이 되는 데이터에 다른 사람이 접근/수정 하는 걸 방지할 때 사용한다.
(참고) 물론 인수들이 많은 상황이거나 메소드 들이 많은 경우엔
클래스
를 사용해서 데이터를 은닉
시키는 것이 더 효율적일 수 있다.
#plus 함수 구현
def plus(*args):
sum = 0
for element in args:
sum += element
return sum
# minus 함수 구현
def minus(*args):
sum = 0
for element in args:
sum -= element
return sum
def set_function(func):
def operation(*args):
func_name = func.__name__
result = func(*args)
print(f'{func_name}함수의 연산 결과 : {result}')
return operation
plus_func = set_function(plus)
plus_func(2, 3 ,4, 5, 8)
minus_func = set_function(minus)
minus_func(9, 8, 2)
결과
plus함수의 연산 결과 : 22
minus함수의 연산 결과 : -19