본문 바로가기
목차
Python

[Python] Type Annotation: 파이썬에서 변수 및 함수에 type 지정.

by ds31x 2023. 8. 30.
728x90
반응형

 

Python은 Dynamic language이기 때문에 variable은 여러 type object를 가리킬 수 있다.

  • 이는 매우 높은 유연성을 제공해주고, 작은 규모의 소스코드에서는 잘 동작한다.
    (특히 type에 대해 자유롭다보니 언어의 진입장벽을 낮춰주는 효과도 있다.)
  • 하지만, object의 implici type conversion이 제한되는 strong typing language  이므로
  • runtime에서 TypeError가 발생할 확률이 커지기 때문에 대규모의 프로젝트에서는 버그가 많아진다는 단점을 가진다.

때문에, 안정성을 중시하는 software를 개발하는 입장에서는 compile 단계에서 type check를 통해 문제점을 사전에 해결할 수 있는 static language가 보다 선호되는 경우가 많다.

 

이같은 단점을 보완하기 위해 Python 3.5부터는 Type Annotation 을 제공하기 시작했다.


Type Annotation

Type annotation을 변수에 적용한 단순한 예는 다음과 같음.

var_int: int = 11
var_float: float = 3.14
var_str: str = 'ABCD'
var_list: list = [0, 1, 2, 3]

 

다음과 같이 parameter의 type에 대해서도 적용가능함.

def product(a: int, b: int) -> int:
    return a*b

하지만, annotation이라는 용어에서 알 수 있듯이

  • 어떤 type으로 해당 parameter에 대한 argument를 입력할지를 알려주기는 하지만,
  • static language에서처럼 compile error등으로 강제하진 못한다.
참고: annotation(어노테이션)이란?
1. 일반적인 의미:
   - 주석, 메모, 설명을 덧붙이는 것
   - 문서나 텍스트에 부가적인 정보를 추가하는 행위
2. 프로그래밍에서의 의미:
  - 코드에 메타데이터를 추가하는 방법
  - 컴파일러나 런타임에 추가 정보를 제공

 

즉, 다음과 같이 다른 type으로 parameter의 값을 지정하더라도
python에서 문제없는 지정이라면 아무 문제 없이 돌아간다. (즉, 일종의 권고안에 불과하다.)

def product(a: int, b: int) -> int:
    return a*b

ret_v = product(10.0, 2.0)

Typing모듈과 Static analyzer (mypy)

static language처럼 type check를 하고 싶다면,

  • type annotation을 typing모듈을 사용하여 보다 명시적으로 처리하고,
  • mypy또는 pyright(Microsoft)와 같은 thrid-party static analyzer를 이용하면 된다.
  • Python 3.5+ 부터 사용가능.
여기선 mypy 의 경우만 살펴본다 .
(별도 설치 필요함)
static analyzer는 코드를 실행하지 않고 분석하는 도구를 총칭함.
static type checker는 static analyzer의 하위카테고리로 타입 관련 오류만 찾는 도구를 가리킴.

 

typing 모듈은 보다 명시적이고 구체적으로 type을 지정해줄 수 있다.

다음은 int를 component로 갖는 Vector를 지정하는 방식임.

from typing import TypeAlias

Vector: TypeAlias = list[int] # Python 3.10+ TypeAlias로 좀 더 명확해짐.
# type Vector = list[int] # Python 3.12+, soft keyword 인 type 가 추가됨(import문 필요없음)

