PEP 484에 대한 소개

Python 3.5 버전에는 닀음곌 같은 형식윌로 IDE와 윔드 가독성에 도움을 쀄 수 있도록 핚수의 읞자와 반환값에 대한 타입 힌튞가 처음윌로 도입 되었닀.

def greeting(name: str) -> str:
    return 'Hello ' + name

귞늬고 후에 나옚 3.6 버전에서는 읞자와 반환값 만읎 아니띌 변수에도 타입 힌튾 표Ʞ가 가능핎졌닀.

def greeting(name: str) -> str:
    s: str = 'Hello ' + name
    return s

타입 힌튞의 의의와 목표

타입 힌튾 Ʞ능은 타입 표시에 ꎀ한 표쀀 구묞을 제공하고, 더 쉬욎 정적 분석곌 늬팩토링 및 타입 정볎륌 추론하는 것에 대한 도움을 죌Ʞ 위핎 만듀얎졌닀.

예륌 듀얎 예상하지 못한 타입읎 변수에 할당될 때나 핚수에 전달될 때 IDE나 정적 검사Ʞ는 쉜게 였류띌고 판당할 수 있을것읎닀. 또한 닀륞 사람읎나, 쉜게 잊얎버늎것 같은 윔드에 ì–Žë–€ 타입읎 Ʞ대되는지 쉜게 알렀쀄 수 있닀.

타입 힌튾는 적절한 도구와 핚께 사용될 겜우 정적 ì–žì–Žê°€ 가지는 장점읞 타입 시슀템의 견고핚을 동적 얞얎로썚 조ꞈ읎띌도 따띌잡을 수 있도록 도와쀄 것읎닀.

귞러나 파읎썬읎 정적 타입을 지향하는 것은 아니닀.

타입 힌튾 Ʞ능은 말 귞대로 타입 "힌튾" Ʞ능음 뿐읎닀. 타입 힌튾는 정적 검사Ʞ와 IDE륌 사용하며 윔드의 질을 높읎Ʞ 위핎 사용 될 수 있윌나 결윔 런타임에 영향을 끌치지 않는닀. 정수형을 가질 변수에 묞자엎 타입을 힌튞로 작성핎 놓는닀고 핎서 파읎썬은 아묎런 묞제가 있닀고 생각하지 않을 것읎닀.

사싀 타입 힌튾는 윔드에 붙은 죌석에 가깝닀. 독슀튞링을 doc을 사용하여 가젞올 수 있는 것 처럌 타입 힌튾 정볎 또한 annotations속성을 통하여 타입 힌튞륌 가젞올 수 있닀.

타입 힌튞륌 표현하는 묞법

파읎썬의 타입 힌튾는 typing 몚듈을 사용하여 작성할 수 있닀.

간닚한 타입 표Ʞ

뚌저 핚수 선얞부에 ꎀ한 타입 힌튾는 읞수 뒀에 윜론을 붙여서 읞수의 타입 힌튞륌 붙읎고, ꎄ혞 ë’€ 윜론 전에 "-> 타입" 을 붙읎는 형식윌로 반환값에 대한 타입 힌튞륌 지정할 수 있닀.

def make_post(title: str, author_id: int=0) -> str:
    ...

변수의 타입은 핚수 읞자와 비슷한 형식윌로 힌튞륌 붙음 수 있닀.

num: int = 34  # int type
string: str = "Hello types!"  # str type
test: Test = Test(num)  # class "Test" type

큎래슀 멀버 변수도 변수와 비슷하닀.

class A:
    x: int  # int type
    y: str  # str type
    z: float  # float type

    def __init__(self, x: int, y: str, z: float):
        self.x = x
        self.y = y
        self.z = z

특별한 타입듀

타읎핑 몚듈에는 특별한 타입듀 몇가지가 졎재한닀.
ê·ž 쀑 Any와 NoReturn에 대핮 소개한닀.

Any는 말 귞대로 몚든 타입을 허용한닀.

x: Any = 3  # 된닀
y: Any = "anyone"  # 된닀

NoReturn은 늬턎읎 되는것읎 아니띌 예왞륌 발생시킀는 등의 겜우에 사용한닀.

from typing import NoReturn

def stop() -> NoReturn:
    raise RuntimeError('no way')

타입 별칭

타입 별칭은 간닚하닀. 타입을 새 변수에 대입하멎 ê·ž 변수는 타입 별칭윌로썚 Ʞ능한닀. 닀음은 간닚한 예시읎닀.

from typing import List

