본문 바로가기

Language/Python

RePythonOOP 14일차 Python First-class Functions, First-class citizen Higer Order Function, reduce(), callable(), class as callable()

 

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__을 사용한다  

위의 실행순서에 대해 궁금하신분은 아래의 링크를 클릭해서 한 단계 씩 실행해 보세요

http://bitly.kr/He0f9aI

 

Visualize Python, Java, JavaScript, C, C++, Ruby code execution

Write code in Python 3.6 Python 2.7 Python 3.6 with Anaconda (experimental) Java 8 C (gcc 4.8, C11) C++ (gcc 4.8, C++11) JavaScript ES6 TypeScript 1.4 Ruby 2.2 Someone is typing ... Visualize Execution Live Programming Mode hide exited frames [default] sho

www.pythontutor.com