Type casting in Python

오픈 소스 프로젝트를 하나 시작했습니다. Typeable 이라는 것인데 파이썬에 타입 캐스팅을 도입하는 것을 목표로 하고 있습니다.

파이썬에 타입 캐스팅이 아예 없는 것은 아닙니다. 가령 int(3.14) 같은 표현은 사실상 float 형을 int 형으로 바꾸는 타입 캐스팅입니다. set([1,2,3]) 도 마찬가지입니다. 이처럼 파이썬의 타입(혹은 생성자)은 하나의 인자를 제공할 때 타입 캐스팅 연산자 역할을 하는 경우가 많습니다만, 반드시 그런 것은 아닙니다. bytes(10) 은 타입 캐스팅이라고 보기에는 무리가 있는 걸과를 줍니다.

타입 캐스팅은 변환(conversion)을 수반합니다. 3.143 으로 바꾸는 것이 변환의 예로 충분하지 않다면 int("2021") 을 생각해보십시오. 이런 변환이 원하는 것이 아닐 경우도 있는데, 가령 str(b'hello') 는 디코딩하는 대신 "b'hello'" 를 돌려줍니다.

타입 캐스팅은 검사(validation)도 수반합니다. int(None) 처럼 변환이 불가능하면 TypeError 를 일으킵니다. 이 역시 언제나 상황에 맞지는 않습니다. 가령 bool 이나 str 은 거의 예외를 일으키지 않습니다. (사실 일부러 그렇게 만드는 경우를 빼고는 당장 생각나는 경우가 없습니다.) 또 앞의 str(b'hello') 과는 달리 그 반대인 bytes('hello')TypeError 를 일으킵니다.

이 처럼 파이썬에 타입 캐스팅이 부분적으로 지원되기는 하지만, 일관성이 결여되어 있습니다. 즉 타입 캐스팅을 언어 기능으로 일관되게 지원하려는 의도가 없었다는 뜻입니다. 또 한가지 문제는, 얕은(shallow) 타입 캐스팅만 지원할 뿐, 깊은(deep) 타입 캐스팅을 지원하지 않는다는 것입니다. 파이썬의 컨테이너 형들에 대해 앝은 복사를 지원하는 copy.copy() 와 깊은 복사를 지원하는 copy.deepcopy() 가 표준 라이브러리에서 제공되고 있지만 타입 캐스팅에는 이런 지원이 없습니다. 예를 들어 봅시다. val 을 키가 문자열이고 값이 정수의 리스트인 딕셔너리로 변환한다고 합시다. 아마 다음과 같이 작성할 수 있을겁니다:

{str(k): list(map(int,v)) for k, v in val.items()}

이 코드가 변환이 가능한 경우를 일부 놓치는 문제는 제외하더라도, 자료 구조를 코드로 매변 옮겨 적어야 한다는 것이 더 큰 문제입니다. 뭔가 더 좋은 방법이 있어야만 합니다. 그렇게 믿는 이유는 이미 파이썬에서 "키가 문자열이고 값이 정수의 리스트인 딕셔너리" 라는 표현을 코드로 나타내는 방법을 제공하고 있기 때문입니다. dict[str,list[int]] 가 그 표현입니다. (3.9 이전 버전에서는 이 표현은 허락되지 않습니다. 대신 typing.Dicttyping.List 를 사용해서 Dict[str,List[int]] 라고 적어야 합니다.) 저는 예전에 주석으로 {str:[int]} 라고 적어놓고는 했습니다만, 파이썬 표준 방식은 조금 더 장황한 양식을 요구합니다. 어쨌거나 이것도 꽤 직관적이고 간결합니다. 자 그러면 혹시 dict[str,list[int]](val) 하면 될까요? 에러는 발생하지 않습니다만, 결과는 원하는 결과가 아닙니다. dict(val) 한 것과 정확히 같은 결과입니다. 다시 말해 dict[str,list[int]] 라는 표현은 형 어노테이션으로는 쓸모가 있지만 런타임 환경에서는 dict 와 동작이 같습니다.

typing 을 좀 더 살펴보면, typing.cast() 라는 함수가 있습니다. 설명을 조금 보면 이게 바로 우리가 원하는 것입니다. 값을 지정한 형으로 변환한다네요. 하지만 더 읽어보면 형 검사기에 정보를 주기위한 용도로 사용될 뿐, 런타임 환경에서는 값을 그냥 반환한다고 합니다. 자 이것이 typing 모듈의 현 상황입니다. 형 어노테이션과 형 검사기를 지원하지만 런타임 환경에서는 그리 쓸모 없는 모듈. 과연 그럴까요?

형 어노테이션이 런타임 환경에 조금씩 스며들고 있습니다. functools.singledispatch() 를 보십시오, 비록 대안을 제공하고는 있지만 첫번째 인자의 형 어노테이션을 확인합니다. dataclasses 를 보십시오, 이 모듈은 형 어노테이션을 하지 않고는 아예 쓸 수도 없습니다. typing.get_type_hints() 함수는 런타임 환경에서 이런 기능들을 구현하는데 쓸 수 있는 아주 강력한 도구입니다.

그래서 시작했습니다. 런타임 환경에서 동작하는 typing.cast() 를 만들자. 이제 겨우 부분적이나마 동작하는 버전이 나와서 공유합니다. 그래서 앞에서 예로 든 변환은 이제 이렇게 수행할 수 있습니다:

cast(dict[str,list[int]], val)

앞으로 나눌 이야기가 많으니 오늘은 이만 합니다.

Typeable 의 설명서는 https://typeable.flowdas.com 에 있습니다. 설치하려면 pip install typeable 하시면 됩니다.