url_ls = List[str]  # List with str type 
crawling_result = List[str]  # List with str type 

def crawler(urls: url_ls) -> crawling_result:
    ...

묌론 더욱 복잡한 타입 별칭도 만듀 수 있닀. 읎 윔드는 제넀늭을 활용한 것윌로 추후에 더 알아볌 것읎닀.

from typing import TypeVar, Tuple, Iterable

T = TypeVar('T', int, float)
Vector = Iterable[Tuple[T, T]]

def inproduct(v: Vector[T]) -> T:  # 벡터의 낎적
    return sum(x*y for x, y in v)

객첎로썚의 핚수의 타입

핚수륌 작성하닀 볎멎 핚수륌 반환하는 핚수 또는 핚수륌 핚수륌 작성하게 될 것읎닀. 또는 변수에 핚수 객첎륌 할당할 수도 있닀. 읎러한 객첎로썚의 핚수의 타입은 Callable을 사용하여 표현할 수 있닀.

from typing import Callable

def callback_loader(callback: Callable[[float], int]) -> int:
    # float을 읞수로 받아 int 형을 반환하는 윜백을 읞수로 받는닀.
    return callback(3.7)
from typing import Callable

def closure(txt: str) -> Callable[[], str]:
    # 아묎 읞수도 받지 않고 str 형을 반환하는 핚수륌 반환한닀.
    def inner_func() -> str:
        return txt

    return inner_func
from typing import Callable

def func(txt: str) -> str:
    return txt

x: Callable[[str], str] = func  # 핚수 객첎륌 할당

큎래슀 타입

파읎썬의 타입 힌튾는 특정한 큎래슀띌는 것 또한 명시할 수 있닀.

class A:
    def print_all() -> None:
        ...

def print_class(cls_obj: A) -> None:
    cls_obg.print_all()

a = A()
print_class(a)  # 정적 검사가 통곌할 것읎닀.

귞런데 만앜 상속받은 하위 큎래슀도 받아듀읎렀멎 얎떻게 핎알 할까?

class A:
    def print_all() -> None:
        ...

class B(A):
    ...

def print_class(cls_obj: A) -> None:
    cls_obg.print_all()

b = B()
print_class(b)  # 정적 검사Ʞ는 타입읎 음치하지 않는닀고 겜고할 것읎닀.

읎럎 때륌 위핎 Type[C] 묞법읎 쀀비되얎 있닀. (C는 큎래슀륌 나타낞닀) 귞냥 C륌 타입윌로 사용하멎 C의 읞슀턎슀만을 받아듀읎지만 Type[C]을 사용하멎 상위 큎래슀륌 상속받은 몚든 하위 큎래슀 또한 허용하게 된닀.

from typing import Type

class A:
    def print_all() -> None:
        ...

class B(A):
    ...

def print_class(cls_obj: Type[A]) -> None:
    # A의 하위 큎래슀도 허용
    cls_obg.print_all()

b = B()
print_class(b)  # 정적 검사가 통곌할 것읎닀.

제넀늭

데읎터 형식에 의졎하지 않고 읞자, 변수 또는 반환값 등읎 여러 닀륞 데읎터 타입듀을 가질 수 있는 방식을 제넀늭읎띌고 한닀.

파읎썬의 타입 힌튾 Ʞ능에서도 제넀늭 표현읎 가능하닀.

닀음은 간닚한 제넀늭을 사용한 큎래슀의 선얞곌 사용의 예시읎닀.

from typing import TypeVar, Generic, List

T = TypeVar('T')

class C(Generic[T]):
    def __init__(self) -> None:
        self.ls: List[T] = []
        # T 타입의 늬슀튞륌 쎈Ʞ화한닀

    def put(item: T) -> None:
        # T 타입을 읞수로 받는닀
        self.ls.append(item)

    def get(index: int) -> T:
        # T 타입을 반환한닀
        return self.ls[index]

c = C[str]()  # 타입은  str로 결정된닀
c.put("hello")  # 아묎런 묞제가 없닀
c.get(0)  # str 타입의 값을 반환할 것윌로 Ʞ대된닀.

핚수에도 제넀늭을 적용할 수 있닀.

from typing import TypeVar, Sequence

T = TypeVar('T')

def first(sqnce: Sequence[T]) -> T:
    # 입력받은 시퀀슀 객첎의 타입에 따띌 반환 타입도 결정된닀.
    return sqnce[0]

유니옚

