CS🏅/파이썬 🖤

[Python] Typing 1편

Dobby98 2023. 5. 30. 23:37

참고자료

 

파이썬 Typing 파헤치기 - 기초편

동적 언어에서의 타입 검사 파이썬은 동적 언어로 잘 알려진 언어입니다. 즉, 변수의 타입을 일일이 명시하지 않아도 되고, 특정 변수의 타입이 중간에 바뀌어도 됩니다. 파이썬과 같은 동적 언

sjquant.tistory.com


요즘 오픈 소스를 살펴보면서 공부하는 습관을 기르고 있다

오픈 소스를 살펴보면 Typing hint로 주는 경우가 있는 이부분은 처음 보고 어색한 부분이었기 때문에

추가적을 공부를 하였다

 

그럼 이제부터 공부한 내용을 기반으로 Python 의 Typing에 대해서 정리해보겠다

 

1. 동적언어의 Typing?

Python은 많은 사람들이 알고 있들이 동적언어이다.

동적언어의 가장큰 장점은 굳이 변수의 타입을 일일이 명시할 필요가 없다는 것이다.

 

Java, C와 같이 정적인 언어에서는 변수의 타입을 지정해주어야하기 때문에 귀찮고 까다롭지만

비교적 Python, JS와 같이 동적언어는 굳이 그럴필요가 없다

 

그럼 왜 동적언어에서 Typing을 할까?

바로 Error를 정확하게 리턴할 수 없다는 점에서 그렇다

 

예를 들어서 '3'과 3의 차이로 인해서 오류가 발생할때 변수에 지정되어있는 type이 없다면 

사용자는 쉽게 해당 변수의 타입을 알 수 없고 오류에 대한 이해도가 떨어질 것이다

따라서 JS에서는 typscript와 같은 언어가 등장함으로 이를 보완해 주었다

Python에서는 3.5 버전부터 Typing hint를 통해서 변수 타입을 나타내는 기능을 추가해 주었다

 

따라서 조금더 안전한 프로그래밍을 할 수 있고 오류의 이해도 빠르게 할 수 있어서 개발 속도도 향상할 수 있다


2. Python Typing 기초

Typing을 하는 방법은 간단하다

먼저 변수 하나에 Typing을 하는 방법은 다음과 같다

a : int = 3

간단하게 설명하면 a라는 변수는 int형임을 나타내고 있다

 

또한 직접 함수를 작성할 때도 함수의 input과 return에 대한 type을 지정해줄 수 있다

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

간단하게 int형인 a,b를 input으로 받아서 int형을 리턴하게 된다

이를 활용하면

1. 조금더 가독성 높은 코드를 작성할 수 있고 

2. vscode 등의 IDE를 활용할 때 조금더 쉽게 type을 확인할 수 있게 된다

3. mypy, pyright를 활용해서 타입에러를 방지하기가 쉬워진다

 

 

GitHub - python/mypy: Optional static typing for Python

Optional static typing for Python. Contribute to python/mypy development by creating an account on GitHub.

github.com

 

 

GitHub - microsoft/pyright: Static Type Checker for Python

Static Type Checker for Python. Contribute to microsoft/pyright development by creating an account on GitHub.

github.com

단순히 Type을 지정해줌으로 인해서 오류를 확인할 수 없다

따라서 mypy나 pyright를 함께 활용하게 되면 vscode와 같은 IDE에서 간단하게 타입 오류를 확인할수 있다

mypy보다 pyright가 조금더 빠르다고 한다!!


3. Type의 종류

간단하게int, float등의 기본적인 type을 활용하여서 거대한 open source나 우리가 활용할 함수의 type을 지정해주는 것은 매우 어려운 일이다 - int, str등 여러 종류의 input 들어갈 수 있는 메소드인 경우 typing이 복잡해짐

 

따라서 python에서는 조금더 포괄적인 개념인 type 종류들이 존재한다

하나씩 살펴보자

 

3.1 typing.Union

from typing import Union

def love(a: Union[str, int, float, None]) -> str:

하나의 메소드에 여러 종류의 타입을 지정해주고 싶으면 Union을 활용해서 마치 list와 같은 형태로 지정해주면된다

해석하면 해당 메소드는 str, int, float, None을 input으로 받을 수 있고 이를 str로 리턴한다

 

3.2 typing.Option

from typing import Optional

def love(a : Optional[int]) -> str:

하나의 type and None을 Optional로 지정해줄 수 있다

쉽게 말해서 하나의 type또는 None을 해당 메소들에서는 input으로 줄 수 있는 것이다

 

3.3 typing.List, typing.Tuple, typing.Dict

우리가 많이 사용하는 List, Dict, Tuple 같은 자료형 변수의 타입을 지정해줄 때는 아래의 방법을 활용해주면 된다

from typing import List, Dict, Tuple

a : List[int]
b : Tuple[int, int]
c : Dict[str, int]

쉽게 설명하면 a라는 변수는 list인데 이때 리스트의 원소는 int라는 소리이다

또한 Tuple의 경우에는 tuple의 원소가 2개이기 때문에 2개의 type을 지정해주면 된다

마지막으로 Dict의 경우 Key와 Value로 구성되어 있기 때문에 각 type을 지정해주면 된다 

 

3.4 TypedDict

앞에서 살펴본 Dict의 방법을 살펴보면 궁금한것이 있을 것이다

바로 value에 여러 종류의 값이 들어갈 때는 어떻게 해야할까?

 

역시 우리의 개발자 행님들은 미리 이를 대비 해두었다

from typing import TypedDict

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

def calc_cost(person: Person) -> float:

간단하게 TypedDict를 활용해서 Class로 타입을 지정해두고 이를 활용해서 typing을 하면된다

so easy

 

또는 아래 처럼 작성도 가능하다

Person = TypedDict("Person", name=str, age=str, gender=str)
Person = TypedDict("Person", "name": str, "age": int, "gender": str})

 

3.5 typing.Generator, typing.Iterable, typing.Iterator

어떤 함수가 제너레이터 기능을 하고 있다면 리턴 type을 Generator로 지정해주면 된다

def one() -> Generator[int, float, str]:
    sent = yield 0
    while sent >= 0:
        sent = yield round(sent)
    return 'Done'
    
    
 #Generator[YieldType, SendType, ReturnType]로 구성
 
 #만약 return 없이 yield로만 구성되어 있다면
 #Generator[YieldType, None, None]
 ## 또는
 # typing.Iterable[YieldType] 또는 typing.Iterator[YieldType]

 

 

3.6 typing.Callable

만약에 함수를 인자로 받는 다면 어떻게 표현해야 할까?

아래의 방식으로 표현하면 된다

 

Callable[[Arg1Type, Arg2Type], ReturnType]의 형식으로 표현하면 된다

def on_some_event_happened(callback: Callable[[int, str, str], int]) -> None:
    ...

def do_this(a: int, b: str, c:str) -> int:
    ...

on_some_event_happened(do_this)

 

3.7  typing.Type

일반적으로 클래스의 객체는 그냥 명시해주면된다.

class Transaction:
    ...

def process_txn(txn: Transaction):
    ...

하지만 클래스를 자체를 인자로 받는다면  typing.Type[Class명]으로 해주면된다

class Factory:
    ...

class AFactory(Factory):
    ...

class BFactory(Factory):
    ...

def initiate_factory(factory: Type[Factory]):

 

3.8  typing.Any

만약 어떤 타입이든 상관이 없다면 typing.Any를 사용해주면된다!! 

그런데 이러면 typing을 사용하는 이유가 없지 않을까??