본문 바로가기
CS

[Python] 함수 인트로스펙션(Function Introspection)과 매개변수(Function Parameter), 애너테이션(Function Annotation)

by cuda 2022. 11. 30.

함수 인트로스펙션

함수 객체는 __doc__이외에도 많은 속성을 가지고 있다. 일반적인 객체에는 없지만 함수에는 있는 고유한 속성을 알아보자.
집합으로 변환한 뒤, 차집합을 구하는 방식으로 객체에는 없지만 함수에는 있는 고유한 속성을 나타내보았다.

>>> class C: pass
>>> obj = C()
>>> def func(): pass
>>> sorted(set(dir(func)) - set(dir(obj)))
['__annotations__', '__call__', '__closure__', '__code__', '__defaults__', '__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']

함수 매개변수에 대한 정보

함수에 어떤 매개변수가 필요한지, 매개변수에 기본값이 있는지 없는지 확인할 수 있는 방법은 무엇이 있을까?
함수 객체 안의 __defaults__속성에는 위치 인수와 키워드 인수의 기본값을 가진 튜플이 들어있다.
키워드 전용 인수의 기본값은 __kwdefaults__속성에 들어 있다. 그러나 인수명은 __code__속성에 들어 있는데,
이 속성은 여러 속성을 담고 있는 code 객체를 가리킨다.
예제와 함께 알아보자

def clip(text, max_len = 80):
    """
    max_len 앞이나 뒤의 마지막 공백에서 잘라낸 텍스트 반환
    """
    end = None
    if len(text) > max_len:
        space_before = text.rfind(" ", 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(" ", max_len)
            if space_after >= 0:
                end = space_after

    if end is None:
        end = len(text)
    return text[:end].rstrip()

다음과 같이 원하는 길이 가까이에 있는 공백을 기준으로 문자열을 잘라서 반환하는, 문자열 단축 함수가 있다.

>>> clip.__defaults__
(80,)

>>> clip.__code__.co_varnames
('text', 'max_len', 'end', 'space_before', 'space_after')

>>> clip.__code__.co_argcount
2

__defaults__를 통해 함수의 위치 인수와 키워드 인수의 기본값을 확인할 수 있다.
또한 __code__를 통해 더 자세히 알아볼 수 있는데, __code__.co_varnames를 통해 인수명과 함수 본체에서 생성한 지역 변수명도 들어 있다.
이 때, 맨 앞의 __code__.co_argcount로 나오는 개수만큼이 함수의 인수명이고, 그 뒤는 모두 함수 본체에서 생성한 지역 변수이다.
그러나, 이런 방식으로 보면 매우 복잡하고, 가독성도 떨어진다.
그래서 inspect모듈을 사용하면 더욱 깔끔하게 나타낼 수 있다.

>>> from inspect import signature
>>> sig = signature(clip)
>>> str(sig)

>>> for name, param in sig.parameters.items():
...     print(param.kind, ":", name, '=', param.default)
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80

inspect.signature()는 inspect.Signature 객체를 반환하며, 이 객체에 들어 있는 parameters속성을 이용하면 정렬된 inspect.Parameter 객체를 읽을 수 있다.
Parameter객체 안에는 name, default, kind 등의 속성이 들어 있다.
inspect._empty라는 값은 해당 매개변수에 default 값이 없음을 나타낸다.
kind속성은 _ParameterKind클래스에 정의된 다음 다섯 가지 값 중 하나를 가진다.

  • POSITIONAL_OR_KEYWORD

    위치 인수나 키워드 인수로 전달할 수 있는 매개변수(파이썬 함수 매개변수 대부분이 여기에 속한다.)
  • VAR_POSITIONAL

    위치 매개변수의 튜플
  • VAR_KEYWORD

    키워드 매개변수의 딕셔너리
  • KEYWORD_ONLY

    키워드 전용 매개변수(Python 3)
  • POSITIONAL_ONLY

    위치 전용 매개변수. 현재 파이썬 함수 선언 구문에서는 지원되지 않지만, 키워드로 전달한 매개 변수를 받지 않는 divmod()처럼 C언어로 구현된 기존 함수가 여기에 속함

함수 애너테이션(Function Annotation)

파이썬 3부터는 함수의 매개변수와 반환값에 메타데이터를 추가할 수 있는 구문을 제공한다.
위에서 알아봤던 clip()함수에 애너테이션을 추가한 버전을 살펴보겠다.

def clip(text:str, max_len:'int > 0' = 80) -> str:
    """
    max_len 앞이나 뒤의 마지막 공백에서 잘라낸 텍스트 반환
    """
    end = None
    if len(text) > max_len:
        space_before = text.rfind(" ", 0, max_len)
        if space_before >= 0:
            end = space_before
        else:
            space_after = text.rfind(" ", max_len)
            if space_after >= 0:
                end = space_after

    if end is None:
        end = len(text)
    return text[:end].rstrip()

함수 선언에서 각 매개변수에는 콜론(:)뒤에 애너테이션 표현식을 추가할 수 있다. 기본값이 있을 때, 애너테이션은 인수명과 등호(=)사이에 들어간다.
반환값에 애너테이션을 추가하려면 매개변수를 닫는 괄호와 함수 선언의 제일 뒤에 오는 콜론 사이에 -> 기호와 표현식을 추가한다.
이 때, 애너테이션 표현식은 어떠한 자료형도 될 수 있다. str이나 int와 같은 클래스, 혹은 위 예제에서의 max_len에 대한 애너테이션인
'int > 0' 과 같은 문자열이 애너테이션에 가장 널리 사용되는 자료형이다.
애너테이션은 인터프리터가 전혀 처리하지 않으며, 오로지 함수 객체 안의 dict형 __annotations__속성에 저장될 뿐이다.

>>> clip.__annotations__
{'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}

다음과 같이 dictionary에 작성했던 parameter와 return의 annotation이 출력됨을 확인할 수 있다.
언급한대로, 애너테이션은 파이썬 인터프리터에 아무런 영향을 끼치지 않는다.
위에서 잠깐 알아본 inspect.signature() 라이브러리로 애너테이션을 추출할 수 있다.

>>> from inspect import signature
>>> sig = signature(clip)
>>> sig.return_annotation
<class 'str'>

>>> for param in sig.parameters.values():
...     note = repr(param.annotation).ljust(13)
...     print(note, ":", param.name, "=", param.default)

<class 'str'> : text = <class 'inspect._empty'>
'int > 0'     : max_len = 80

signature()함수는 Signature객체를 반환한다. Signature에는 return_annotationparameters 속성이 있는데,
parameters는 파라미터명을 Parameter객체에 매핑하는 딕셔너리다. 각 Parameter객체는 annotation 속성을 가지고 있는데, 예제에서 이를 이용하여 에너테이션을 출력했다.

댓글