일급 함수는 일급 객체로서의 함수를 말한다. 일급 객체는 다음의 조건을 만족한다.
파이썬에서는 정수, 문자열, 딕셔너리는 일급 객체이다. 심지어 함수도 일급 객체이다. 일급 함수일 경우 생기는 여러 유용한 활용법을 알아보자.
중요 키워드
help, __doc__
고위함수, map, filter, functools.reduce, sum, all, any
lambda
callable
데코레이터, 클로저
dir, __dict__
inspect, signature, _ParameterKind, annotation
operator, functools, itemgetter, attrgetter, methodcaller, partial, partialmethod
덜 중요한 키워드
operator.add
메모이제이션, lru_cache, singledispatch, wraps
참고 자료
http://docs.python.org/3/howto/functional.html
https://docs/djangoproject.com/en/1.5/ref/contrib/admin
http://bit.ly/1Vm8dv2
http://bit.ly/1FHiOXf
http://bit.ly/1FHiN5F
https://www.python.org/dev/peps/pep-0362/
http://bit.ly/1FHiTdh
http://bit.ly/1FHjdZv
def 키워드를 통해 런타임에 함수를 생성할 수 있다. (변수명=함수명)을 하여 변수명에 함수를 대입할 수 있다. type(함수명)을 하면 function클래스의 객체라는 사실을 알 수 있다. 함수명.__doc__
을 하여 함수의 docstring을 가져올 수 있다. help(함수명)을 하여 함수에 대한 설명을 가져올 수 있다. map함수의 인자는 함수를 받는다.
함수를 인자로 받거나 결과로 반환하는 함수를 고위함수라 한다. map, filter, functools.reduce가 고위함수이다. 이는 지능형 리스트, 제너레이터 표현식 등으로 대체할 수 있다. reduce로 합을 구할 땐 대신 sum을 사용하는 것이 가독성 측면에서 더 좋다. all, any함수도 유용하다.
lambda로 익명함수를 생성할 수 있는데 고위함수의 인자로 주로 사용된다. lambda는 하나의 표현식으로 만들어져야 한다는 제한이 있어서 while, try같은 구문을 사용하지 못해 자주 쓰이지는 않는다.
()로 호출할 수 있는 객체를 콜러블이라 한다. callable함수를 통해 해당 객체가 콜러블인지 확인할 수 있다. 파이썬에서 콜러블은 7가지가 있다.
__new__
메서드를 호출한다.__call__
메서드를 호출한다.제너레이터 함수는 여타 콜러블과 다르고 코루틴으로 활용될 수도 있다.
다음과 같이 __call__
을 구현하여 클래스 객체가 콜러블로 작동하게 할 수 있다.
import random
class Cage:
def __init__(self, items):
self._items = list(items)
random.shuffle(self._items)
def pick(self):
try:
return self._items.pop()
except:
raise LookupError('Empty Cage')
__call__ = pick
__call__=pick
을 def __call__
를 통해 선언하여 pick의 결과를 반환하도록 구현해도 된다.__init__
의 items를 로컬에 복사할 때 list생성자를 사용하지 않으면 바깥으로부터 넘어온 items객체와 로컬의 self._items가 같은 객체를 공유하기 때문에 위험하다.함수에 정보를 저장해놓고 사용하는 이런 구조의 경우 데코레이터로 만들 수도 있고 메모이제이션같은 상황에 활용될 수 있다. 함수의 내부 상태를 구성해서 반환하는 함수인 클로저도 있다.
function클래스의 객체는 dir
을 통해 어떤 속성이 있는지 확인할 수 있다. 클래스와 마찬가지로 __dict__
가 있어서 함수에 사용자 속성을 저장할 수 있다. 장고에서 이런 기능이 활용된다.
function클래스도 클래스이므로 클래스가 가지고 있는 속성을 모두 가지고 있다. 클래스에는 없는, function클래스에만 있는 속성에는 __annotations__
, __call__
, __closure__
, __code__
, __defaults__
, __get__
, __globals__
, __kwdefaults__
, __name__
, __qualname__
이 있다.
함수의 매개변수는 다음과 같이 사용할 수 있다.
def f(a, *b, c, **d):
print('positional parameter'.ljust(40), a)
print('arbitrary positional parameters'.ljust(40), b)
print('keyword parameter'.ljust(40), c)
print('arbitrary keyword parameters'.ljust(40), d)
f(1, 3, 2, c=1, d=2, e=51)
---결과---
positional parameter 1
arbitrary positional parameters (3, 2)
keyword parameter 1
arbitrary keyword parameters {'d': 2, 'e': 51}
만약 위 코드에서 b, d가 필요없어서 지웠다면 c는 positional parameter가 되어버린다. 그대로 keyword-only로 받고 싶다면 def f(a, *, c)
로 선언한다.
함수명.__defaults__
로 파라미터 기본값을 tuple로 확인할 수 있다.
함수명.__kwdefaults__
로 키워드 파라미터의 기본값을 dict로 확인할 수 있다.
함수명.__code__.co_argcount
로 positional 파라미터의 수를 확인할 수 있다.
함수명.__code__.co_varnames
로 파라미터와 내부 변수의 이름을 tuple로 확인할 수 있다.
inspect.signature
로 어떤 함수에 대한 Signature
객체를 얻을 수 있는데, 이 객체의 parameters 속성으로 파라미터의 정보가 담긴 OrderedDict를 얻을 수 있다. 각 파라미터는 Parameter객체로 되어있는데 name, default, kind의 속성이 있다. default값이 없을 경우 inspect._empty
로 되어있다. kind는 아래에서 설명하는 inspect._ParameterKind형으로 확인할 수 있다.
함수의 파라미터 종류는 inspect._ParameterKind에 정의되어있다. 총 5가지로, 다음과 같다.
from inspect import _ParameterKind list(_ParameterKind) ---결과--- [<_ParameterKind.POSITIONAL_ONLY: 0>, <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>, <_ParameterKind.VAR_POSITIONAL: 2>, <_ParameterKind.KEYWORD_ONLY: 3>, <_ParameterKind.VAR_KEYWORD: 4>]
각 파라미터 종류별로 하나씩 가지고 있는 함수는 다음과 같이 선언한다.
def f(a=3, /, b=2, *c, e=3, **f): pass for k, v in signature(f).parameters.items(): print(v.kind) ---결과--- POSITIONAL_ONLY POSITIONAL_OR_KEYWORD VAR_POSITIONAL KEYWORD_ONLY VAR_KEYWORD
매개변수로
/
나*
가 올 수 있는데,/
왼쪽은 positional파라미터,*
오른쪽은 keyword파라미터, 그 사이는 두 방식 모두 인자를 전달할 수 있는 positional or keyword파라미터이다.
Parameter에는 annotation 속성도 있다. 파라미터에 적용한 어노테이션 정보가 담긴다.
Signature
객체의 bind함수를 통해 함수를 실행시키기 전에, 전달하려는 인자가 함수에 맞는지 확인할 수 있다. 맞으면 BoundArguments
객체를 얻을 수 있고 각 파라미터에 어떤 값이 할당되었는지 arguments
속성을 통해 dict로 확인할 수 있다. 맞지 않으면 오류가 발생한다.
다음과 같이 함수의 파라미터와 반환값에 어노테이션을 붙일 수 있다.
def f(a:int, b:'int>0'=3) -> None:
pass
f.__annotations__
---결과---
{'a': int, 'b': 'int>0', 'return': None}
Signature객체의 return_annotation속성을 통해 반환형의 어노테이션을 얻을 수 있고 Parameter객체의 annotation속성을 통해 파라미터의 어노테이션을 얻을 수 있다. 얻은 어노테이션을 통해 파라미터의 자료형을 체크하거나 조건을 검사할 수 있다. linter와 같은 도구에서 활용된다.
operator 모듈에는 다양한 간단한 함수가 선언되어있다. 이를 활용하여 고위함수에서 함수 인자로 전달하기 위한 함수를 lambda 대신 대체할 수 있다.
import functools
import operator
print(functools.reduce(lambda x, y: x * y, range(1, 10)))
print(functools.reduce(operator.mul, range(1, 10))) #윗 줄과 실행결과가 같다.
operator.mul은 두 수를 입력받아 곱하여 반환하는 함수이다. add, sub 등 다양한 함수가 미리 정의되어있다. 그 외에도 itemgetter, attrgetter, methodcaller도 있다.
itemgetter(i)는 lambda x: x[i]를 대체한다. slice객체를 전달해도 되고 인자를 여러 개 넣으면 각각 인덱싱한 결과를 튜플로 묶어 반환한다.
attrgetter(i)는 lambda x: x.i를 대체한다. 이때 i는 문자열로 전달해야 한다. 마찬가지로 인자 여러 개를 넣으면 튜플의 결과를 얻을 수 있고 연달아 .연산으로 접근해야 하는 경우 i에 .을 넣어 한 번에 접근하는 것이 가능하다.
methodcaller(f, a, ...)는 lambda x: x.f(a, ...)를 대체한다. 가장 처음 인자가 메소드 명이고 그 뒤로 이어지는 인자가 f의 인자로 들어간다.
functools 모듈에는 함수형 프로그래밍에 유용한 partial, partialmethod 함수가 있다. partial은 함수와 그 함수의 인자 중 일부를 전달하여, 전달된 일부의 인자로 고정시키고 나머지 인자들로 함수를 호출할 수 있다. partialmethod는 partial과 똑같지만 클래스의 메소드에 대하여 작동한다.
functools 모듈에 있는 lru_cache, singledispatch, wraps 등 데코레이터와 함께 메모이제이션을 구현할 수 있다.