def scale(scalar: int, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

new_vector = scale(3,[3,2,1])

 

만약, 다음과 같이 int가 아닌 float argument가 주어지고

이를 test.py로 저장한 후 mypy test.py로 체크하면 type이 틀렸다고 알려줌.

from typing import TypeAlias

Vector: TypeAlias = list[int]

def scale(scalar: int, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

new_vector = scale(1.7,[3,2,1])

 

mypy로 체크한 결과는 다음과 같음.

$ mypy test.py
test.py:9: error: Argument 1 to "scale" has incompatible type "float"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)


python -m mypy --strict test.py로 수행해도 됨.

위의 예에서는 명시적으로 TypeAlias를 이용했지만 다음과 같이 사용할 수도 있다.

Vector = list[int] 
# import typing
# Vector = typing.List[int] # python 3.8까지는 이 방법만 가능.

def scale(scalar: int, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

new_vector = scale(1,[3,2,1])

list, set, dict 등에서 위와 같이 square bracket(대가로)을 통해 item들의 type을 지정가능하다.


최근 변화로 인한 typing 모듈사용법 변경 정리 (Python 3.9+ 부터)

Python 3.9 미만 버전에서는
from typing import List, Tuple, Dict, Set 등을 사용해야 했지만,
현재는 list, tuple, dict, set등으로 처리할 수 있음
(즉, 단순한 경우엔 굳이 typing 모듈에 필요없음)

generic을 이용한 변경 (Python 3.9+)

# 이전 버전
from typing import List, Dict
def func(items: List[str]) -> Dict[str, int]: ...

# 현재 버전 (3.9+)
def func(items: list[str]) -> dict[str, int]: ...

 

 

typing모듈이 필요한 경우는

  • 하나의 variable 또는 parameter에 여러가지 type을 허용하고자 하는 경우: typing.Union을 이용. (Python 3.10 미만버전)
  • None이 지정가능한 parameter의 경우: typing.Optional (Python 3.10 미만버전)
  • 하나의 variable 또는 parameter에 호출이 될 수 있는 function이 지정되도록 하는 경우: typing.Callable을 이용. collections.abc.Callable을 사용하는 것이 보다 권장됨.
  • 재할당이 안 되는 경우: typing.Final

Python 3.10+에서는 UnionOptional 대신에 | operator만으로도 처리가 가능함.
var_real_number : int | float

 

# ------------------
# typing.Union -> | 
# 이전 방식
from typing import Union
def func(x: Union[str, int]) -> None: ...

# 현재 방식 (3.10+)
def func(x: str | int) -> None: ...

# ------------------
# typing.Optional -> | None
# 이전 방식
from typing import Optional
def func(x: Optional[str]) -> None: ...

# 현재 방식 (3.10+)
def func(x: str | None) -> None: ...

# ------------------
# typing.Callable -> | collections.abc.callable
def function_name(param1: int, param2: str) -> bool:
    # 함수 내용
    return True  # 또는 False    
# 이전 방식
from typing import Callable
def process_data(func: Callable[[int, str], bool]) -> None:
    pass
# 현재 방식 (3.9+)
from collections.abc import Callable
def process_data(func: Callable[[int, str], bool]) -> None:
    pass
    
# ------------------
# typing.Final
from typing import Final

MAX_SIZE: Final = 100
# MAX_SIZE: Final[int] = 100 #명시적으로 타입도 지정가능.
# 타입 체커에서만 경고, 런타임에서는 재할당 가능

typeguard 를 이용한 type check

typeguard 모듈의 @typechecked 데코레이터를 이용하여 보다 엄격하게 TypeError를 발생시킬 수 있다. 이 경우 유연성은 부족하지만, 디버깅에서 어느 type으로 인한 것인지가 명확해진다.

from typing import Tuple
from typeguard import typechecked

@typechecked
def calculate_area(dimensions: Tuple[int, int]) -> int:
    width, height = dimensions
    return width * height

my_dimensions = (10, 20)
area = calculate_area(my_dimensions)
print(area)

my_wrong_dimensions = (10, "20")
area = calculate_area(my_wrong_dimensions)  # This will raise a TypeError
print(area)
  • 위의 code snippet에서 @typechecked를 제거하면 TypeError없이 동작한다.
  • @typechecked가 붙을 경우 엄격한 type check가 이루어지고 TypeError가 발생함.

typing 모듈의 Union, Optional, Callable, Final 

Union

다음의 함수의 parameter rint 또는 float의 argument가 할당될 수 있음.

from typing import Union

def ds_squared(
    r: Union[int,float]
) -> Union[int,float]:
    return r**2

 

앞서 설명한대로, 3.10 이상의 파이썬을 사용하는 경우 |(vertical bar)로 대체 가능함.

Callable

Callable[[int], str]int type의 single parameter를 가지며, 반환값이 str이다.

특이한 경우로 Callable[... , str]인 경우 어떤 형태의 parameter도 가능함을 의미.

주의할 것은 ...은 parameters로는 사용가능하나, return value에 대해서 사용할 수 없다는 점임.

 

참고:  ...과 Any와의 차이

  • ... : 입력 파라미터의 갯수와 타입이 제한되지 않음을 의미. 갯수지정의 문제로 returne value에선 사용 불가.
  • Any: 반환값이나 파라미터가 어떤 타입도 될 수 있음을 의미. return value에 대해 지정가능.
from typing import Callable
# from collections.abc import Callable  # 3.9+ 권장: collections.abc 사용


# Example 1: Callable[[int], str]
def process_number(func: Callable[[int], str], number: int) -> str:
    return func(number)

def number_to_string(n: int) -> str:
    return f"The number is {n}"

result = process_number(number_to_string, 42)
print(result)  # 출력: The number is 42

# Example 2: Callable[..., str]
def process_anything(func: Callable[..., str], *args, **kwargs) -> str:
    return func(*args, **kwargs)

def greeting(name: str, age: int) -> str:
    return f"Hello {name}, you are {age} years old."

result = process_anything(greeting, "Alice", 30)
print(result)  # 출력: Hello Alice, you are 30 years old.

 

Python 3.9+ 에서는 collections.abc.Callable을 대신 사용하기를 권함

Optional

Optional을 통해 None인 경우를 허용하면서 None이 아닌 경우 어떤 타입인지를 square bracket으로 지정할 수 있음.

def ds_power(
    base : int|float,
    exp : Optional[float] = None,
) -> int|float:
    if exp == None:
        return base**2
    else:
        return base**exp

 

앞서 설명한대로, 3.10 이상의 파이썬을 사용하는 경우 | None 으로 대체 가능함.
def ds_power(
    base: int | float,
    exp: float | None = None,
) -> int | float:
    if exp is None:
        return base ** 2
    return base ** exp

Final

from typing import Final

PROTOCOL: Final[str] = 'https'
  • Final의 역할:
    • Final은 변수의 값이 프로그램 실행 도중 변경되지 않아야 함을 나타냄.
    • 타입 힌트로만 동작하므로, 실제로 값을 변경하려 하면 런타임 에러는 발생하지 않지만, 정적 타입 검사 도구(mypy)는 이를 감지.
  • Final[str]:
    • 이 변수는 문자열(str) 타입이어야 하며, 값이 변경되지 않아야 함을 의미.
  • PROTOCOL 변수:
    • PROTOCOLhttps라는 값을 가지며, 이후 코드에서 이 값을 변경하면 타입 검사 도구가 경고를 발생시킴.

참고자료

https://docs.python.org/3/library/typing.html

 

typing — Support for type hints

Source code: Lib/typing.py This module provides runtime support for type hints. For the original specification of the typing system, see PEP 484. For a simplified introduction to type hints, see PE...

docs.python.org

https://www.sourcetrail.com/python/python-strong-type/

 

Solved: strong type in Python - SourceTrail

Are you looking to improve your website's typography? If so, you're in luck! Strong type is a powerful tool that can help you improve your website's readability and overall user experience. In this article, we'll explore the basics of strong type and how y

www.sourcetrail.com


https://dsaint31.tistory.com/515

 

[Python] (Data) Type: Summary

1. Type 이란?Programming에서 사용되는 모든 value 혹은 object 들의 종류 (category) 를 (data) type이라고 부름.수학에서 숫자의 종류(type)를 실수, 정수, 자연수 등으로 나누는 것을 생각하면 쉽다.Programming에

dsaint31.tistory.com


 

728x90