개발환경 준비하기 (VSCode, vim-lsp) § 코루틴과 제네레이터에 타입 주석 넣기 § Protocol로 복잡한 타입 정의 및 조합하기 § Generic으로 플러그인 시스템 설계하기 § 동적 타입 검사를 보완적으로 활용하기 (JSON + trafaret) § Python 미래 버전의 타입 관련 기능 소개 Contents
강타입 언어이며 동적 타입 언어이다. – Strong type: 오브젝트의 타입이 중간이 예측 불가능한 방식으로 변하지 않음 – Dynamic type: 변수가 아닌 오브젝트에 타입이 지정됨 üb = 1; b = 'a'가 가능한 이유 : 변수 b는 오브젝트를 가리키는 이름일 뿐이며 1이나 'a'의 타입은 변하지 않음 ü단점 : IDE나 타입검사 도구가 변수의 타입을 정적으로 결정하기 어려움 (코드의 가능한 모든 분기를 분석하거나 코드를 실행해봐야 알 수 있음) Myth busting
코드를 정적 타입(static typing)으로 작성하기 위한 명세 – 함수 인자, 함수의 반환값, 변수 (property 포함) 타입 정의 가능 – 말 그대로 주석이므로 런타임에 별도의 검사가 이뤄지지 않고 무시됨 § 도입 효과 – 여러 사람이 작성하는 규모가 큰 프로젝트에서 코드 유지보수가 쉬워짐 – IDE나 type checker가 사전에 타입 불일치에 의한 버그를 발견할 수 있게 됨 ü특히 값이 None인 경우에 대한 처리 빠뜨리는 코드 – 읽을 거리 : https://dropbox.tech/application/our-journey-to-type-checking-4- million-lines-of-python 타입 주석(type annotation)
... class Document: title: str body: str metadata: Mapping[str, Any] parser: ClassVar[Parser] def somecode(): ... q: asyncio.Queue[bytes] = asyncio.Queue() ... def somecode2(): ... q: asyncio.Queue[bytes] q = asyncio.Queue() 함수의 인자와 반환 타입 지정 § 현존 static type checker 구현들은 인자 기본값이 None인 경우 Optional 붙은 것으로 자동 해석함 § mypy는 반환 타입이 지정되었을 때만 함수 본문의 타입 검사를 수행함 클래스·인스턴스 변수의 타입 지정 § 명시하지 않은 경우, __init__() 메소드 내에서 self의 속성에 할당하는 값의 형식에 따라 자동 추론하기도 함 일반 변수의 타입 지정 § 특히 다른 변수들을 담을 수 있는 컨테이너(자료구조) 타입에 대해서는 generic을 활용하여 하위 타입을 정의할 수 있음 일반 변수의 사용 전 타입 선언 § 런타임에는 아무 기능을 하지 않는 코드이지만 별도의 문장으로 타입 선언만 먼저 해두는 것도 가능함 ⚠
class Point(typing.TypedDict): x: int y: int @dataclass.dataclass class Point: x: int y: int @attr.s(auto_attribs=True) class Point: x: int y: int 내장 dataclass 패키지 및 외부 attr 패키지 § 인스턴스 변수의 타입 선언을 런타임에 읽어들여 그에 맞는 타입을 갖는 생성자 및 속성 접근자를 제공하는 구조체 클래스를 생성 collections.namedtuple의 다른 선언 방법 § 필드의 타입을 지정한 named tuple을 클래스로 선언 § __annotations__ 속성이 추가 제공되어 필드별 타입을 dict 형식으로 조회할 수 있음 dict의 key별 타입을 고정하여 사용하는 방법 § 런타임 동작은 일반 dict와 동일하지만 static type checker들이 키의 존재 유무 및 타입 검사가 가능하도록 도와줌
generator 및 iterator인 경우 – 둘의 차이에 대해서는 관련 블로그 글 및 번역문을 참고! https://mingrammer.com/translation-iterators-vs-generators/ § Iterator 자리에는 Generator를 넣을 수 있지만, Generator 자리에는 Iterator를 항상 넣을 수는 없음 Generator[YieldType, SendType, ReturnType] AsyncGenerator[YieldType, SendType] Iterator[YieldType] AsyncIterator[YieldType] Coroutine[YieldType, SendType, ReturnType] = Generator + __awaitable__() Awaitable[ReturnType]
특정 메소드들의 부분적 인터페이스 요건만 따로 정의할 수 있음 – 하나의 오브젝트가 여러 Protocol을 구현할 수 있음 (=조합할 수 있음) – 예시) Iterable, Iterator, Callable, Sized, Hashable, ... Protocol로 부분적 타입 정의하기 from typing import Protocol class Closable(Protocol): def close(self) -> None: pass def close_all( things: Iterable[Closable] ) -> None: for t in things: t.close() f = open('foo.txt') close_all([f]) # OK! close_all([1]) # Error: 'int' has no 'close' method
같은 경우는 어떻게 할까? § 런타임에 타입 검사하기 – typeguard : 타입 주석을 런타임에 읽어들여 검사 – trafaret : 임의의 직렬화 데이터를 런타임에 구조 검사 동적 타입 검사 기법(feat. JSON) import datetime import trafaret as t date = t.Dict(year=t.Int, month=t.Int, day=t.Int) >> (lambda d: datetime.datetime(**d)) assert date.check({'year': 2012, 'month': 1, 'day': 12}) == datetime.datetime(2012, 1, 12) (example from https://github.com/Deepwalker/trafaret/)
검사 및 변환 Backend.AI의 trafaret 활용 @attr.s(auto_attribs=True, slots=True, frozen=True) class DestroySessionParams: session_id: str @classmethod def as_trafaret(cls) -> t.Trafaret: return t.Dict({t.Key('session_id'): t.String}) async def destroy_session( request: web.Request ) -> web.Response: params = await check_params(request, DestroySessionParams) await do_destroy(params.session_id) return web.Response(status=204) async def check_params(request, param_cls): try: raw_params = await request.json() checked_params = param_cls.as_trafaret() \ .check(raw_params) return param_cls(**checked_params) except t.DataError as e: raise web.HTTPInvalidRequest(body=json.dumps({ "title": "Invalid API parameters", "type": "https://api.backend.ai/probs" "/invalid-api-params", "data": e.as_dict(), }), content_type="application/problem+json") t.DataError는 다중 key를 가진 t.Dict에서 여러 key에서 오류가 발견될 경우 모든 오류 key에 대한 오류 설명을 포함하고 있기 때문에 디버깅 및 사용자 친화적 오류 UX 설계에 도움이 됨
타입주석을 AST 구문분석만 하고 실제로 실행하지 않음 – Python 3.7부터 __future__.annotations 불러와 활성화 가능 (Python 3.10에서 기본으로 활성화) § PEP-585: Type Hinting Generics In Standard Collections – Python 3.9 주요 신기능! – 내장 컨테이너 타입들에 제네릭 요소 타입 지정 기능 곧 도입될 기능들 from typing import List a: List[int] a: list[int]