참고한 자료
지난 글
우리는 지난 시간에 Python의 Typing기능의 간단한 기초를 알아보았다
이번 시간에는 조금더 심화 과정의 Typing을 알아볼려고 한다
위에 참고한 블로그 주소가 있는데
조금더 자세한 내용을 알고 싶다면 참고하길 바란다 !!
1. tying.Callable[..., ReturnType]
우리는 지난 시간에 함수를 인자로 받는 경우 어떻게 typing을 해야하는지 배웠다
tying.Callable을 사용해서 [[input Type], ReturnType]을 입력해주었다
그러면 만약 input type을 신경쓰고 싶지 않다면 어떻게 해야할까
이때 사용할 수 있는 것이 tying.Callable[..., ReturnType]이다
쉽게 말해서 ... 을 활용해서 그냥 넘기면 된다
def on_some_event_happened(callback: Callable[..., int]) -> None:
...
def do_this(a: int, b: str, c:str) -> int:
...
on_some_event_happened(do_this)
2. typing.TypeVar
Python에서 여러 타입을 일반화 하는 것을 Generic type이라고 한다
이러한 Generic을 Typing 하는 방법은 TypeVar를 활용하면된다
from typing import Sequence, TypeVar, Iterable
T = TypeVar("T")
def batch_iter(data: Sequence[T], size: int) -> Iterable[Sequence[T]]:
for i in range(0, len(data), size):
yield data[i:i + size]
해석하자면 위의 함수는 Input으로 Sequence[int] or Sequence[str] or Sequence[float] 등 다양한 형태의 type이 모두 들어갈 수 있고 이를 통해서 Iterable한 Sequence형태의 output이 만들어진다
Bound
또한 여기에 bound을 적용해서 타입의 종류를 지정해줄 수 있다
from typing import Sequence, TypeVar, Iterable, Union
T = TypeVar("T", bound=Union[int, str, bytes])
def batch_iter(data: Sequence[T], size: int) -> Iterable[Sequence[T]]:
for i in range(0, len(data), size):
yield data[i:i + size]
즉, 상속될 수 있는 타입을 지정해 줄 수 있는 것이다
여기서 TypeVar['T']에서 'T'는 해당 바운드에 해당하는 모든 type을 허용한다는 것이다
만약 'T' 대신에 들어갈 수 있는 것에는 'S'와 'A' 등의 약속과 같은 기호를 넣을 수 있다 - 반드시는 아님, 지정가능
S = TypeVar('S', bound=str) ## Can be any subtype of str
A = TypeVar('A', str, bytes) # Must be exactly str or bytes
TypeVar의 파라미터로 들어 갈 수 있는 것을 조금더 살펴보면
class typing.TypeVar(name, *constraints, bound=None, covariant=False, contravariant=False)
여기서 name은 'T', 'S' 등이고 constraints은 위의 A를 활용한 코드에서 str, bytes를 지정해준 것으로 반드시 해당 type이어야하는 것을 의미하고 , bound는 수용할 수 있는 타입의 집합을 지정해주는 것이다
참고로 bound와 constraints는 동시에 될 수 없다
또한 covariant, contravariant는 아래에서 한번더 나오지만
아래의 Stack overflow의 설명을 참고하시길..
3. typing.Generic
위에서 배운 TypeVar를 통해서 Generic 타입의 의존관계를 Typing할 수 있었다
하지만
함수가 아니라 클래스에서 Generic 타입을 선언해줄 때는 표현하기가 어렵다
따라서 Class에서 Generic 타입을 선언해줄 때는 TypeVar와 Generic을 같이 사용해주어서 표현할 수 있다
# https://docs.python.org/3/library/typing.html#user-defined-generic-types
from typing import TypeVar, Generic
from logging import Logger
T = TypeVar('T')
class LoggedVar(Generic[T]):
def __init__(self, value: T, name: str, logger: Logger) -> None:
self.name = name
self.logger = logger
self.value = value
def set(self, new: T) -> None:
self.log('Set ' + repr(self.value))
self.value = new
def get(self) -> T:
self.log('Get ' + repr(self.value))
return self.value
def log(self, message: str) -> None:
self.logger.info('%s: %s', self.name, message)
위 코드를 살펴 보면 Class의 get의 input ,set의 return 등이 __init__의 value 타입에 의존적이라는 것을 확인할 수 있다
또한 해당 클래스를 input으로 받는 메소드를 구성해줄때는 Class[type]을 표시해서 특정 type을 지정해줄 수 있다
위의 코드를 input으로 활용하는 메소드를 예시로 작성해보면 다음과 같다
# https://docs.python.org/3/library/typing.html#user-defined-generic-types
from collections.abc import Iterable
def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
for var in vars:
var.set(0)
또한 Pytorch의 DataLoader가 이러한 방식을 활용한 Class이다
...
T = TypeVar('T')
T_co = TypeVar('T_co', covariant=True)
class DataLoader(Generic[T_co]):
r"""
Data loader. Combines a dataset and a sampler, and provides an iterable over
the given dataset.
The :class:`~torch.utils.data.DataLoader` supports both map-style and
iterable-style datasets with single- or multi-process loading, customizing
loading order and optional automatic batching (collation) and memory pinning.
See :py:mod:`torch.utils.data` documentation page for more details.
Args:
...
여기서 T_co 와 T의 차이점은 TypeVar의 covariant True 여부이다
covariant에 대한 참고 자료
4. typing.ParamSpec
ParamSpec은 Callable의 인자를 다른 Callable의 인자로 넘겨 줄 때 사용한다
from typing import TypeVar, Callable, ParamSpec
import logging
T = TypeVar('T')
P = ParamSpec('P')
def add_logging(f: Callable[P, T]) -> Callable[P, T]:
'''A type-safe decorator to add logging to a function.'''
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
logging.info(f'{f.__name__} was called')
return f(*args, **kwargs)
return inner
@add_logging
def add_two(x: float, y: float) -> float:
'''Add two numbers together.'''
return x + y
@add_logging
def send_msg(msg: str) -> None:
print(f"I Sent {msg}")
쉽게 말해서 위의 코드의 add_logging의 내부 함수인 inner에 P.args와 P.kwargs를 지정해주면
아래에 add_two, send_msg에 데코레이터로 활용을 할때 해당 함수 인자의 타입을 받아 올 수 있는 것이다
이 기능은 파이썬 3.10 이상부터 사용할 수 있다고 한다
추가 DOC
'CS🏅 > 파이썬 🖤' 카테고리의 다른 글
[파이썬 - 알쓸신잡] 왜 Dict는 List 보다 빠를까? (0) | 2023.06.12 |
---|---|
[CS]CPU bound vs. I/O bound (0) | 2023.06.08 |
[Python] Typing 1편 (0) | 2023.05.30 |
[Python] Iterator vs. Generator (0) | 2023.03.14 |
[Python] 파이썬 데코레이터 (0) | 2022.12.27 |