[Python] Higher-order Function

Hyeseong·2020년 12월 4일
0

python

목록 보기
8/22
post-thumbnail

Higher-order Function

파이썬 함수 특징

함수 객체 속성 확인

Map, Filter, Reduce

익명함수

다양한 매개변수 사용


파이썬 함수 특징

1. 런타임 초기화

2. 변수 등에 할당 가능

3. 함수 인수 전달 가능(ex. sorted(keys=len)

4. 함수 결과로 반환 가능 return funcs


함수 객체 예제

def factorial(n):
	''' factorial function -> n: int'''
	if n == 1: 
		return 1
	return n * factorial(n-1)

class A:
	pass


print('ex1-1',factorial(5))
print('ex1-2', factorial.__doc__)
print('ex1-3', type(factorial), type(A))
print('ex1-4',dir(factorial))
print('*'*20)
print('ex1-5',set(dir(factorial))-set(dir(A)))
print('ex1-6',set(sorted(dir(factorial)))-set(sorted(dir(A))))
print()
print('ex1-7',factorial.__name__)
print('ex1-7',factorial.__code__)

facotrial을 함수로 구현해서 사용해보도록 할게요.

  • 첫 번째로, ex1-1을 보면 기본적으로 정수 인자 하나를 받고 그 값을 반환해요.

  • 두 번째로, ex1-2의 경우 __doc__ 메소드를 통해서 내부의 따옴표 3개로( ''' ''' or """ """)주석 처리한 내용을 볼 수 있어요.

  • 세 번째, ex1-3의 경우 파이썬의 특징인 모든것을 객체화 했는 모습을 알 수 있는데요. 눈여겨 볼 만한 것이 분명 factorial 메서드인데 class라고 출력되네요.

  • 네 번째, dir()메서드를 통해서 내부에 어떠한 것들이 들어 있는지 알 수 있어요. 기본적으로 내장함수들이 들어 있으며, 던더 메소드인 메잭 매소드를 확인 할 수 있어요. (참고, 38개가 있어요.)

  • 다섯 번째, 그렇다면 앞서 클래스 객체라는 것을 3번째에서 확인했는데요. 클래스가 가지고 있는 함수를 제외한 함수들이 있는걸 확인 하는 방법이 ex1-5입니다. 리본적으로 dir() 메소드를 사용하면 리스트인데 그대로 '- '기호를 사용하여 차집합을 이용할 수 없어 set()메서드로 형변환을 하여 이용한거에요.(참고, 10개의 요소가 있음)

  • 여섯 번째, ex1-5를 활용하여 a-z 오름차순으로 나타낼수도 있어요.
    sorted()를 이용하면 되요.

  • 일곱 번째, 메직메소드 __name__\을 이용하면 정의된 객체의 이름도 확인 할 수 있어요.

  • 여덜 번쨰, __code__\를 통해서 해당 객체가 메모리 어디에 저장되어 있으며 파일의 pwd까지 확인 할 수 있어요.

output

ex1-1 120

ex1-2  factorial function -> n: int

ex1-3 <class 'function'> <class 'type'>

ex1-4 생략

ex1-5 {'__call__', '__annotations__', '__qualname__', '__globals__', '__closure__', '__name__', '__kwdefaults__', '__defaults__', '__get__', '__code__'} 10   

ex1-6 {'__code__', '__call__', '__defaults__', '__annotations__', '__name__', '__closure__', '__qualname__', '__get__', '__globals__', '__kwdefaults__'}

ex1-7 factorial

변수 활용

여기서는 함수를 변수에 할당하여 활용하는 방법에 대해 알아 볼게요.
우선 호출기호 소괄호()를 떼내고 함수명을 우항에 대고 할당할 변수를 좌항에 두면 기본적인 작업이 이루어 져요.

var_func = factorial
print('ex2-1',var_func)
print('ex2-2',var_func(5))
print('ex2-3',map(var_func, range(1,6)))
print('ex2-3',list(map(var_func, range(1,6))))


# 함수 인수 전달 및 함수로 결과 반환 -> 고위함수(Higher-order Function)

print('ex3-1', list(map(var_func, filter(lambda x: x % 2, range(1,6)))))
print('ex3-2', [var_func(i) for i in range(1,6) if i %2])

# reduce() 권장하지는 않지만 다른 언어에서 많이 사용하므로 개념 정도만 숙지함

from functools import reduce
from operator import add

print('ex3-3', reduce(add, range(1,11)))
print('ex3-4', sum(range(1,11))) # 또 다른 방법
  • ex2-1의 경우 function이라고 찍히고 해당 메서드의 이름과 메모리 위치 어디에 있는지 알려줘요.

  • ex2-2의 경우 우리가 알고 있는 함수처럼 인자값 5를 넣고 그 결과가 반환된 걸 확인 할 수 있어요.

  • ex2-3map()을 이용하여 메서드와 iterable한 객체를 맵핑 할 경우 map 객체가 반환되고 주소 값을 확인 할 수 있어요.

  • ex2-4는 바로 위 ex2-3에 저장된 값을 확인 하고 싶을 경우 list로 형변환 하면 값을 모두 확인 할 수 있어요.참고, 이외 tuple, set가능 but! dictionary 불가!

  • ex3-1, 보기만 해도 눈이 아찔한데요. 차분히 확인해보면 별거 아니에요.

    • list container에 있다는점
    • 그리고 map객체가 list안에 있다는거
    • map의 첫번째 인수가 var_func이라는 점/ 두번째 자리에 iterable 객체가 filter 객체라는점
      • filter 객체는 참이면 값을 갖고 있고 거짓이면 값을 포함하지아요.
    • filter 메서드의 첫번째 인수가 lambda(익명함수)함수고, 두번째 인수가 iterable한 객체가 오게되요.
  • ex3-2, ex3-1과 동일한 결과가 출력되지만 더 간단한 표현식인 리스트 컴프리헨션이에요.

  • ex3-3, python에서 depricated라고 해서 권장되지 않는 방법이지만 reduce함수를 소개할게요(다른 언어에서 많이 사용하는 개념이고 알아두면 도움이되요.)

    • reduce는 말 그대로 줄여 나간다는 건데요.첫 번째 인자로 함수를 받고 두번째 iterable한 자료를 바탕으로 하나씩 진행되요.
      • 여기서는 ((1+2)+3)+4...요런 방식이에요.
  • ex3-4, ex3-3을 간단하게 만든 방법이에요.

output

ex2-1 <function factorial at 0x000001EFF958D280>
ex2-2 120
ex2-3 <map object at 0x000001EFF9548D60>
ex2-3 {1, 2, 6, 24, 120}
ex3-1 [1, 6, 120]
ex3-2 [1, 6, 120]
ex3-3 55
ex3-4 55

익명함수(lambda)

가급적 주석 사용
가급적 함수 사용
일반 함수 형태로 리팩토링 권장

print('ex3-5', reduce(lambda x, t: x+t, range(1,11)))
# 결과: ex3-5 55

ex3-4와 차이점이 있다면 add함수를 썻냐 아니면 lambda함수를 썻냐는 차이점 밖에 없어요.

Callable : 호출 연산자 -> 메소드 형태로 호출 가능한지 확인
호출 연산자를 알게 모르게 우리는 확인 했다는 거 알고 계신가요.

ex1-4 ['annotations', 'call',... 만약 call 이 녀석이 있으면 호출 가능하다는 의미임.

import random 

# 로또 추첨 클래스 선언 
class LottoGame:
	def __init__(self):
		self._balls = [n for n in range(1,46)]
		
	def pick(self):
		random.shuffle(self._balls)
		return sorted([random.choice(self._balls) for _ in range(6)])

	def __call__(self):
		return self.pick()

# 객체 생성
game = LottoGame()

# 게임 실행
# 호출 가능 확인
print('Ex4-1',callable(str), callable(list), callable(factorial), callable(3.14))
print('Ex4-2',game.pick())
print('Ex4-3',game())
print('Ex4-4',callable(game))

output

Ex4-1 True True True False
Ex4-2 [2, 7, 23, 25, 30, 41]
Ex4-3 [8, 14, 20, 25, 31, 38]
Ex4-4 True

로또 게임을 활용해서 callable메소드를 이용해 볼게요.

  • 더블언더 init 메서드를 활용해서 1~45까지의 숫자가 있는 self._balls 인스턴스 속성을 만들게요.

  • 그리고 pick() 메서드를 정의해서 내부에 random.shuffle()로 섞어줄게요. 그리고 return할 때는 list 컴프리헨션을 이용해서 random한 값을 6번 골라서 작은 값 부터 정렬대도록 sorted()를 이용합니다.

  • 그리고 __call__()메서드를 사용해서 ex4-3처럼 클래스의 객체를 호출 가능하게 만들수 있게 할거에요.

  • ex4-1을 보면 정수 3을 제외한 str, list, factorial 모두가 callable True라는 걸 알수 있어요.

    • 왜냐? () 괄호를 str(), list(), factorial()과 같이 쓸수 있기 때문이고 이를 가능하게 한것이 dir()로 각각 3개를 까보면 __call__()메직 메소드가 있기 때문이에요.
  • ex4-2, 로또 추첨기가 잘 사용된 걸 확인 할 수 있어요.

  • ex4-3, 만약 LottoGame 클래스 안에 __cal__()메서드가 정의 되어 있지 않다면 4-3은 TypeError: 'LottoGame' object is not callable라는 오류가 발생하게 되조.
    -ex4-4, class 안에 __cal__()를 정의 했기에 True를 확인 할 수 있어요.


다양한 매개변수 입력

(*args, **kwargs) / packing, unpacking에 관한 것

파이썬의 특성상 다양한 매개변수를 받게 되요.

  • *가 하나 있는 경우, packing하여 tuple자료형으로 넘어오게됨
  • **개는 딕셔너리 자료형으로 넘어오게됨
def args_test(name, *contents, point=None, **attrs):
	return '<args_test> -> ({}) ({}) ({}) ({})'.format(name, contents, point, attrs)

print('ex5-1', args_test('test1', 'test2'))# test2에 잘보면 ,가 있는데 튜플의 증거임.
print('ex5-2', args_test('test1', 'test2', 'test3', id='admin', point=7)) # id는 키값 'admin'은 value값으로 지정됨
print('ex5-3', args_test('test1', 'test2', 'test3', id='admin', point=7, password='1234')) 

output

ex5-1 <args_test> -> (test1) (('test2',)) (None) ({})
ex5-2 <args_test> -> (test1) (('test2', 'test3')) (7) ({'id': 'admin'})        
ex5-3 <args_test> -> (test1) (('test2', 'test3')) (7) ({'id': 'admin', 'password': '1234'})

함수의 인자값 종류에 대해 알아볼게요.

  • ex5-1에서는 'text1'의 인자는 -> name이 받고, 'test2'-> *contents가 받게되요.
    • test2는 tuple 자료형인데 잘보면 , 콤마가 붙어 있어요. 나머지 point와 **attrs값은 입력되지 않아 None, 빈 딕셔너리 값이란걸 확인 할 수 있어요.
  • ex5-2, point에 7이라는 값을 넣고, id='admin'이라고 넣어 봤더니 각각 7이라는 정수와 딕셔너리로 id가 key - 'admin'이 value로 pair를 이루고 있는걸 확인가능하네요
  • ex5-3,

함수 Signiture

inspect 패키지는 자주 사용하지만 개발자를 위한 프레임워크를 만들지 않는 이상 아래 소스코드와 같이 잘 사용하지 않음

from inspect import signature

sg = signature(args_test)

print('ex6-1',sg)
print('ex6-2',sg.parameters)

print()

# 모든 정보 출력

for name, param in sg.parameters.items():
	print('ex6-3 -', name,  param.kind, param.default)
ex6-1 (name, *contents, point=None, **attrs)
ex6-2 OrderedDict([('name', <Parameter "name">), ('contents', <Parameter "*contents">), ('point', <Parameter "point=None">), ('attrs', <Parameter "**attrs">)])

ex6-3 - name POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
ex6-3 - contents VAR_POSITIONAL <class 'inspect._empty'>
ex6-3 - point KEYWORD_ONLY None
ex6-3 - attrs VAR_KEYWORD <class 'inspect._empty'>
  • 6-1, 매개변수를 확인 할 수 있어요.
  • 6-2, OrderedDict 형태로 매개변수의 이름과 <parameter "?">로 나열하여 보여주네요.
  • 6-3, 파라미터 이름, 파라미터 종류, 기본값 이렇게 3가지를 출력해서 더 구체적으로 보여줄수 있어요.

partial 사용법

인수 고정 -> 주로 특정 인수 고정 후 콜백 함수에 사용
functools 패키지에서 partial 메소드를 사용하는게 keypoint에요.

이를 활용하기 위해 mul메서드(곱하기 연산자)를 operator 패키지에서 가져다가 임포트도 합니다.

from operator import mul
from functools import partial

print('ex7-1', mul(10,100)) # 참고로 인수 3개는 안됨

# 인수 고정
five = partial(mul, 5) # mul 함수에 5를 고정 시켜버리고 변수에 저장함
print('ex7-2',five(1000)) # five 함수에는 현재 5가 고정 되어 있으니 어떠한 값을 넣든 넣은 값에 *5임

# 고정 추가 
six = partial(five, 6) # five함수에 6을 추가함 결국, 5*6이 되어버림
print('ex7-3',six()) # 만약 six함수에 인수를 넣으면 오류 발생함.(인수3개를 넣어 버린것이기 때문)
print('ex7-4',[five(i) for i in range(1,11)]) # partial 함수로 한쪽을 고정해서 리스트로 요소를 하나씩 꺼내서 해당 고정값과 곱하여 결과를 냄.
print('ex7-5', list(map(five, range(1,11))))

output

ex7-1 1000
ex7-2 5000
ex7-3 30
ex7-4 [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
ex7-5 [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
  • 7-1, mul()메서드는 인수를 2개만! 받아요. 3개 받으면 오류발생!
  • 7-2, partial() 메서드로 첫 번째 인자를 mul 메서드의 이름을 넣고, 두번째로 mul 메서드의 인자값으로 고정하고자 하는 숫자 5를 넣어 five라는 변수명의 클래스 객체를 만들어요.
  • 7-3, five(1000) == mul(5,1000)과 동일합니다.
  • 7-4, 리스트 컴프리헨션을 활용해 볼 게요.
    • [1,2,3,4,5,6,7,8,9,10]의 각 요소에 5를 곱한 결과에요.
  • 7-5, 7-4와 동일한 결과가 도출되지만 map함수를 이용해서 만들어 볼 수 도 있어요.
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글