Python에도 Type을 써야 하나?

https://medium.com/depurr/python-type-hinting-a7afe4a5637e

Python은 동적 타이핑 언어기 때문에 함수 및 클래스의 parameter type을 명시해주지 않아도 된다.

따라서 C++의 templates나 JAVA에서의 generic 없이도 함수 및 클래스를 type에 상관없이 재사용할 수 있다.

 

C++로 예를 들면 template을 통해서 type을 임의로 선언해뒀다가 호출 시에는 type을 명시해줘야 한다.

이는 template으로 임시로 부여한 type을 대신할 명시적인 type을 입력하는 것으로 정적 타이핑 언어인 C++에선 필수적이다. 

#include<iostream>
#include<string>
 
using namespace std;
 
template <typename T>
T sum(T a, T b){ // template으로 임의 type 할당
    return a + b;
}
 
int main(void) {
    int a=1, b =2;
    
    double d1 = 2.2;
    double d2 = 3.3;
 
    string s1 = "Show me ";
    string s2 = "The Money 6";
    
    cout << "int 합 : " << sum<int>(a, b) << endl;
    cout << "double 합 : " << sum<double>(d1, d2) << endl; // 호출할 때 type을 명시해준다.
    cout << "string 합 : " << sum<string>(s1, s2) << endl;
    
    return 0;
}


출처: https://blockdmask.tistory.com/43 [개발자 지망생]

 

이때, C++에선 에러가 발생하지 않기 위해서 type을 지정했지만 type을 지정함으로써 함수에 입력되는 type을 한눈에 확인할 수 있어 가독성이 올라간다는 장점이 있다. 

 

type을 써줘서 가독성이 올라가면 결국 코드 유지 보수에 도움될 것이다. 이점을 이용해 python에서도 type을 써줄 수 있는데 이를 type hint라고 한다. 이때, hint라는 말에서도 알 수 있듯이 type을 강제하지는 않고 단순 가독성만 올려주는 역할을 한다. 

 

이는 다음 예시 코드를 통해 확인할 수 있다. 언급했듯이 type을 강제하지 않기 때문에 1, 2이 출력됐다.

def temp(a : set, b : int):
    print(a, b)  
temp(1, 2)

1, 2

Type hint 사용법

type hint는 모든 변수에 적용 가능하다. 이때, iterable 자료형의 경우 python 3.9 미만 에선

from typing import List, Set, Dict 과 같이 선언하면 iterable 내부의 type 또한 지정할 수 있다.

자주 쓰이는 type hint를 정리해보면 다음과 같다.

 

일반 변수
def get_full_name(first_name: str, last_name: str):
    full_name = first_name.title() + " " + last_name.title()
    return full_name

print(get_full_name("john", "doe"))

 

iterable
  • Set, Tuple, List 등등 
  • 성분 별로 따로 명시
from typing import Set, Tuple

def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]):
    return items_t, items_s

 

Union
  • 여러 타입을 가질 때 사용
  • item이 int 또는 str 타입 가질 경우
  • 3.10 버전 제외하고는 from typing import Union 실행해줘야 함
from typing import Union

def process_item(item: Union[int, str]):
    print(item)

 

Optional
  • 일반적으로 가지는 type 외 None type을 가질 경우 사용
  • 미리 None을 가질 수 있음을 명시해 코드 관리 유용
  • 일반적으로 str이지만 None 일 수도 있음
from typing import Optional

def say_hi(name: Optional[str] = None):
    if name is not None:
        print(f"Hey {name}!")
    else:
        print("Hello World")

 

Callable
  • 함수에 대한 type hint
  • parameter와 return에 대한 type 지정 가능
from typing import Callable


def add(a: int, b: int) -> int:
    return a + b

def foo(func: Callable[[int, int], int]) -> int:
    return func(2, 3)

print(foo(add))

 

Class
  • 클래스에 대한 type hint
  • 클래스 이름/이름(str) 형태 모두 가능
