타입시스템이란

  • 언어의 기본 타입 또는 개발자가 정의한 타입을 기반으로 해당 타입을 언어와 연관시키는 메커니즘을 뜻함

  • 타입 동등, 타입 호환, 타입 추론에 대한 규칙을 지킨다면 타입시스템이라고 볼 수 있음

    • 동등: 두 타입이 동일할 때 적용되는 규칙(int = int)
    • 호환: 두 타입이 정확히 일치하지 않더라도 어느정도 호환이 되는지를 나타내는 규칙(any :=int)
    • 추론: 타입이 정의되어있지 않지만 주변 문맥에 따라 타입이 결정됨(a=3이면 a는 int)
  • 모든 언어는 이러한 타입시스템을 가지고 있으며 동적 타입과 정적 타입으로 나눠진다

    • 동적 타입: 런타임에 모든 변수의 유형을 결정하고 잘못된 경우 예외가 발생 (파이썬, 자바스크립트, PHP 등)
    • 정적 타입: 컴파일 타임에 모든 변수의 유형을 결정하고 잘못된 경우 예외가 발생 (C, C++, JAVA등)
  • 최근에는 파이썬이나 자바스크립트 같은 동적 타입 언어로 개발할 때도 정적 타입 언어의 특성들을 활용하여 시스템의 안정성을 개선하는 개발 방식이 일반화 되고 있다.

정적 타입시스템의 필요성

  • 코드 가독성 개선, 문서화에 도움이 됨
  • 잘못된 타입으로 인한 버그 예방
  • 코드와 서비스의 안정성 확보

파이썬의 타입 힌트

  • 파이썬의 타입 힌트는 파이썬 코드에 정적 타입을 선택적으로 제공하는 기능이다.

  • 타입 힌트를 추가하면 IDE로 코딩할 때 잘못된 타입을 사용할 경우에 경고 표시가 뜨는 것 같은 기능을 활용할 수 있다. 현재 대부분의 IDE에서 타입 힌트를 지원하고 있다

  • 모든 부분에 타입 힌트를 적용할 필요는 없지만, 타입 힌트가 많을 수록 정적 코드 분석도구(mypy 등)를 통해 프로그램의 잠재적 버그를 찾아낼 수 있는 정보도 많아진다.

  • 타입 힌트를 위하여 파이썬 3.5버전부터 typing이란 내장 모듈을 지원한다.

  • 타입 힌트 사용법:


from typing import List, Set, Dict, Tuple, Optional, Union

# 변수
my_number: int = 777
my_string: str = "foo"
foo: float # 타입 힌트가 있다면 변수에 값을 할당하지 않아도 괜찮다
foo = 3.14

# 함수 매개변수와 리턴 값
def add(a: int, b: int) -> int:
	return a + b
    
# 다중 타입 설정
spam: Union[int, str, float] = 42
spam = "hello"
spam = 3.14

# 파이썬 3.10 부터는 다음과 같은 코드도 가능하다.
spam: int | str | float = "spam"

# None과 다중 타입 설정
last_name: Optional[str] = None
last_name = "Kim"

# 모든 데이터 타입이 가능한 Any
spam: Any = 42
spam = "spam"
spam = True

# 내부 값을 정하지 않은 리스트
spam: list = [1, 2, 3, 'foo', 3.14, True]

# 내부 값을 구체적으로 선언한 리스트
cat_name: List[str] = ["simon", "garfield", "chester"]
numbers: List[Union[int, float]] = [42, 3.14, 99.9, 87]

# https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#built-in-types

# 간단한 파이썬 빌트인 타입들은 임포트할 필요가 없고 그냥 사용하면 된다
x: int = 1
x: float = 1.0
x: bool = True
x: str = "test"
x: bytes = b"test"

# 컨테이너 타입들(list, set, tuple 등)은 빌트인 타입들을 사용하여 
# 대괄호 안에 컨테이너 요소들의 타입 힌트를 작성할 수 있다
# 단 이런 경우에 빌트인 타입들을 사용하는 것은 파이썬 3.9버전 이상에서만 가능하다
x: list[int] = [1]
x: set[int] = {6, 7}

# 파이썬 3.8 이전 버전에서는 위와 같은 타입 힌트를 사용하려면 
# typing 모듈에서 대문자로 시작하는 해당 타입을 임포트해서 사용해야한다
x: List[int] = [1]
x: Set[int] = {6, 7}

# 딕셔너리에선 키와 밸류 모두 타입 지정이 필요하다
x: dict[str, float] = {"field": 2.0}  # 파이썬 3.9+
x: Dict[str, float] = {"field": 2.0}

# 고정된 크기의 튜플은 모든 요소의 타입을 지정할 수 있다.
x: tuple[int, str, float] = (3, "yes", 7.5)  # 파이썬 3.9+
x: Tuple[int, str, float] = (3, "yes", 7.5)

# 고정되지 않은 튜플은 한가지 타입 또는 ellipsis(...)를 사용한다
x: tuple[int, ...] = (1, 2, 3)  # 파이썬 3.9+
x: Tuple[int, ...] = (1, 2, 3)

# None이 될 수 있는 값들은 Optional[]을 사용한다
x: Optional[str] = some_function()

# Mypy는 if절에서 None이 될 수 없는 값을 알고 있다.
if x is not None:
    print(x.upper())
    
# 만약 어떤 값이 절대로 None이 될 수 없다면 assert문을 활용하라
assert x is not None
print(x.upper())
  • Any와 타입힌트가 없는 것의 차이점:
    타입 힌트로 Any를 쓰면 임의 타입의 값을 받아들인다고 명시적으로 작성하는 것이며 타입 힌트가 없는 것은 아직 타입 힌트를 받지 않았음을 의미한다
  • Union, Optional, Any 같은 타입은 꼭 필요한 경우에만 사용하는 것이 좋다.

타입 힌트와 어노테이션의 차이

  • 스택 오버플로우 글: 이 글의 댓글 부분에 따르면 타입 힌트(type hinting)은 어노테이션의 한 기능(application)이라고 한다. 타입 힌트보다 어노테이션이 더 큰 개념인 것 같다.

__annotations__

  • 타입 힌트를 적용한 함수나 클래스 같은 객체의 __annotations__ 속성에 타입 힌트 메타데이터가 딕셔너리로 저장된다.
# 위의 add 함수의 __annotations__ 출력
print(add.__annotations__)
>>> {'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

정적 분석 도구

  • 파이썬의 타입 힌트 구문과 상관없이 파이썬 인터프리터는 힌트를 완전히 무시한다.
  • 타입 힌트가 있다고 해도 인터프리터의 런타임 타입 검사를 강제하지 않는다.
  • 따라서 코드에 보다 엄격한 타입 검사를 실시하려면 mypy 같은 정적 분석 도구를 사용해야 한다.
  • 프로그램이 실행되는 동안이 아니라 실행되기 전에 소스코드를 분석하여 오류를 찾아내기 때문에 정적 분석 도구라고 부른다.
  • 프로그램이 실행되는 중에 분석하는 것은 런타임 분석이나 동적 분석 도구라고 부른다.

참고자료

0개의 댓글