Python에도 Type을 써야 하나?
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]):
참고