📌 이 포스팅에서는 Python의 First-Class Functions의 특징과 Function의 callable, signatures, partial 매서드에 대해 알아보겠습니다.
🔥 First-Class Functions 특징
🔥 Functions의 parameter
🔥 Functions의 내장 매서드
✔️ 재귀함수를 이용하여 Factorial을 이용하면 아래와 같습니다. return을 통해 다시 함수를 반환할 수 있는 것은 Function의 특징 중 하나입니다.
def factorial(n): if n == 1: return n return n * factorial(n-1) # 👈 함수를 반환 가능 print(factorial(5)) # 120
✔️ 함수를 변수에 할당한 뒤 사용할 수 있습니다.
def factorial(n): if n == 1: return n return n * factorial(n-1) var_func = factorial # 👈 함수를 변수에 할당 print(var_func) # <function factorial at 0x7ffeda7d4040> print(var_func(5)) # 120 print(map(var_func, range(1,6))) # <map object at 0x7fb245056ee0> print(list(map(var_func, range(1,6)))) # [1, 2, 6, 24, 120]
✔️ map, filter, reduce 등 다른 함수에 함수를 인수로 전달하여 사용할 수 있습니다.
def factorial(n): if n == 1: return n return n * factorial(n-1) print(factorial(5)) # 120 print(map(var_func, filter(lambda x: x%2, range(1,6)))) # <map object at 0x7fea8b156fa0> # 👈 map함수에 var_func 함수를 parameter로 전달 print(list(map(var_func, filter(lambda x: x%2, range(1,6))))) # [1, 6, 120] <= 1,3,5 만 실행 print([var_func(i) for i in range(1,6) if i % 2]) # [1, 6, 120] <= 1,3,5 만 실행
✔️ reduce 함수는 배열의 원소를 순회하며, 값을 누적시키면서 함수를 실행합니다.
✔️ 단, python에서는 range함수로도 가능하기 때문에 reduce 함수를 잘 사용하진 않습니다.
✔️ 또한 익명함수를 사용할 때는 자신과 타인을 위해 가급적 주석을 사용하는 편이 좋습니다.
from functools import reduce from operator import add # reduce : 함수와 반복되는 객체를 전달 받아 함수에서 요구하되는 기능을 순차적으로 누적시킴 print(reduce(add, range(1,11))) # 55 print(reduce(lambda x, t: x+t, range(1,11))) # 55 👈 sum을 익명함수로 대체 print(sum(range(1,11))) # 55
✔️ 아스타 1개(*args
)는 여러 개를 패킹해서 tuple형식으로 넘기고, 아스타 2개(**kwargs
)는 dict로 parameter를 패킹하여 전달합니다.
✔️ 아스타 뒤에 args, kwargs는 다른 이름으로 사용해도되지만, 일반적으로 *args
, **kwargs
를 사용합니다.
def args_test(name, *contents, point=None, **attrs): return '<args_test> -> ({}) ({}) ({}) ({})'.format(name, contents, point, attrs) print(args_test('test1')) # <args_test> -> (test1) (()) (None) ({}) print(args_test('test1', 'test2')) # <args_test> -> (test1) (('test2',)) (None) ({}) print(args_test('test1', 'test2', 'test3', id='admin')) # <args_test> -> (test1) (('test2', 'test3')) (None) ({'id': 'admin'}) print(args_test('test1', 'test2', 'test3', point=7)) # <args_test> -> (test1) (('test2', 'test3')) (7) ({}) print(args_test('test1', 'test2', 'test3', point=7, password='7777')) # <args_test> -> (test1) (('test2', 'test3')) (7) ({'password': '7777'})
✔️ 함수에서만 존재하는 매서드는 아래와 같이 확인할 수 있습니다.
__name__
: 함수 이름 반환하는 매직 매서드__code__
: 함수의 위치 반환하는 매직 매서드# 함수 def my_func(n): return n # 클래스 class MyClass: pass print(type(my_func), type(MyClass)) # <class 'function'>, <class 'type'> print(set(dir(my_func))-set(dir(MyClass))) # 👈 함수만 갖고 있는 속성들 확인 """ { '__kwdefaults__', '__name__', '__annotations__', '__get__', '__code__', '__call__', '__globals__', '__qualname__', '__closure__', '__defaults__'} """ print(my_func.__name__) # my_func print(my_func.__code__) # my_func<code object my_func at 0x7fc32a24b920, file "/Users/jangjaewon/Documents/main.py", line 12>
✔️ callable은 호출 연산자로 해당 객체가 함수 형태로 호출 가능한지에 대해 Bool 형태로 반환시켜줍니다.
✔️ callable에 객체를 전달했을 때, True이면 함수처럼 호출하여 사용 가능하고 False이면 불가 합니다.
print(callable(str)) # True print(callable(int)) # True print(callable(list)) # True print(callable(factorial)) # True print(callable(3.14)) # False
✔️ callable 함수를 활용하여 클래스로 생성한 intance를 함수처럼 사용하는 방법을 알아보겠습니다.
✔️ 로또 번호 6개를 랜덤으로 출력해주는 클래스를 만들었을 때, callable 함수로 확인해보면 False를 반환하고 함수처럼 ()를 붙여 호출하면, TypeError가 발생됩니다.
✔️ 이는 클래스로 만든 인스턴스는 기본적으로 함수처럼 호출이 불가능하단 것을 의미합니다.
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 n in range(6)]) game = LottoGame() # 👈 객체 생성 print(game.pick()) # 👈 [10, 14, 14, 20, 32, 35] <=실행할 때마다 랜덤으로 6개씩 뽑아 정렬 후 반환 print(callable(game)) # 👈 False <= 함수처럼 호출 불가능 # print(game()) # 👈 TypeError: 'LottoGame' object is not callable
✔️ 이럴 때, callable 매직 매서드를 오버라이딩시키면 클래스로 만든 인스턴스를 함수처럼 호출하여 사용할 수 있습니다.
✔️ 함수의 호출 방법인 ()는 매직매서드인 callable과 같습니다.
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 n in range(6)]) def __call__(self): # 👈 callable 매직 매서드 오버라이딩 return self.pick() game = LottoGame() # 👈 객체 생성 print(game.pick()) # 👈 [10, 14, 14, 20, 32, 35] <=실행할 때마다 랜덤으로 6개씩 뽑아 정렬해서 반환 print(callable(game)) # 👈True print(game()) # 👈 [4, 11, 12, 34, 40, 45] <= 함수처럼 사용 가능하기 때문에 인스턴스로 바로 실행 가능
✔️ signatures 매서드에 함수를 넣으면 함수의 parameter를 조사(inspect)해서 반환합니다.
✔️ signature 함수는 inspect에서 import하여 사용할 수 있습니다. ⇢ 🔎 from inspect import signature
✔️ parameters을 찍어주면, 더 자세한 정보를 확인할 수 있습니다.
from inspect import signature # 👈 signature 위치 def args_test(name, *contents, point=None, **attrs): return '<args_test> -> ({}) ({}) ({}) ({})'.format(name, contents, point, attrs) sg = signature(args_test) print(sg) # 👈 (name, *contents, point=None, **attrs) print(sg.parameters) # 👈 parameters는 더 구체적인 세부사항을 아래처럼 반환시켜줍니다. """ OrderedDict([('name', <Parameter "name">), ('contents', <Parameter "*contents">), ('point', <Parameter "point=None">), ('attrs', <Parameter "**attrs">)]) """ for name, param in sg.parameters.items(): print(name, param.kind, param.default) # 👈 name, param 종류, param 기본값 """ name POSITIONAL_OR_KEYWORD <class 'inspect._empty'> contents VAR_POSITIONAL <class 'inspect._empty'> point KEYWORD_ONLY None attrs VAR_KEYWORD <class 'inspect._empty'> """
✔️ partial 매서드는 인수(parameter)를 고정하여 사용할 수 있게 해주며, 주로 콜백 함수에 사용합니다.
✔️ 첫번째 parameter는 함수를 받고, 두번째 parameter는 고정할 인수를 받습니다.
✔️ 이를 이용하여 하나 이상의 인수(parameter)를 고정(할당)시킨 새로운 함수를 반환받아 사용할 수 있습니다.
from operator import mul # 👈 곱하기 함수 from functools import partial # 👈 partial 함수 print(mul(10, 100)) # 1000 <= mul 함수는 2개의 인자를 받아 곱해주는 "*"와 같습니다. five = partial(mul, 5) # mul은 파라미터를 2개 받을 수 있는데, 그 중 하나를 5로 고정하겠다. six = partial(five, 6) # # 이미 인수로 5가 고정된 five 함수에 인수 6을 고정시키겠다.(인수 더이상 못들어감) print(five(100)) # 500 print(six()) # 30 # 활용 print([five(i) for i in range(1,11)]) # [5, 10, 15, 20, 25, 30, 35, 40, 45, 50] print(list(map(five, range(1,11)))) # [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]