Insight
1. 일급 함수, 일급 시민, 고위 함수에 대해서 설명할 수 있다.
2. 함수가 객체로 취급 되어 객체 처럼 행동하는 것을 일급 함수라고 한다.
3. 함수는 클래스보다 기본적인 내장 메서드가 더 많다.
# {'__annotations__', '__defaults__', '__qualname__', '__call__', '__kwdefaults__', '__name__', '__globals__', '__get__', '__code__', '__closure__'}
4. 함수에 비해 클래스만 가지는 것은 약한참조(__weakref__)밖에 없다
5. 일급 시민의 의미들을 알 수 있엇다.
6. callable() 사용하는 프로그래머로서 나중에 함수를 인자로 사용시 에러가 나와도 걱정 하지 않게 되었다.
7. callable()은 정말 클래스와 함수를 넘나들게 해줄 수 있는 유연한 함수 기능 이다!
사전 지식
모든 자료형은 객체이다.
그래서 파이썬은 순수 객체지향 언어이다.
일급함수 : 프로그래밍 언어가 함수 (function) 를 first-class citizen으로 취급하는 것
일급 시민(first-class citizen)의 의미
- 런타임 초기화,
- 변수에 함수 할당 가능(데코레이터, 클로저),
- 함수 인수 전달 가능(ex) 제약조건 len=10 등),
- 함수 결과로 반환 가능
클래스와 함수간의 차이는 무엇인가? 라고 물으면 쉽게 답 할수 있을까?
구조적 차이는 쉽게 설명이 가능 하다.
함수는 크게 보면 행위 만을 가지며 이 행위를
클래스에서는 속성과 행위중 행위를 함수로 정의를 한다.
함수는 객체인가?
부터 예시를 통해 짚고 넘어가자
def Fac():
return None
class F:
def fac():
return None
함수든 클래스든 모두 객체이다.
print(type(Fac), type(F)) #<class 'function'> <class 'type'>
그렇다면 파이썬에서 함수와 클래스가 가지는 내부적 기능의 차이는 어떤 것 일까?
클래스에 없는 함수만의 고유한 특징은 아래이다.
print(set(sorted(dir(Fac))) - set(sorted(dir(F))))
# {'__annotations__', '__defaults__', '__qualname__', '__call__', '__kwdefaults__',
# '__name__', '__globals__', '__get__', '__code__', '__closure__'}
함수에는 없는 클래스 만의 특징은 아래이다.
print(set(sorted(dir(F))) - set(sorted(dir(Fac))))
# {'__weakref__'}
변수에 함수 할당 가능한지, 결과만 반환 되는지
def factorial(n):
'''
factorial function
n : int
'''
if n == 1:
return 1
return n * factorial(n-1)
f1_func = factorial(5) # return이 int여서 함수가 아닌 정수(int)반환
f2_func = factorial # 함수가 변수에 할당됨
print(type(f1_func), type(f2_func)) # <class 'int'> <class 'function'>
print(f1_func, f2_func) # 120 <function factorial at 0x7fa70784c7a0>
f1_func 는 결과만 반환한 int type이 되었고
f2_func는 변수(싱글톤)에 함수를 할당하였다.
위의 f2_func는 인자를 가지는 함수다
하지만 factorial함수와 다르게 인자가 없는 함수는 어떨가?
def fake_factorial():
a = 1
b = 2
return a
f3_func = fake_factorial()
f4_func = fake_factorial
# 예상 했던 결과값을 가진다. 단지위 f1_func는 파라미터 없이 factorial()을 할당하지 못한다.
print(type(f3_func), type(f4_func)) # <class 'int'> <class 'function'>
함수 인수 전달 가능한지?
f5_func = factorial
# 할당된 변수를 map함수를 사용해서 for문과 같이 돌릴 수 있다.
print(list(map(f5_func, range(1,6)))) # [1, 2, 6, 24, 120]
Higer Order Function
함수 인수를 전달하고 함수로 결과를 반환하는 함수를 고위(고차) 함수라고 한다 (Higer Order Function)
함수안의 함수를 사용해보자
# 홀수 차례인 1,3,5 인자인 것만 return하는 함수
print(list(map(f5_func, filter(lambda x : x % 2, range(1,6))))) # [1, 6, 120]
print([f5_func(i) for i in range(1, 6) if i % 2]) # [1, 6, 120]
디스어셈블링으로 뭐가 더 효율적인지도 한번 보자
# 위의 함수를 비교해보자
print(dis('list(map(f5_func, filter(lambda x : x % 2, range(1,6))))'))
# 1 0 LOAD_NAME 0 (list)
# 2 LOAD_NAME 1 (map)
# 4 LOAD_NAME 2 (f5_func)
# 6 LOAD_NAME 3 (filter)
# 8 LOAD_CONST 0 (<code object <lambda> at 0x7fdbeb078030, file "<dis>", line 1>)
# 10 LOAD_CONST 1 ('<lambda>')
# 12 MAKE_FUNCTION 0
# 14 LOAD_NAME 4 (range)
# 16 LOAD_CONST 2 (1)
# 18 LOAD_CONST 3 (6)
# 20 CALL_FUNCTION 2
# 22 CALL_FUNCTION 2
# 24 CALL_FUNCTION 2
# 26 CALL_FUNCTION 1
# 28 RETURN_VALUE
# Disassembly of <code object <lambda> at 0x7fdbeb078030, file "<dis>", line 1>:
# 1 0 LOAD_FAST 0 (x)
# 2 LOAD_CONST 1 (2)
# 4 BINARY_MODULO
# 6 RETURN_VALUE
# None
print(dis('[f5_func(i) for i in range(1, 6) if i % 2]'))
# 1 0 LOAD_CONST 0 (<code object <listcomp> at 0x7f21f01a0810, file "<dis>", line 1>)
# 2 LOAD_CONST 1 ('<listcomp>')
# 4 MAKE_FUNCTION 0
# 6 LOAD_NAME 0 (range)
# 8 LOAD_CONST 2 (1)
# 10 LOAD_CONST 3 (6)
# 12 CALL_FUNCTION 2
# 14 GET_ITER
# 16 CALL_FUNCTION 1
# 18 RETURN_VALUE
# Disassembly of <code object <listcomp> at 0x7f21f01a0810, file "<dis>", line 1>:
# 1 0 BUILD_LIST 0
# 2 LOAD_FAST 0 (.0)
# >> 4 FOR_ITER 20 (to 26)
# 6 STORE_FAST 1 (i)
# 8 LOAD_FAST 1 (i)
# 10 LOAD_CONST 0 (2)
# 12 BINARY_MODULO
# 14 POP_JUMP_IF_FALSE 4
# 16 LOAD_GLOBAL 0 (f5_func)
# 18 LOAD_FAST 1 (i)
# 20 CALL_FUNCTION 1
# 22 LIST_APPEND 2
# 24 JUMP_ABSOLUTE 4
# >> 26 RETURN_VALUE
# None
리스트 컴프리핸션이 훨신 더 빠르다.
조금더 구체적인 예를 들기 위해 중첩 함수인 reduce()와
하나식 더 하는 add를 사용해 고위 함수를 만들어 보겠다.
# reduce() 함수 개념
from functools import reduce
from operator import add # 오퍼레이터의 더하기 기능을 가져온다.
# reduce함수는 원소들을 중첩(누적)하여 하나로 만드는 과정 왼쪽으로부터 오른쪽까지의 single values를 합치는것입니다.
# 함수, iter한 자료형을 파라미터로 받습니다.
print(reduce(add, range(1,11))) # (((1+2)+3)+4)......10 = 55
add는 간단한 가산 함수이니
람다 함수로 직접 구현하여 다시 돌려보자
(람다로 구현 하는것이 성능이 더 좋기는 하다)
# 익명함수 (람다)
# 다른 개발자가 알수 있도록 주석을 작성해준다
# 가독성을 위해 일반 함수 형태로 리펙토링 권장
print(reduce(lambda i,j : i + j , range(1,11))) # 55
callable() __call__
추가적인 팁은 인자로서의 사용이 가능한 함수인지 알기 위해 (내장 매직메서드 __call__이 있는지)
callable(호출 연산자) 로 True , False 조회가 가능하다. -> 메소드 형태로 호출 가능한지 확인가능하다.
print(callable(str), callable(list), callable(add)) # True ,True, True
위에서 쓴 add도 callable 함으로 인자로 사용이 되었다.
class as callable()
그렇다면 위의 add를 lambda로 구현 햇는데
클래스를 callable하게 구현하여 lambda를 대처하는 클래스를 만들어 볼 수 있을까?
첫번째는 그냥 클래스의 메서드에 직접 접근하여 구해버리는 방법
from functools import reduce
class Add:
# 덧샘을 리턴하는 함수
def add(self,i,j):
return i+j
add__ = Add()
print(reduce(add__.add, range(1,11))) # 55
두번째는 callable(__call__)을 활용하여 사용하기
from functools import reduce
class Add2:
# 덧샘을 리턴하는 함수
def add(self,i,j):
return i+j
# callable
def __call__(self,i,j):
return self.add(i,j)
add__ = Add2()
print(reduce(add__, range(1,11))) # 55
__call__을 사용한다