class Hello:
    def world(self) -> int:
        return 7
        
# 이름과/이름(str) 형태 모두 가능
hello: Hello = Hello()
hello: "Hello" = Hello() 

def foo(ins: Hello) -> int:
    return ins.world()

print(foo(hello))
  • 이때, 자기 자신 class를 type hint 처리 시 python 3.10 미만에선 str 형태로 처리해야 함
from typing import Optional

class Node:
    def __init__(self, data: int, node: Optional["Node"] = None):  # str 형태
        self.data = data
        self.node = node

 

Final 
  • 상수의 개념을 사용하기 위해서 도입
  • Final로 type hint 설정 후 해당 값을 변경할 경우 type check 시 에러가 발생함
from typing import Final

RATE : Final = 300

RATE = 255 # type check 시 에러 발생

 

Type Alias
  • type에 대하여 alias(별칭)을 지정
  • 만약 type이 너무 길어서 관리하기 힘들 경우에 사용
  • 매우 긴 Union type을 Value라는 alias로 지정
# Value라는 alias로 지정
Value = Union[ 
    int, bool, Union[List[str], List[int], Tuple[int, ...]], Optional[Dict[str, float]]
]

 

TypedDict
  • Dict의 각 성분에 어떤 type이 와야 하는지 지정 가능
  • json 자료형을 dict 자료형으로 변환할 때 활용 
from typing import TypedDict

class Point(TypedDict):
    x: int
    y: float
    z: str
    hello: int

point: Point = {"x": 8, "y": 8.4, "z": "hello", "hello": 12}

 

Type Var
  • 기존 type hint 적용 시 입력 순간 이미 type이 결정되더라도 type을 결정해서 적어줄 수 없다는 단점 존재
from typing import Union, Optional

class Robot:
	def __init__(self, arm : Union[int, str], head : int):
		self.arm = arm
		self.head = head

 # 이 경우 arm의 type이 이미 결정 됐음에도 arm_check에서 type을 
 # Union으로 적어줘 결정할 수 없다는 단점 존재
	def arm_check(self):
		check: Optional[Union[int, str] = None
  • TypeVar를 이용하면 입력 순간 결정된 Type을 계속 해서 적어줄 수 있음
  • 이때, TypeVar를 클래스에서 사용하려면 Generic[TypeVar] 형태로 클래스에 적어줘야 함 
from typing import Union, Optional, TypeVar, Generic

ARM = TypeVar("ARM", int, str, float) #  type 미리 정의 

class Robot(Generic[ARM]): # Generic[TypeVar] 필요
	def __init__(self, arm : ARM, head : int):
		self.arm = arm
		self.head = head

 # 이 경우 arm의 type이 이미 결정 됐음에도 arm_check에서 type을 
 # Union으로 적어줘 결정할 수 없다는 단점 존재
	def arm_check(self):
		check: Optional[ARM] = None
 
 # TypeVar 명시
 Robot[int](1, 3)  # TypeVar의 type이 int
 Robot[str]("2", 3)  # TypeVar의 type이 str
 Robot[float](3.4, 3)  # TypeVar의 type이 float
  • 만약 Generic을 사용한 클래스를 상속하려면 Generic을 모두 명시해줘야 함
class Siri(Generic[ARM], Robot[ARM]):

참고

 

Python Types Intro - FastAPI

From typing, import List (with a capital L): from typing import List def process_items(items: List[str]): for item in items: print(item) Declare the variable, with the same colon (:) syntax. As the type, put the List that you imported from typing. As the l

fastapi.tiangolo.com

 

타입 파이썬! 올바른 class 사용법과 객체지향 프로그래밍 - 인프런 | 강의

Python으로 생산성있는 개발만 아니라 견고하고 안전하게, 그리고 확장성있는 개발을 하세요! 🔥, - 강의 소개 | 인프런...

www.inflearn.com