Profile picture

[Python] 파이썬 Type Annotation/Hint

JaehyoJJAng2024년 06월 02일

타입 검사

파이썬은 동적 언어로 알려진 언어이다. 변수의 타입을 일일이 명시하지 않아도 되고, 특정 변수의 타입이 중간에 바뀌어도 별 문제는 없다.

다른 언어들(java,rust,c)등의 정적 언어보다 러닝 커브도 낮고 좀 더 빠른 프로그래밍이 가능하다는 장점이 있지만 잘못된 타입을 사용하여 예상치 못한 에러를 발생시킬 수도 있다.

무엇보다 코드 양이 많아지면 잘못된 타입으로 에러를 만나거나 헷갈릴 경우가 많이 생길거다.

그래서 최근에는 동적 언어들에도 타입을 명시하고 검사하는 수요가 많이 늘고있다. 자바스크립트의 경우 타입스크립트가 많은 인기를 얻고있고, 파이썬의 경우에도 3.5 버전 이상부터 Typing 또는 Type Hint를 지원하고 있다.


Typing 기초

파이썬 3.5버전 이후부터 아래와 같이 변수나 함수의 인자, 리턴 값에 타입을 명시할 수 있다. 변수나 인자의 경우 변수: <타입>으로 명시할 수 있고, 함수의 응답 값은 :전에 -> <타입>을 붙여주면 된다

A: int = 3
B: float = 3.5
C: bool = True
D: str = 'TEST'

def process_message(msg: str = None) -> str:
    return msg.strip()

위의 코드를 보면 Aint 타입을, Bfloat타입을 , Cboolean타입을, Dstring타입을 가지고 있다
process_message 함수는 str타입의 msg를 인자로 받고있고 str타입을 리턴하고 있다


이렇게 타입을 명시하면 다음과 같은 이점을 얻을 수 있다

  • 코드의 가독성 증가
  • 코드의 생상성 증가
  • mypy나 pyright와 같은 정적 도구들의 도움으로 사전에 타입 에러를 방지할 수 있다

기초 타입

파이썬의 빌트인 타입인 int, str, float .. 만으로는 우리가 원하는 모든 타입을 명확하게 명시할 수는 없다. 이런 경우에는 파이썬의 typing 모듈의 도움을 받으면 된다.


1. typing.Union

하나의 함수의 인자에 여러 타입이 사용될 수 있을 때 typing.Union을 사용하도록 하자

from typing import Union

def process_message(msg: Union[str,bytes,None]) -> str:
    ...

위의 코드는 str 또는 bytes 타입 혹은 None 값을 인자로 받는다


2. typing.Optional

Union[<타입>,None]Optional로 대체할 수 있다

from typing import Optional

def process_message(msg: Optional[str,bytes]) -> None:
    ...

위의 코드에서 msg의 인자로 str 또는 bytes 혹은 None 값을 인자로 받는다

❗️ 참고

파이썬 3.10 버전 부터 Union이나 Optional 타입 대신 |를 사용할 수 있다.

Union / Optional 타입 사용 시 코드

from typing import Union,Optional

def eat_food_optional(food: Optional[str] = None) -> Optional[str]:
    if food:
        return f'{food} is goood'

def eat_foot_union(food: Union[str,None] = None) -> Union[str,None]:
    if food:
        return f'{food} is goood'

| 사용 시 코드

@app.get('/todos/{todo_id}')
def get_todo_handler(todo_id: int) -> Dict[str,int | str | bool]:
    return todo_data[todo_id] if todo_id in todo_data else {}
def eat_food(food: str | None = None) -> str | None:
    if food:
        return f'{food} is goood'

3. typing.자료형

  • typing.List
  • typing.Tuple
  • typing.Dict

리스트(배열)안에 있는 타입을 나타내주기 위해서는 typing.List[<타입>]를 사용하면 된다.

from typing import List

class ListType():
    names: List[str] = []

lt = ListType()
lt.names.append('Hello')

튜플의 타입을 나타내주기 위해서는 typing.Tuple[<타입>,<타입>]를 사용하면 된다

from typing import Tuple

location: Tuple[int,int,int]

딕셔너리의 타입은 typing.Dict[<키 타입>,<밸류 타입>]를 사용하면 된다

from typing import Dict

map: Dict[str,int] = dict()
map['number'] = 50

❗️ 참고

파이썬 3.9 버전 이상부터는 리스트, 튜플, 딕셔너리에 대해 typing 모듈을 임포트하지 않아도 아래와 같은 타입 명세가 가능하다

names: list[str]
location: tuple[int,int,int]
map: dict[str,int]

4.typing.TypedDict

딕셔너리의 경우 밸류의 타입이 하나로 고정되는 일만 있는 것은 아니다. 이럴 때는 TypedDict를 활용할 수 있다.
TypedDict를 상속받은 클래스를 만들고 다음과 같이 키와 밸류의 타입을 매칭 시켜주면 된다 (파이썬 3.8부터 지원)

from typing import TypedDict

class Person(TypedDict):
     name: str
     age: int
     gender: str

def calc_cost(person: Person) -> float:
    ...

또는 아래와 같이 사용할 수 있다

from typing import TypedDict
Person = TypedDict("Person", name=str, age=str, gender=str)
Person = TypedDict("Person", {"name": str, "age": int, "gender": str})

❗️ 참고 - dataclass

TypedDict는 많은 경우에 dataclass로 대체해서 사용 가능하고, 데이터 객체를 표현하기 위해 해당 방법이 더 적절할 수 있다 (파이썬 3.7부터 지원)

from dataclass import dataclass

@dataclass
class Person:
    name: str
    age: int
    gender: str

def calc(person: Person) -> float:
    ...

Loading script...