함수 객체 예제
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, 보기만 해도 눈이 아찔한데요. 차분히 확인해보면 별거 아니에요.
ex3-2, ex3-1과 동일한 결과가 출력되지만 더 간단한 표현식인 리스트 컴프리헨션이에요.
ex3-3, python에서 depricated라고 해서 권장되지 않는 방법이지만 reduce함수를 소개할게요(다른 언어에서 많이 사용하는 개념이고 알아두면 도움이되요.)
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
가급적 주석 사용
가급적 함수 사용
일반 함수 형태로 리팩토링 권장
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라는 걸 알수 있어요.
ex4-2, 로또 추첨기가 잘 사용된 걸 확인 할 수 있어요.
ex4-3, 만약 LottoGame 클래스 안에 __cal__()메서드가 정의 되어 있지 않다면 4-3은 TypeError: 'LottoGame' object is not callable
라는 오류가 발생하게 되조.
-ex4-4, class 안에 __cal__()를 정의 했기에 True를 확인 할 수 있어요.
파이썬의 특성상 다양한 매개변수를 받게 되요.
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'})
함수의 인자값 종류에 대해 알아볼게요.
딕셔너리로 id가 key - 'admin'이 value로 pair를 이루고 있는걸 확인가능하네요
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'>
인수 고정 -> 주로 특정 인수 고정 후 콜백 함수에 사용
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]
five
라는 변수명의 클래스 객체를 만들어요.