제넀늭곌 달늬 허용 가능한 타입의 범위가 정핎젞 있닀멎 Union을 사용할 수 있닀.
Union은 제한된 타입의 집합읎닀. Union읎 타입 힌튞로 사용된닀멎 핎당 변수-읞수-늬턎값은 핎당 Union에 속핎있는 타입을 가질수 있닀고 표현된닀

from typing import Union

...

def dispencer(select: int) -> Union[Coke, Soda]:
    # Coke륌 늬턎할 수도 있고, Soda륌 늬턎할 수도 있닀.
    ...

였버로딩

였버로딩읎란 같은 핚수 읎늄을 가지지만 읞수가 닀륞 핚수륌 ì„ ì–ží•  수 있는 방법을 말한닀. 묌론 파읎썬은 였버로딩을 지원하지 않지만 typing 몚듈의 @overload 데윔레읎터륌 사용하여 였버로딩읎 가능한 것처럌 볎읎게 할 수 있닀.

였버로드 데윔레읎터륌 사용하Ʞ 위핎서는 였버로드 데윔레읎터륌 적용한 핚수 원형을 만듀고, 였버로드 데윔레읎터가 적용되지 않은 볞첎륌 만듀얎알 한닀.

였버로드 데윔레읎터가 적용된 윔드륌 싀행시쌜 볎멎 데윔레읎터가 없는 볞첎만 싀행된닀.

from typing import overload, Union

class MyIter:

    @overload
    def __getitem__(self, i: int) -> int: ...

    @overload
    def __getitem__(self, s: slice) -> bytes: ...

    def __getitem__(self, x: Union[int, slice]) -> Union[int, bytes]:
        if isinstance(x, int):
            pass
        elif isinstance(x, slice):
            pass

전방 ì°žì¡°

현재(3.7 버전까지)의 타입 힌튾 Ʞ능을 닀음곌 같은 윔드륌 사용할 수 없닀. 힌튞륌 평가할 때 아직 정의되지 않은 타입을 사용할 수 없Ʞ 때묞읎닀.

class Node:
    def __init__(self, right: Node, left: Node):
        # 아직 Node의 정의가 끝나지 않아 였류가 발생한닀.
        self.right = right
        self.left = left

읎 묞제에는 두가지의 핎결 방안읎 졎재한닀. 타입 정의륌 나쀑에 확읞할 수있는 묞자엎 늬터럎로 처늬하던가, from __future__ import annotations 구묞(파읎썬 3.7 만 핎당)을 파음 ë§š 위에 삜입하여 타입 힌튞에 대한 평가륌 lazy 하게 핚윌로썚 핎결할 수 있닀.

class Node:
    def __init__(self, right: 'Node', left: 'Node'):
        self.right = right
        self.left = left
from __future__ import annotations

class Node:
    def __init__(self, right: Node, left: Node):
        self.right = right
        self.left = left

제넀레읎터와 윔룚틎

제넀레읎터 핚수는 Generator[yield_type, send_type, return_type]의 형식윌로 타입 힌튞륌 가질 수 있닀.

def echo_round() -> Generator[int, float, str]:
    res = yield
    while res:
        res = yield round(res)
    return 'OK'

귞러나 비동Ʞ 핚수(윔룚틎)은 조ꞈ 특별하닀. 비동Ʞ 핚수륌 하나 선얞핎 볎자.

async def test():
    return "Hello async!"

만앜 읎 핚수가 묎조걎 await 된닀멎 읎 핚수의 결곌는 str 타입을 가지게 될 것읎지만 await 되지 않는닀멎 윔룚틎 자첎륌 반환하게 될 것읎닀.
비동Ʞ 핚수의 타입 힌튾는 닀음곌 같읎 사용하자.

from typing import Corutine  # Generator와 사용법은 같닀.

async def test() -> Corutine[Any, Any, str]:
    return "Hello async!"

async def run():
    res: Corutine[Any, Any, str] = test()
    res_2: str = await test()

읎륌 용용핎서 비동Ʞ 핚수 객첎륌 표현할 수도 있닀

from typing import Corutine, Callable

async def test() -> Corutine[Any, Any, str]:
    return "Hello async!"

x: Callable[[], Corutine[Any, Any, str]] = test

마치멎서

여Ʞ까지 파읎썬의 타입 힌튞에 대핮 알아볎았닀. PEP 484에서 밝히듯 타입 힌튾는 파읎썬읎 정적 얞얎로 쓰읎Ʞ 위한 것도 아니고, 필수도 아니닀. 귞러나 적절한 도구와 같읎 사용한닀멎 점점 컀지는 윔드륌 만듀얎 가는 것을 도와쀄 수 있을 것읎닀.