IT/언어

[python] 타입힌트(Type Hints)

개발자 두더지 2021. 4. 12. 01:38
728x90

  Python 3.5부터 Type Hints라는 기능이 도입되었다. 이것은 (데이터)형에 관한 주석(데이터형 어노테이션)을 붙일 수 있는 것으로 간단히 살펴 보자면 아래와 같이 작성한다.

def greeting(name: str) -> str:
    return 'Hello ' + name

 어노테이션에 실제로 해당하는 부분은 아래와 같다.

- name: str : 인수 name이, str형이라는 것을 어노테이션한다.

- -> str : 함수 greeting의 반환값의 형이 str이라는 것을 어노테이션한다.

  또한 Type Hints에서는 변수 선언에 있어서 형 코맨트에 대해서도 언급하고 있다.

x = []   # type: List[Employee]

 이것은 어떠한 문법이 아니라 정말 코맨트의 확장 역할이지만, 현재 이미 이러한 형태로 형에 관련된 코맨트를 붙인 상태에서 위의 기법대로 작성한다면 이후에 어떠한 툴이 형 체크를 할 가능성이 있다.

 이것이 Python에 도입된 "형식이 있는 세계'가 된다.

 또한, 붙여진 어노테이션은 실행시에는 체크되지 않는다. 즉 코맨트의 연장선이 된다. 따라서 강제력이 없지만, 실행시에 무엇도 하지 않기 때문에 성능에 영향을 미치지 않는다. 

 원칙적으로는 정적 분석을 위해 문법이지만, typing.get_type_hints로 어노테이션 정보를 얻을 수 있기 때문에 본인 스스로 실행시에 체크할 수 있는 기능을 만들 수 있다(typeannotations 등). 

 

 

어노테이션의 작성법


 어노테이션의 작성법에 대해서는 typing에 따라 소개하고자 한다.

 

1) Type aliases

형의 별칭(별명)을 정의하는 것이 가능하다.

Vector = List[float]

 다만 개인적인 경험으로는 이러한 작성법은 혼란을 초래하는 경우가 있기 때문에 (클래스인가 별칭인가 판별이 바로 되지 않는 경우가 있다), 사용할 때 이름을 붙이는 방법에 대해 주의할 필요가 있다.

 

2) Callable

 함수의 인수, 반환값 형의 정의를 정리하기 위한 것이다. 콜백함수와 같이 함수가 곧 인수가 되는 경우, 혹은 함수를 반환하는 경우에 이용한다.

 Callable([인수형], 반환값 형식) 과 같은 문법으로 작성한다. 아래에서는 함수를 인수로 받는 feeder/async_query에 대해, Callable로 어노테이션을 작성한 예이다.

from typing import Callable

def feeder(get_next_item: Callable[[], str]) -> None:
    # Body

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    # Body

 

3) Generics

  소위 Generics를 사용하는 것도 가능하다. Java등에서 쓰는 List<T>를 아래와 같이 쓸 수 있다. 

from typing import Sequence, TypeVar

T = TypeVar('T')      # Declare type variable

def first(l: Sequence[T]) -> T:   # Generic function
    return l[0]

 또한, TypeVar에서는 Generics로써 유효한 데이터 형을 한정하는 것도 가능하다. 아래의 샘플 코드에서는 AnyStr로써 str, bytes만을 허용하고 있다.

from typing import TypeVar

AnyStr = TypeVar('AnyStr', str, bytes)

def concat(x: AnyStr, y: AnyStr) -> AnyStr:
    return x + y

 

4) 사용자 정의 Generic types (User-defined generic types)

 Generics class로 MyClass<T>와 같은 것을 만들고 싶은 경우, 아래와 같이 정의한다.

from typing import TypeVar, Generic

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('{}: {}'.format(self.name message))

 그리고 아래와 같이 사용할 수 있다.

from typing import Iterable

def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
        var.set(0)

 

5) The Any type

 모든 type은 Any의 서브타입이 된다. Any는 클래스의 object와는 다르기 때문에, 주의가 필요하다.

 

6) Union types

 허용되는 몇 가지 형을 묶어 정리한 것이다. Optinal는 이 Union의 한 종류이다.

from typing import Union

def handle_employee(e: Union[Employee, None]) -> None: ...

 위 코드는 아래의 코드와 동일하다.

from typing import Optional

def handle_employee(e: Optional[Employee]) -> None: ...

 

 

형을 확인하는 방법


 위에서 얘기했듯, 형 어노테이션의 작성은 형을 붙이고 싶은 대부분의 상황을 망라하고 있다. Python 표준에서는 어노테이션을 체크하기 위한 공식 툴을 제공하고 있지 않았지만(3.5 출시 당시), 다른 블로그 글을 찾아보니 3.6에서는 단순 print로도 확인할 수 있도록 제공하고 있는 듯하다. 참고로 3.5 버전의 경우에서는 mypy라는 툴을 사용하여 확인했었다.

 

 

도입 방침


  Type Hinst의 도입 효과로써는 아래와 같은 것들이 있을거라고 생각한다.

- 간단한 단위테스트 : 실행시에 함수의 잘못 이용하여 발생하는 에러를 방지하기 위한 테스트로써의 기능을 할 수 있다.

 → 데이터 베이스 등, 형의 엄밀성이 요구되는 부분에 적용

 → 날짜 등, 사양이 애매한 부분 (UTC인가, 표기법은 어떻게 할 것인가 등) 에 대해 형을 명시적으로 체크하기 위해 사용

 → 동적 호출 등을 활용하는 곳에 형을 명시

- 사양의 명확화 : 인수로써 무엇을 허용하고 어떠한 값을 리턴해주는 가를 명시적으로 표시한다.

 → Optional 을 사용하는 것으로 None대응의 필요성에 대해 시사

 → 호출하는 쪽에서 사전처리해 둘 것인가, 처리하는 쪽에서 대응할 것인가 애매한 경우에 ( 예를 들어, 호출 전에 캐스트해둘 것인가, 요소가 1개인 경우는 하나의 배열로 해 둘 것인가 등) 사양을 명확히한다.

→ Callback적으로 이용하는 함수에 대해, 다시 한 번 사양을 명확화한다(Deligate).  

그리고, 적용대상에 대해서는 아래와 같이 분류할 수 있다고 생각한다.

- 완전 대상 : 모든 처리가 대상

- 부분 대상 : DB쪽 처리, 특정 모듈 등 일정 기준을 바탕으로 설정된 부분이 대상이 된다.

 어노테이션 하고자할 때 어노테이션의 도입 패턴으로 다음과 같이 생각할 수 있다.

- 완전 도입 : 모든 적용 대상에 대해 어노테이션을 작성

-  순차 도입 : 사전 설정이나 코드 리뷰 등, 작성하는 편이 좋은 곳부터 순차적으로 작성

- 자유 도입 : 코맨트와 같이, 개발자가 필요하다고 생각할 때 작성

어노테이션을 적용하는 것에 대해 어떠한 기대 효과를 가지고 있는지 명확하게 하지 않으면, 적용 대상의 도입 방법 기준도 애매해지기 때문에 어노테이션의 어떠한 장점을 최대화하고 싶은지 정한 뒤 도입하는 것이 좋다.


참고자료

qiita.com/icoxfog417/items/c17eb042f4735b7924a

728x90